转一篇socket的文章,很有用:
Socket类表现了客户端套接字,它是属于一台或两台计算机的两个TCP通讯端口之间的通讯通道.
一:Socket类
Socket类表现了客户端套接字,它是属于一台或两台计算机的两个TCP通讯端口之间的通讯通道。端口可以连接到本地系统的另一个端口,这样可以避免使用另一台计算机,但是大多数网络软件将使用两台计算机。但是TCP套接字不能与两台以上的计算机通讯。如果需要这种功能,客户端应用程序必须建立多个套接字连接,每台计算机一个套接字。
构造函数
java.net.Socket类有几个构造函数。其中两个构造函数允许使用布尔型参数指定是否使用UDP或TCP套接字,我们不赞成使用它们。这儿没有使用这两个构造函数,并且没有列举在此处--如果需要UDP功能,请使用DatagramSocket。
try
{
// 连接到指定的主机和端口
Socket mySocket = new Socket ( "www.awl.com", 80);
// ......
}
catch (Exception e)
{
System.err.println ("Err - " + e);
}
但是还有很多构造函数可以用于不同的情形。除非特别指出,所有的构造函数都是公共的。
· protected Socket ()-使用当前套接字产生组件提供的默认实现建立不连接的套接字。开发者一般不应该使用这个方法,因为它不允许指定主机名称和端口。
· Socket (InetAddress address, int port)产生 java.io.IOException异常。
· java.lang.SecurityException-建立连接到指定的IP地址和端口的套接字。如果不能建立连接,或连接到主机违反了安全性约束条件(例如某个小的服务程序试图连接到某台计算机而不是载入它的计算机时),就产生这种异常。
· Socket (InetAddress address, int port, InetAddress localAddress, int localPort)产生java.io.IOException、java.lang.SecurityException异常-建立连接到指定的地址和端口的套接字,并把它绑定到特定的本地地址和本地端口。默认情况下,使用一个自由(空)的端口,但是在多地址主机环境(例如本地主机有两个或多个的计算机)中,该方法也允许你指定一个特定的端口号、地址。
· protected Socket (SocketImpl implementation)--使用特定的套接字的实现(implementation)建立未连接的套接字。通常情况下开发者不应该使用这个方法,因为它允许指定主机名称和端口。
· Socket (String host, int port)产生java.net.UnknownHostException、java.io.IOException、java.lang.SecurityException异常--建立连接到特定主机和端口的套接字。这个方法允许指定一个字符串而不是一个InetAddress。如果指定的主机名称不能够解析,就不能建立连接,如果违反了安全性约束条件就产生异常。
· Socket (String host, int port, InetAddress localAddress, int localPort)产生java.net.UnknownHostException、java.io.IOException、java.lang.SecurityException异常--建立连接到特定主机和端口的套接字,并绑定到特定的本地端口和地址。它允许指定字符串形式的主机名称,而不是指定InetAddress实例,同时它允许指定一个将绑定的本地地址和端口。这些本地参数对于多地址主机(如果可以通过两个或更多IP地址访问的计算机)是有用的。如果主机名称不能解析,就不能建立连接,如果违反了安全性约束条件会产生异常。
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类时改变缓冲区大小可能更有效。当对写进行缓冲的时候,更好的选择是使用BufferedOutputStream和BufferedWriter。
⑷ SO_LINGER套接字操作
当某个TCP套接字连接被关闭的时候,可能还有一些数据在队列中等待发送但是还没有被发送(特别是在IP数据报在传输过程中丢失了,必须重新发送的情况下)。Linger(拖延)套接字选项控制未发送的数据可能发送的时间总和,过了这个时间以后数据就会被完全删除。通过使用Socket.setSoLinger(boolean onFlag, int duration)方法完全激活/禁止linger选项、或者修改linger的持续时间都是可以的。
// 激活linger,持续50秒
someSocket.setSoLinger( true, 50 );
⑸ TCP_NODELAY套接字操作
这个套接字选项是一个标记,它的状态控制着是否激活Nagle算法(RFC 896)。因为TCP数据是使用IP数据报在网络上发送的,因此每个包都有一定位数的开销(例如IP和TCP头部信息)。如果在某个时刻每个包中只发送了少量的字节,头部信息的大小将远远超过数据的大小。在局域网中,发送的额外的数据可能不会很多,但是在Internet上,成百、成千、甚至于成百万地客户端可能通过某个路由器发送这种数据包,加起来显著地增加了带宽的消耗。
解决的方法是Nagle算法,它规定TCP在一个时刻只能发送一个数据报。当每个IP数据报得到肯定应答的时候,才能发送新的队列中包含数据的数据报。它限制了数据报头部信息消耗的带宽总量,但是有不太重要的代价--网络延迟。因为数据被排队了,它们不是立即发送的,因此需要快速响应时间的系统(例如X-Windows或telnet)的速度被减慢了。禁止Nagle算法可能提高性能,但是如果被太多的客户端使用,网络性能也会降低。
可以通过调用Socket.setTcpNoDelay(boolean state)方法激活或禁止Nagle算法。例如,为了禁止该算法,可能使用下面的代码:
// 为了得到更快的响应时间禁止Nagle算法
someSocket.setTcpNoDelay(false);
为了获取Nagle算法的状态和TCP_NODELAY标识符,可以使用Socket.getTcpNoDelay()方法:
// 得到TCP_NODELAY标识符的状态
boolean state = someSocket.getTcpNoDelay();
⑹ SO_TIMEOUT套接字操作
超时设置选项是最有用的套接字选项。在默认情况下,I/O操作(基于文件的或基于网络的)都是阻塞的操作。试图从InputStream读取数据将无限期等待直到输入到达。如果输入永远没有到达,应用程序将停止并且在大多数情况下变得不可用(除非使用了多线程)。用户不喜欢不能响应的应用程序,他们认为这类应用程序行为很讨厌。更牢固的应用程序应该预料到这类问题并采取正确的操作。
注意
在测试期间的本地内部网环境中网络问题很少,但是在Internet上,应用程序停止是很可能的。服务器应用程序并没有免疫力--服务器也使用Socket类连接客户端,并且很容易停止。因为这个原因,所有的应用程序(无论是客户端或者服务器)都应该温和地处理网络超时的问题。
当激活SO_TIMEOUT选项时,任何向套接字的InputStream的读取请求都会启动一个计时器。当数据没有按时到达并且计时器超期的时候,就产生java.io.InterruptedIOException异常,你可以捕捉该异常。接着就是应用程序开发者的工作了--可以再次尝试、通知用户或取消连接。可以调用Socket. setSoTimeout(int)方法控制计时器的持续时间,它的参数是等待数据的毫秒数。例如,为了设置5秒钟超时,将使用下面的代码:
// 设置5秒钟超时
someSocket.setSoTimeout ( 5 * 1000 );
激活设置后,任何读取数据的企图都可能产生InterruptedIOException异常,该异常扩展自java.io.IOException类。由于读取数据的企图可能已经产生了IOException异常,所以不需要更多的代码来处理该异常了--但是,有些应用程序可能希望逐步捕捉与超时设置相关地异常,在这种情况下可能需要添加另外地异常处理代码:
try
{
Socket s = new Socket (...);
s.setSoTimeout ( 2000 );
// 执行一些读取操作
}
catch (InterruptedIOException iioe)
{
timeoutFlag = true; // 执行一些操作,例如设置标识符
}
catch (IOException ioe)
{
System.err.println ("IO error " + ioe);
System.exit(0);
}
为了得到TCP计时器的长度,可以使用Socket.getSoTimeout()方法,它返回一个整型值。如果返回值为零表明超时设定被禁止了,任何读取操作将无限期阻塞。
// 查看超时设定是否为零
if ( someSocket.getSoTimeout() == 0) someSocket.setSoTimeout (500);