套接字或插座(socket)是一种软件形式的抽象,用于表达两台机器间一个连接的“终端”。针对一个特定的连接,每台机器上都有一个“套接字”,可以想象它们之间有一条虚拟的“线缆”。JAVA有两个基于数据流的套接字类:ServerSocket,服务器用它“侦听”进入的连接;Socket,客户端用它初始一次连接。侦听套接字只能接收新的连接请求,不能接收实际的数据包。

套接字是基于TCP/IP实现的,它是用来提供一个访问TCP的服务接口,或者说套接字socket是TCP的应用编程接口API,通过它应用层就可以访问TCP提供的服务。

在JAVA中,我们用ServerSocket、Socket类创建一个套接字连接,从套接字得到的结果是一个InputStream以及OutputStream对象,以便将连接作为一个IO流对象对待。通过IO流可以从流中读取数据或者写数据到流中,读写IO流会有异常IOException产生。

套接字底层是基于TCP的,所以socket的超时和TCP超时是相同的。下面先讨论套接字读写缓冲区,接着讨论连接建立超时、读写超时以及JAVA套接字编程的嵌套异常捕获和一个超时例子程序的抓包示例。

1 socket读写缓冲区

一旦创建了一个套接字实例,操作系统就会为其分配缓冲区以存放接收和要发送的数据。

JAVA可以设置读写缓冲区的大小-setReceiveBufferSize(int size), setSendBufferSize(int size)。

向输出流写数据并不意味着数据实际上已经被发送,它们只是被复制到了发送缓冲区队列SendQ,就是在Socket的OutputStream上调用flush()方法,也不能保证数据能够立即发送到网络。真正的数据发送是由操作系统的TCP协议栈模块从缓冲区中取数据发送到网络来完成的。

当有数据从网络来到时,TCP协议栈模块接收数据并放入接收缓冲区队列RecvQ,输入流InputStream通过read方法从RecvQ中取出数据。

2 socket连接建立超时

socket连接建立是基于TCP的连接建立过程。TCP的连接需要通过3次握手报文来完成,开始建立TCP连接时需要发送同步SYN报文,然后等待确认报文SYN+ACK,最后再发送确认报文ACK。TCP连接的关闭通过4次挥手来完成,主动关闭TCP连接的一方发送FIN报文,等待对方的确认报文;被动关闭的一方也发送FIN报文,然等待确认报文。

正在等待TCP连接请求的一端有一个固定长度的连接队列,该队列中的连接已经被TCP接受(即三次握手已经完成),但还没有被应用层所接受。TCP接受一个连接是将其放入这个连接队列,而应用层接受连接是将其从该队列中移出。应用层可以通过设置backlog变量来指明该连接队列的最大长度,即已被TCP接受而等待应用层接受的最大连接数。

当一个连接请求SYN到达时,TCP确定是否接受这个连接。如果队列中还有空间,TCP模块将对SYN进行确认并完成连接的建立。但应用层只有在三次握手中的第三个报文收到后才会知道这个新连接。如果队列没有空间,TCP将不理会收到的SYN。

如果应用层不能及时接受已被TCP接受的连接,这些连接可能占满整个连接队列,新的连接请求可能不被响应而会超时。如果一个连接请求SYN发送后,一段时间后没有收到确认SYN+ACK,TCP会重传这个连接请求SYN两次,每次重传的时间间隔加倍,在规定的时间内仍没有收到SYN+ACK,TCP将放弃这个连接请求,连接建立就超时了。

JAVA Socket连接建立超时和TCP是相同的,如果TCP建立连接时三次握手超时,那么导致Socket连接建立也就超时了。可以设置Socket连接建立的超时时间-

connect(SocketAddress endpoint, int timeout)

如果在timeout内,连接没有建立成功,在TimeoutException异常被抛出。如果timeout的值小于三次握手的时间,那么Socket连接永远也不会建立。

不同的应用层有不同的连接建立过程,Socket的连接建立和TCP一样-仅仅需要三次握手就完成连接,但有些应用程序需要交互很多信息后才能成功建立连接,比如Telnet协议,在TCP三次握手完成后,需要进行选项协商之后,Telnet连接才建立完成。

3 socket读超时

如果输入缓冲队列RecvQ中没有数据,read操作会一直阻塞而挂起线程,直到有新的数据到来或者有异常产生。调用setSoTimeout(int timeout)可以设置超时时间,如果到了超时时间仍没有数据,read会抛出一个SocketTimeoutException,程序需要捕获这个异常,但是当前的socket连接仍然是有效的。

如果对方进程崩溃、对方机器突然重启、网络断开,本端的read会一直阻塞下去,这时设置超时时间是非常重要的,否则调用read的线程会一直挂起。

TCP模块把接收到的数据放入RecvQ中,直到应用层调用输入流的read方法来读取。如果RecvQ队列被填满了,这时TCP会根据滑动窗口机制通知对方不要继续发送数据,本端停止接收从对端发送来的数据,直到接收者应用程序调用输入流的read方法后腾出了空间。

4 socket写超时

socket的写超时是基于TCP的超时重传。超时重传是TCP保证数据可靠性传输的一个重要机制,其原理是在发送一个数据报文后就开启一个计时器,在一定时间内如果没有得到发送报文的确认ACK,那么就重新发送报文。如果重新发送多次之后,仍没有确认报文,就发送一个复位报文RST,然后关闭TCP连接。首次数据报文发送与复位报文传输之间的时间差大约为9分钟,也就是说如果9分钟内没有得到确认报文,就关闭连接。但是这个值是根据不同的TCP协议栈实现而不同。

如果发送端调用write持续地写出数据,直到SendQ队列被填满。如果在SendQ队列已满时调用write方法,则write将被阻塞,直到SendQ有新的空闲空间为止,也就是说直到一些字节传输到了接收者套接字的RecvQ中。如果此时RecvQ队列也已经被填满,所有操作都将停止,直到接收端调用read方法将一些字节传输到应用程序。

当Socket的write发送数据时,如果网线断开、对端进程崩溃或者对端机器重启动,TCP模块会重传数据,最后超时而关闭连接。下次如再调用write会导致一个异常而退出。

Socket写超时是基于TCP协议栈的超时重传机制,一般不需要设置write的超时时间,也没有提供这种方法。

5 双重嵌套异常捕获

如果ServerSocket、Socket构造失败,只需要仅仅捕获这个构造失败异常而不需要调用套接字的close方法来释放资源(必须保证构造失败后不会留下任何需要清除的资源),因为这时套接字内部资源没有被成功分配。如果构造成功,必须进入一个try finally语句块里调用close释放套接字。请参照下面例子程序。

  1. import java.net.*;
  2. import java.io.*;
  3. public class SocketClientTest
  4. {
  5. public static final int PORT = 8088;
  6. public static void main( String[] args ) throws Exception
  7. {
  8. InetAddress addr = InetAddress.getByName( "127.0.0.1" );
  9. Socket socket = new Socket();
  10. try
  11. {
  12. socket.connect( new InetSocketAddress( addr, PORT ), 30000 );
  13. socket.setSendBufferSize(100);
  14. BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
  15. int i = 0;
  16. while( true )
  17. {
  18. System.out.println( "client sent --- hello *** " + i++ );
  19. out.write( "client sent --- hello *** " + i );
  20. out.flush();
  21. Thread.sleep( 1000 );
  22. }
  23. }
  24. finally
  25. {
  26. socket.close();
  27. }
  28. }
  29. }
  1. import java.io.*;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. public class SocketServerTest
  5. {
  6. public static final int PORT = 8088;
  7. public static final int BACKLOG = 2;
  8. public static void main( String[] args ) throws IOException
  9. {
  10. ServerSocket server = new ServerSocket( PORT, BACKLOG );
  11. System.out.println("started: " + server);
  12. try
  13. {
  14. Socket socket = server.accept();
  15. try
  16. {
  17. BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
  18. String info = null;
  19. while( ( info = in.readLine() ) != null )
  20. {
  21. System.out.println( info );
  22. }
  23. }
  24. finally
  25. {
  26. socket.close();
  27. }
  28. }
  29. finally
  30. {
  31. server.close();
  32. }
  33. }
  34. }

执行上面的程序,在程序运行一会儿之后,断开client和server之间的网络连接,在机器上输出如下:

Server上的输出:

Echoing:client sent -----hello0

Echoing:client sent -----hello1

Echoing:client sent -----hello2

Echoing:client sent -----hello3

Echoing:client sent -----hello4

Echoing:client sent -----hello5

Echoing:client sent -----hello6

---->> 断开了网络连接之后没有数据输出

Client上的输出:

socket default timeout = 0

socket = Socket[addr=/10.15.9.99,port=8088,localport=4691]

begin to read

client sent --- hello *** 0

client sent --- hello *** 1

client sent --- hello *** 2

client sent --- hello *** 3

client sent --- hello *** 4

client sent --- hello *** 5

client sent --- hello *** 6

client sent --- hello *** 7

client sent --- hello *** 8

client sent --- hello *** 9

client sent --- hello *** 10

---->> 断开网络连接后客户端进程挂起

java.net.SocketException : Connection reset by peer: socket write error

at java.net.SocketOutputStream.socketWrite0( Native Method )

at java.net.SocketOutputStream.socketWrite( SocketOutputStream.java:92 )

at java.net.SocketOutputStream.write( SocketOutputStream.java:136 )

at sun.nio.cs.StreamEncoder.writeBytes( StreamEncoder.java:202 )

at sun.nio.cs.StreamEncoder.implFlushBuffer( StreamEncoder.java:272 )

at sun.nio.cs.StreamEncoder.implFlush( StreamEncoder.java:276 )

at sun.nio.cs.StreamEncoder.flush( StreamEncoder.java:122 )

at java.io.OutputStreamWriter.flush( OutputStreamWriter.java:212 )

at java.io.BufferedWriter.flush( BufferedWriter.java:236 )

at com.xtera.view.SocketClientTest.main( SocketClientTest.java:99 )

当hello6被发送到server端后,网络连接被断开,这时server端不能接收任何数据而挂起。client端仍然继续发送数据,实际上hello7、hello8、hello9、hello10都被复制到SendQ队列中,write方法立即返回。当client的SendQ队列被填满之后,write方法就被阻塞。TCP模块在发送报文hello7之后,没有收到确认而超时重传,再重传几次之后关闭了TCP连接,同时导致被阻塞的write方法异常返回。

通过抓包工具,我们可以看到超时重传的报文。

 
 
http://blog.csdn.net/zdwzzu2006/article/details/7748006

JAVA Socket超时浅析(转)的更多相关文章

  1. JAVA Socket超时浅析

    JAVA Socket超时浅析 套接字或插座(socket)是一种软件形式的抽象,用于表达两台机器间一个连接的"终端".针对一个特定的连接,每台机器上都有一个"套接字&q ...

  2. [ 转载]JAVA Socket超时浅析

    JAVA Socket超时浅析 转载自 http://blog.csdn.net/sureyonder/article/details/5633647 套接字或插座(socket)是一种软件形 式的抽 ...

  3. Java Socket底层实现浅析

    最近在学Java的socket编程,发现Java可以很简单的通过socketAPI实现网络通信,但是我一直有个疑问,Java的socket的底层是怎么实现的? 如果没记错的话Java的底层是C和C++ ...

  4. java socket 网络编程常见异常

    1.java.net.SocketTimeoutException 这个异常比较常见,socket超时.一般有2个地方会抛出这个,一个是connect的时候,这个超时参数由connect(Socket ...

  5. Java Socket编程(转)

    Java Socket编程 对于Java Socket编程而言,有两个概念,一个是ServerSocket,一个是Socket.服务端和客户端之间通过Socket建立连接,之后它们就可以进行通信了.首 ...

  6. Java Socket网络编程常见异常(转)

    1.java.net.SocketTimeoutException 这个异常比较常见,socket超时.一般有2个地方会抛出这个,一个是connect的时候,这个超时参数由connect(Socket ...

  7. Java Socket 网络编程心跳设计概念

    Java Socket 网络编程心跳设计概念   1.一般是用来判断对方(设备,进程或其它网元)是否正常动行,一 般采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经当掉.用于 ...

  8. Java Socket发送与接收HTTP消息简单实现

    在上次Java Socket现实简单的HTTP服务我 们实现了简单的HTTP服务,它可以用来模拟HTTP服务,用它可以截获HTTP请求的原始码流,让我们很清楚的了解到我们向服务发的HTTP消息的结 构 ...

  9. Java Socket(2): 异常处理

    1 超时 套接字底层是基于TCP的,所以socket的超时和TCP超时是相同的.下面先讨论套接字读写缓冲区,接着讨论连接建立超时.读写超时以及JAVA套接字编程的嵌套异常捕获和一个超时例子程序的抓包示 ...

随机推荐

  1. Java程序猿学习当中各个阶段的建议

    回答阿里社招面试如何准备,顺便谈谈对于Java程序猿学习当中各个阶段的建议   引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的 ...

  2. Windows下visual studio code搭建golang开发环境

    Windows下visual studio code搭建golang开发环境 序幕 其实环境搭建没什么难的,但是遇到一些问题,主要是有些网站资源访问不了(如:golang.org),导致一些包无法安装 ...

  3. A Game of Thrones(8) - Bran

    The hunt left at dawn. The king wanted wild boar at the feast tonight. Prince Joffrey rode with his ...

  4. 收集经常使用的.net开源项目

    Json.NET http://json.codeplex.com/ Json.Net是一个读写Json效率比較高的.Net框架.Json.Net 使得在.Net环境下使用Json更加简单.通过Lin ...

  5. 从头来之【图解针对虚拟机iOS开发环境搭建】 (转)

    1.下载Mac OSX10.9. 点击下载 2.下载VMware Workstation 10,点击下载,网页中包含序列号.安装VM. 3.VM10-MacOS补丁.用于创建苹果虚拟机. 安装VM就不 ...

  6. Python眼睛护士改进版

    添加了设定从(0,0)显示:self.root.geometry('1000x200+0+0')其实主要是两个0.那个1000和200是没用的,因为已经设定了minsize. 添加了窗口置顶:self ...

  7. 单点更新线段树 RMQ

    D. Xenia and Bit Operations time limit per test 2 seconds memory limit per test 256 megabytes input ...

  8. 关于IE打印预览内容显示不全的问题解决

    眼下在调整一个页面打印功能的时候,发现多行文本框TextArea在页面显示的时候,多行文本能够正常显示,可是在打印页面的时候.部分内容就被遮挡住了, 苦思冥想不得其解,后来还是请教了美工. 首先查了下 ...

  9. Debian7.6安装过程中遇到的问题

    一 sudo命令不能用 1 使用su切换到root用户,命令: su 2 使用名:vim /etc/sudoers加入sudoer用户,命令: vim /etc/sudoers 找到root=(ALL ...

  10. wamp5中的apache不能启动,80端口被占用

    在wamp中apache中的httpd.conf文件中 端口文件设置为8080 #Listen 12.34.56.78:8080Listen 8080