打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
java tcp客户端与服务器通讯 详解

前 言

Java作为新一代的面向对象编程语言,具有跨平台、安全、高可靠、多线程等特点,在计箅机网络及通信应用软件丌发上得到广泛的应用    

    传输控制协议是一种基于流的网络通讯方法,它与其它的任何协议都有很大的不同。本文讨论TCP流以及在Java中怎样操作它。

第一章 TCP和客户端/服务器范型

  在网络编程中(同样在其它形式的通讯中,例如数据库编程),使用套接字的应用程序也被分为两类--客户端程序和服务器程序。你可能对"客户端/服务器编程"术语比较熟悉,尽管这个术语的准确意思你不一定清楚。下面的范例就是讨论这个主题。

  1、 客户端/服务器范型

  客户端/服务器范型把软件分为两类--客户端程序和服务器程序。客户端软件启动一个连接并发送请求,而服务器软件监听连接并处理请求。在UDP编程环境中,没有建立实际的连接,并且UDP应用程序可以在相同的套接字上建立并接收请求。在TCP环境中,两台计算机之间建立了连接,客户端/服务器范型是相对应的。

  当软件作为客户端或者服务器的时候,它严格地定义了角色以更容易适应我们所熟悉的思维模型。软件要么启动请求,要么处理请求。在两种角色之间切换使系统更加复杂。即使允许切换,在某个特定的时刻软件程序也只能是客户端,而另一个必须是服务器。如果两个同时是客户端,就没有服务器处理请求了。

  客户端/服务器范型是一个重要的理论概念,它广泛用于实际应用程序中。目前也有其它的通讯模型,例如对等(peer to peer)模型,在这种模型中每一方都可以启动通讯。但是客户端/服务器概念是更加流行的选择,因为它很简单并且在多数网络编程中使用。

  2、网络客户端

  网络客户端启动连接,通常处理网络事务。服务器程序用于实现客户端的请求--客户端不用实现服务器的请求。尽管客户端处于控制地位,但是服务器端仍然有一些功能。客户端可以要求服务器删除本地文件系统的所有文件,但是服务器并不是必须执行这个任务的。

  网络客户端使用双方都同意的通讯标准(即网络协议)与服务器对话。例如HTTP客户端使用的命令组就与邮件客户端使用的不同,而且目的也完成不同。把HTTP连接到邮件服务器,或邮件客户端连接到HTTP服务器,要么会出现一个错误消息,要么出现一个客户端不能理解的错误消息。因为这个原因,作为协议规格的一部分,必须使用某个端口号,这样客户端才能定位服务器。Web服务器通常运行在80端口上,而其它一些服务器可能运行在非标准的端口上,URL的习惯是不列出端口的,它假定使用80端口。

  3、网络服务器

  网络服务器的角色是绑定某个特定的端口(客户端使用它定位服务器),并且监听新的连接。尽管客户端是临时的,并且只有在用户选中的时候才运行,但是服务器程序必须不间断地运行(即使实际上没有已连接的客户端),期望某个客户端在某个时刻需要该服务。服务器程序通常作为数据自适应监视器进程引用,使用Unix用法。它持久的运行,而且一般在该服务器程序的主机启动时启动。因此服务器一直等待,直到某个客户端建立到该服务器端口的连接。有些服务器程序在某个时刻只能处理单个连接,其它一些服务器程序可以通过使用多线程同时处理多个连接。

  当开始连接后,服务器就服从客户端。它等待客户端发送请求,并且"忠实地"处理它们(可是服务器可以响应错误信息,特别是当请求违反某些重要地协议规则或有安全风险的时候)。某些协议(例如HTTP/1.0)通常在每个连接中只允许一个请求,而其它一些协议(例如POP3)支持一系列请求。服务器可以通过发送响应或错误消息应答客户端的请求。学习新的网络协议(编写客户端或服务器)与学习一种新的语言相似,只是语法改变了。但是典型情况下,它的命令的数量更小,使事情更简单。服务器的行为一部分由协议决定,一部分由开发者决定(某些命令是可选的,服务器不一定支持)。 

第二章 TCP套接字和Java

 Java提供了对TCP套接字的良好的支持,有两种套接字类:java.net.Socket和java.net.ServerSocket。当编写连接到已有服务的客户端软件的时候使用Socket类。当编写绑定到本地端口以提供服务的服务器软件的时候使用ServerSocket类。这是与DatagramSocket的UDP工作方式不同的地方--在TCP中,连接服务器地和从客户端接收数据的函数被分为两个独立的类。


  Java网络编程之TCP第二部分降再周六发表,在这一部分中我将详细介绍J2EE规范中的Socket类的体系结构,在下周推出的第三部分中,将介绍如何构建客户端和服务器端程序,同时介绍另一个重要的类:ServerSocket;敬请期待。

第三章Socket类

  Socket类表现了客户端套接字,它是属于一台或两台计算机的两个TCP通讯端口之间的通讯通道。端口可以连接到本地系统的另一个端口,这样可以避免使用另一台计算机,但是大多数网络软件将使用两台计算机。但是TCP套接字不能与两台以上的计算机通讯。如果需要这种功能,客户端应用程序必须建立多个套接字连接,每台计算机一个套接字。

1、建立套接字

  在正常环境下,建立套接字的时候它就连接了某台计算机和端口。尽管有一个空的构造函数,它不需要主机名称或端口,但是它是受保护的(protected),在正常的应用程序中不能够调用它。此外,不存在用于在以后指定这些细节信息的connect()方法,因此在正常的环境下建立套接字的时候就应该连接了。如果网络是好的,在建立连接的时候,调用套接字构造函数将立即返回,但是如果远程计算机没有响应,构造函数方法可能会阻塞一段时间。这是随着系统的不同而不同的,它依赖于多种因素,例如正在使用的操作系统和默认的网络超时设置(例如本地局域网中的一些计算机一般比Internet上的计算机响应得快)。你甚至不能肯定套接字将阻塞多长的时间,但是这是非正常的行为,并且它不会频繁出现。即使如此,在关键事务系统中把此类调用放在第二个线程中或许更合适,这样可以防止应用程序停止。

  注意

  在较低的层次,套接字是由套接字产生组件(socket factory)产生的,它是一个负责建立适当的套接字实现的特殊的类。在正常环境下,将会产生标准的java.net.Socket,但是在一些特殊的情形中,例如使用自定义套接字的特殊的网络环境(例如通过使用特殊的代理服务器穿透防火墙),套接字产生组件实际上可能返回一个套接字子类(subclass)。对于错综复杂的Java网络编程比较熟悉,明确为了建立自定义套接字和套接字产生组件的有经验的开发者可以去了解套接字产生组件的细节信息。对于这个主题的更多信息,你可以查看java.net.SocketFactory和java.net.SocketImplFactory类的Java API文档。

  2、使用套接字

  套接字可以执行大量的事务,例如读取信息、发送数据、关闭连接、设置套接字选项等等。此外,下面提供的方法可以获取套接字的信息(例如地址和端口位置):

  方法

  · void close()产生java.io.IOException异常--关闭套接字连接。关闭连接可能允许也可能不允许继续发送剩余的数据,这依赖于SO_LINGER套接字选项的设定。我们建议开发者在关闭套接字连接之前清除所有的输出流。

  · InetAddress getInetAddress()--返回连接到套接字的远程主机的地址。

  · InputStream getInputStream()产生java.io.IOException异常--返回一个输入流,它从该套接字连接到的应用程序读取信息。

  · OutputStream getOutputStream()产生java.io.IOException异常--返回一个输出流,它向套接字连接到的应用程序写入信息。

  · boolean getKeepAlive()产生java.net.SocketException异常--返回SO_KEEPALIVE套接字选项的状态。

  · InetAddress getLocalAddress()--返回与套接字关联的本地地址(在多地址计算机中有用)。

  · int getLocalPort()--返回该套接字绑定在本地计算机上的端口号。

  · int getPort()--返回套接字连接到的远程服务的端口号。

  · int getReceiveBufferSize()产生java.net.SocketException异常--返回套接字使用的接收缓冲区大小,由SO_RCVBUF套接字选项的值决定。

  · int getSendBufferSize()产生java.net.SocketException异常--返回套接字使用的发送缓冲区大小,由SO_SNDBUF套接字选项的值决定。

  · int getSoLinger()产生java.net.SocketException异常--返回SO_LINGER套接字选项的值,它控制连接终止的时候未发送的数据将排队多长时间。
 
  · int getSoTimeout()产生java.net.SocketException异常--返回SO_TIMEOUT套接字选项的值,它控制读取操作将阻塞多少毫秒。如果返回值为0,计时器就被禁止了,该线程将无限期阻塞(直到数据可以使用或流被终止)。

  · boolean getTcpNoDelay()产生java.net.SocketException异常--如果TCP_NODELAY套接字选项的设置打开了返回"true",它控制是否允许使用Nagle算法。

 ·void setKeepAlive(boolean onFlag)产生java.net.SocketException异常--允许或禁止SO_KEEPALIVE套接字选项。

 ·void setReceiveBufferSize(int size)产生java.net.SocketException异常--修改SO_RCVBUF套接字选项的值,它为操作系统的网络代码推荐用于接收输入的数据的缓冲区大小。并不是每种系统都支持这种功能或允许绝对控制这个特性。如果你希望缓冲输入的数据,我们建议你改用BufferedInputStream或BufferedReader。

 · void setSendBufferSize(int size)产生java.net.SocketException异常--修改SO_SNDBUF套接字选项的值,它为操作系统的网络代码推荐用于发送输入的数据的缓冲区大小。并不是每种系统都支持这种功能或允许绝对控制这个特性。如果你希望缓冲输入的数据,我们建议你改用BufferedOutputStream或Buffered Writer。

  · static void setSocketImplFactory (SocketImplFactory factory)产生java.net.SocketException、java.io.IOException、java. lang.SecurityException异常--为JVM指定一个套接字实现的产生组件,它可以已经存在,也可能违反了安全性约束条件,无论是哪种情况都会产生异常。只能指定一个产生组件,当建立套接字的时候都会使用这个产生组件。

  · void setSoLinger(boolean onFlag, int duration)产生java.net. SocketException、java.lang.IllegalArgumentException异常--激活或禁止SO_LINGER套接字选项(根据布尔型参数onFlag的值),并指定按秒计算的持续时间。如果指定负值,将产生异常。

 ·void setSoTimeout(int duration)产生java.net.SocketException异常--修改SO_TIMEOUT套接字选项的值,它控制读取操作将阻塞多长时间(按毫秒计)。0值会禁止超时设置,引起无限期阻塞。如果发生了超时,当套接字的输入流上发生读取操作的时候,会产生java.io.IOInterruptedException异常。这与内部的TCP计时器是截然不同的,它触发未知报文包的重新发送过程。

 ·void setTcpNoDelay(boolean onFlag)产生java.net.SocketException异常--激活或禁止TCP_NODELAY套接字选项,它决定是否使用Nagle算法。

  · void shutdownInput()产生java.io.IOException异常--关闭与套接字关联的输入流,并删除所有发送的更多的信息。对输入流的进一步的读取将会遭遇流的结束标识符。

  · void shutdownOutput()产生java.io.IOException异常--关闭与套接字关联的输出流。前面写入的、但没有发送的任何信息将被清除,紧接着是TCP连接终止,它通知应用程序没有更多的数据可以使用了(在Java应用程序中,这样就到达了流的末尾)。向套接字进一步写入信息将引起IOException异常。

  3、 向TCP套接字读取和写入信息

  在Java中使用TCP建立用于通讯的客户端软件极其简单,无论使用哪种操作系统都一样。Java网络API提供了一致的、平台无关的接口,它允许客户端应用程序连接到远程服务。一旦建立了套接字,它就已经连接了并准备使用输入和输出流读取/写入信息了。这些流都不需要建立,它们是Socket. getInputStream()和Socket.getOutputStream()方法提供的。

  为了简化编程,过滤器可以很容易地连接到套接字流。下面的代码片断演示了一个简单的TCP客户端,它把BufferedReader连接到套接字输入流,把PrintStream连接到套接字输出流。

try
{
// 把套接字连接到某台主机和端口
Socket socket = new Socket ( somehost, someport ); 

// 连接到被缓冲地读取程序
BufferedReader reader = new BufferedReader ( 
new InputStreamReader ( socket.getInputStream() ) );

// 连接到打印流
PrintStream pstream = 
new PrintStream( socket.getOutputStream() );
}
catch (Exception e)
{
System.err.println ("Error - " + e);
}

4、套接字选项

  套接字选项是改变套接字工作方式的设置,并且它们能影响(正反两方向)应用程序的性能。对于套接字选项的支持是在Java 1.1中引入的,在后面的一些版本中对其中一些做了改进(例如在Java 2 Java 3中支持SO_KEEPALIVE选项)。通常情况下,不应该修改套接字选项,除非有很必要的原因,因为这种改变可能反面影响应用程序和网络的性能(例如,激活Nagle算法可能提高telnet类型应用程序的性能,但是会降低可以使用地网络带宽)。唯一的例外是SO_TIMEOUT选项--事实上,如果套接字连接的应用程序传输数据出现失败的时候,它都应该温和地处理超时问题,而不应该因此延迟速度。

  ⑴SO_KEEPALIVE套接字操作

  Keepalive(保持活动)套接字选项是很有争议的,一些开发者认为使用它会很强大。在默认情况下,两个连接的套接字之间没有数据发送,除非应用程序有需要发送的数据。这意味着在长期存活的进程中空闲地的接字可能几分钟、几小时、甚至于几天不会提交数据。但是,假设某个客户端崩溃了,并且连接终结序号没有发送给TCP服务器。贵重的资源(例如CPU时间和内存)将会浪费在哪个永远不会响应的客户端上。如果允许keepalive套接字选项,套接字的另一端可以探测以验证它是否仍然是活动的。但是,应用程序不能控制keepalive探测器的发送频率。为了激活keepalive,需要调用Socket.setSoKeepAlive(boolean)方法,参数的值为"true""false"值将禁止它)。例如,为了在某个套接字上允许keepalive,可能使用下面的代码:

// 激活SO_KEEPALIVE
someSocket.setSoKeepAlive(true);

  尽管keepalive的好处并不多,但是很多开发者提倡在更高层次的应用程序代码中控制超时设置和死的套接字。同时需要记住,keepalive不允许你为探测套接字终点(endpoint)指定一个值。我们建议开发者使用的另一种比keepalive更好的解决方案是修改超时设置套接字选项。

  ⑵SO_RCVBUF套接字操作

  接收缓冲区套接字选项控制用于接收数据的缓冲区。你可以通过调用方法改变它的大小。例如,为了把缓冲区大小改变为4096,可以使用下面的代码:

// 修改缓冲区大小
someSocket.setReceiveBufferSize(4096);
注意:修改接收缓冲区大小的请求不能保证改变成功。例如,有些操作系统可能不允许修改这个套接字选项,并忽略对该值的任何改变。你可以调用Socket. getReceiveBufferSize()方法得到当前缓冲区的大小。使用缓冲的更好的选择是使用BufferedInputStream/BufferedReader

  ⑶ SO_SNDBUF套接字操作

  发送缓冲区套接字选项控制用于发送数据的缓冲区的大小。通过调用Socket.setSendBufferSize(int)方法,你能够试图改变缓冲区的大小,但是改变缓冲区大小的请求可能被操作系统拒绝。

// 把发送缓冲区的大小改为4096字节
someSocket.setSendBufferSize(4096);
为了得到当前发送缓冲区的大小,你可以调用Socket.getSendBufferSize()方法,它返回一个整型值。
// 得到默认的大小
int size = someSocket.getSendBufferSize();

  使用DatagramSocket类时改变缓冲区大小可能更有效。当对写进行缓冲的时候,更好的选择是使用BufferedOutputStreamBufferedWriter

  ⑷ SO_LINGER套接字操作

  当某个TCP套接字连接被关闭的时候,可能还有一些数据在队列中等待发送但是还没有被发送(特别是在IP数据报在传输过程中丢失了,必须重新发送的情况下)。Linger(拖延)套接字选项控制未发送的数据可能发送的时间总和,过了这个时间以后数据就会被完全删除。通过使用Socket.setSoLinger(boolean onFlag, int duration)方法完全激活/禁止linger选项、或者修改linger的持续时间都是可以的。

// 激活linger,持续50
someSocket.setSoLinger( true, 50 );

第四章 建立TCP客户端

  讨论了套接字类的功能后,我们将分析一个完整的TCP客户端程序。此处我们将看到的客户端程序是一个daytime客户端,它连接到一个daytime服务器程序以读取当前的日期和时间。建立套接字连接并读取信息是一个相当简单的过程,只需要少量的代码。默认情况下daytime服务运行在13端口上。并非每台计算机都运行了daytime服务器程序,但是Unix服务器是客户端运行的很好的系统。
  DaytimeClient是如何工作的  Daytime应用程序是很容易理解的,它使用了文章前面谈到的概念。建立套接字、获取输入流,在很少的事件中(在连接时像daytime一样简单的服务器程序失败)激活超时设置。不是连接已筛选过的流,而是把有缓冲的读取程序连接到套接字输入流,并且把结果显示给用户。最后,在关闭套接字连接后客户端终止。这是你可能得到的最简单的套接字应用程序了--复杂性来自实现的网络协议,而不是来自具体网络的编程。

  运行DaytimeClient  运行上面的应用程序很简单。简单地把运行daytime服务的计算机的主机名称作为命令行参数指定并运行它就可以了。如果daytime服务器程序使用了非标准的端口号(在后面会讨论),记得需要改变端口号并重新编译。

第五章 ServerSocket类

服务器套接字是一种特定类型的套接字,它用于提供TCP服务。客户端套接字绑定到本地计算机的任何空的端口,并且连接到特定服务器程序的端口和主机。服务器套接字与它的差别是它们绑定到本地计算机的某个特定的端口,这样远程客户端才能定位某种服务。客户端套接字连接只能连接到一台计算机,然而服务器套接字能够满足多个客户端的请求。

  它工作的方法很简单--客户端知道服务运行在某个特定的端口(通常端口号是知名的,并且特定的协议使用特定的端口号,但是服务器程序也可能运行在非标准的端口上)。它们建立连接,在服务器程序内部,连接会被接受。服务器程序可以同时接受多个连接,在某个给定的时刻也可以选择只接受一个连接。某个连接被接受后,它就表现为正常的套接字,形式为Socket对象--一旦你掌握了Socket类,编写服务器程序就和编写客户端程序几乎一样简单了。服务器程序和客户端程序的唯一区别是服务器程序帮定到特定的端口,使用ServerSocket对象。ServerSocket对象就像创建客户端连接的工厂--你不必亲自建立Socket类的实例。这些连接都模拟正常的套接字,因此你能够把输入和输出过滤流关联到这些连接上。

  1、建立ServerSocket

  你在建立服务器套接字后,就应该把它绑定到某个本地端口并准备接受输入的连接。当客户端试图连接的时候,它们被放入一个队列中。一旦这个队列中的所有空间都被耗尽,其它的连接的就会被拒绝。  建立服务器套接字的最简单的途径是绑定到某个本地地址,该地址作为使用构造函数的唯一的参数。这是ServerSocket构造函数的最简单的形式,但是下面有一些其它的允许更多自定义的构造函数。所有这些函数都是公共的。 

 · ServerSocket(int port)产生java.io.IOException、java.lang.SecurityException异常--把服务器套接字绑定到特定的端口号,这样远程客户端才能定位TCP服务。如果传递进来的值为零(zero),就使用任何空闲的端口--但是客户端可能没办法访问该服务,除非用什么方式通知了客户端端口号是多少。在默认情况下,队列的大小设置为50,但是也提供了备用的构造函数,它允许修改这个设置。如果端口已经被绑定了,或者安全性约束条件(例如安全性规则或知名端口上的操作系统约束条件)阻挡了访问,就会产生异常。

  · ServerSocket(int port, int numberOfClients)产生java.io.IOException、java.lang.SecurityException异常--把服务器套接字绑定到特定的端口号并为队列分配足够的空间用于支持特定数量的客户端套接字。它是ServerSocket(int port)构造函数的重载版本,如果端口已经被绑定了或安全性约束条件阻挡了访问,就产生异常。

  · ServerSocket(int port, int numberOfClients, InetAddress address)产生java.io.IOException、java.lang.SecurityException异常--把服务器套接字绑定到特定的端口号,为队列分配足够的空间以支持特定数量的客户端套接字。它是ServerSocket(int port, int numberOfClients)构造函数的重载版本,在多地址计算机上,它允许服务器套接字绑定到某个特定的IP地址。例如,某台计算机可能有两块网卡,或者使用虚拟IP地址把它配置成像几台计算机一样工作的时候。如果地址的值为空(null),服务器套接字将在所有的本地地址上接受请求。如果端口已经被绑定了或者安全性约束条件阻挡了访问,就产生异常。

  2、使用ServerSocket

  虽然Socket类几乎是通用的,并且有很多方法,但是Server Socket类没有太多的方法,除了接受请求并作为模拟客户端和服务器之间连接的Socket对象的产生组件就没有几个了。其中最重要的方法是accept()方法,它接受客户端连接请求,但是还有其它几个开发者可能感到有用的方法。

 · Socket accept()产生java.io.IOExceptionjava.lang.Security异常--等待客户端向某个服务器套接字请求连接,并接受连接。它是一种阻塞(blockingI/O操作,并且不会返回,直到建立一个连接(除非设置了超时套接字选项)。当连接建立时,它将作为Socket对象被返回。当接受连接的时候,每个客户端请求都被默认的安全管理程序验证,这使得接受一定IP地址并阻塞其它IP地址、产生异常成为可能。但是,服务器程序不必依赖安全管理程序阻塞或终止连接--可以通过调用客户端套接字的getInetAddress()方法确定客户端的身份。

  · void close()产生java.io.IOException异常--关闭服务器套接字,取消TCP端口的绑定,允许其它的服务使用该端口。

  · InetAddress getInetAddress()--返回服务器套接字的地址,在多地址计算机中(例如某个计算机的本地主机可以通过两个或多个IP地址访问)它可能与本地地址不同。

  · int getLocalPort()--返回服务器套接字绑定到的端口号。

  · int getSoTimeout()产生java.io.IOException异常--返回超时套接字选项的值,该值决定accept()操作可以阻塞多少毫秒。如果返回的值为零,accept()操作无限期阻塞。

  · void implAccept(Socket socket)产生java.io.IOException异常--这个方法允许ServerSocket子类传递一个未连接的套接字子类,让这个套接字对象接受输入的请求。使用implAccept方法接受连接时,重载的ServerSocket.accept()方法可以返回已连接的套接字。很少开发者希望对ServerSocket再细分类,在不必要的情况下应该避免使用它。

  · static void setSocketFactory ( SocketImplFactory factory )产生java.io.IOExceptionjava.net.SocketExceptionjava.lang.SecurityException异常--JVM指定服务器套接字产生组件。它是一个静态的方法,在JVM的生存周期中只能调用一次。如果禁止指定新的套接字产生组件,或者已经指定了一个,就会产生异常。

  · void setSoTimeout(int timeout)产生java.net.SocketException异常--accept()操作指定一个超时值(以毫秒计算)。如果指定的值是零,超时设置就被禁止了,该操作将无限制阻塞。但是,如果允许超时设置,在accept()方法被调用的时候就启动一个计时器。当计时器期满时,产生java.io.InterruptedIOException异常,并允许服务器程序执行进一步的操作。

  3、从客户端接受和处理请求

  服务器套接字的最重要的功能是接受客户端套接字。一旦获取了某个客户端套接字,服务器就可以执行服务器程序的所有"真实的工作",包括从套接字读取信息、向套接字写入信息以实现某种网络协议。发送或接收的准确数据依赖于该协议的详细情况。例如,对存储的消息提供访问的邮件服务器将监听命令并发回消息内容。telnet服务器监听键盘输入并把这些信息传递给一个登陆外壳(shell),并把输出发回网络客户端。具体协议的操作与网络的相关性很小,更多的面向编程。

  建立完整实现通用Internet协议的客户端/服务器应用程序需要作大量的工作,对于网络编程的新手来说这一点更为明显。它也需要其它一些技巧,例如多线程编程。从现在开始,我们聚焦于一个简单的、作为单线程应用程序执行的TCP服务器程序框架。

第六章 建立TCP服务器程序

网络编程的最有趣的部分之一是编写网络服务器。客户端发送请求并响应发回来的数据,但是服务器执行大多数真正的工作。  

服务器程序的第一步是建立一个ServerSocket。如果端口已经绑定了,将会产生一个BindException异常,因为两个服务器程序不可能共享相同的端口。否则,就建立了服务器套接字。下一步是等待连接。

  因为daytime是个非常简单的协议,并且我们的第一个TCP服务器程序示例必须很简单,所以我们此处使用了单线程服务器程序。在简单的TCP服务器程序中通常使用无限运行的for循环,或者使用表达式的值一直为true的While循环。在这个循环中,第一行是server.accept()方法,它会阻塞代码运行直到某个客户端试图连接为止。这个方法返回一个表示某个客户端的连接的套接字。为了记录数据,该连接的IP地址和端口号被发送到System.out。你将看到每次某个人登陆进来并获取某天的时间。

  Daytime是一个仅作应答(response-only)的协议,因此我们不需要担心对任何输入信息的读取过程。我们获得了一个OutputStream(输出流),接着把它包装进PrintStream(打印流),使它工作更简单。我们在使用java.util.Date类决定日期和时间后,基于TCP流把它发送给客户端。最后,我们清除了打印流中的所有数据并通过在套接字上调用close()关闭该连接。

  运行DaytimeServer

  运行该服务器程序是很简单的。该服务器程序没有命令行参数。如果这个服务器程序示例需要运行在UNIX上,你需要把变量SERVICE_PORT的值该为1024,除非你关闭默认的daytime进程并作为root运行这个示例。在Windows或其它操作系统上,就没有这个问题。如果需要在本机上运行该服务器程序,需要使用下面的命令:

java DaytimeServer

第七章 异常处理:特定套接字的异常

  网络作为通讯的媒介充满了各种问题。随着大量的计算机连接到了全球Internet,遭遇到某个主机名称无法解析、某个主机从网络断开了、或者某个主机在连接的过程中被锁定了的情形在软件应用程序的生存周期中是很可能遇到的。因此,知道引起应用程序中出现的这类问题的条件并很好的处理这些问题是很重要的。当然,并不是每个程序都需要精确的控制,在简单的应用程序中你可能希望使用通用的处理方法处理各种问题。但是对于更高级的应用程序,了解运行时可能出现的特定套接字异常是很重要的。

  注意

  所有的特定套接字异常都扩展自SocketException,因此通过捕捉该异常,你可以捕捉到所有的特定套接字的异常并编写一个通用的处理程序。此外,SocketException扩展自java.io.IOException,如果你希望提供捕捉所有I/O异常的处理程序可以使用它。

  1、 SocketException

  java.net.SocketException表现了一种通用的套接字错误,它可以表现一定范围的特定错误条件。对于更细致的控制,应用程序应该捕捉下面讨论的子类。

  2、 BindException

  java.net.BindException表明没有能力把套接字帮定到某个本地端口。最普通的原因是本地端口已经被使用了。

  3、ConnectException

  当某个套接字不能连接到特定的远程主机和端口的时候,java.net.ConnectException就会发生。发生这种情况有一个原因,例如远程服务器没有帮定到某个端口的服务,或者它被排队的查询淹没了,不能接收更多的请求。

  4、 NoRouteToHostException

  当由于出现网络错误,不能找到远程主机的路由的时候产生java.net.NoRouteToHostException异常。它的起因可能是本地的(例如软件应用程序运行的网络正在运行),可能是临时的网关或路由器问题,或者是套接字试图连接的远程网络的故障。另一个普通原因是防火墙和路由器阻止了客户端软件,这通常是个持久的限制。

总结

  在TCP中使用套接字通讯是你应该掌握的一种重要的技术,因为目前使用的大多数有趣的应用程序协议都是在TCP上出现的。Java套接字API提供了一种清晰的、易于使用的机制,利用这种机制开发者可以作为服务器接受通讯或作为客户端启动通讯。通过使用前面讨论的概念(包括Java下的输入和输出流),过渡到基于套接字的通讯是很直接的。有了建立在java.net程序包中的异常处理水平后,很容易处理运行时发生的网络错误。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
java socket/Serversocket编程详解(中/英文)
Socket网络编程常见异常
ServerSocket用法详解
Java版:一个极致精简的socket通讯服务器和客户端
Java 网络编程 | 菜鸟教程
JAVA网络编程Socket常见问题 【长连接专题】 - Gstarwd's Techni...
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服