java socket编程

来源:互联网 发布:ipad客户端办公软件 编辑:程序博客网 时间:2024/06/10 15:42

转自:http://blog.csdn.net/xyz_lmn/article/details/6134687


套接字或插座(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释放套接字。请参照下面例子程序。


 

view plaincopy to clipboardprint?
  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.         
  15.       BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );  
  16.       int i = 0;  
  17.         
  18.       whiletrue )  
  19.       {  
  20.         System.out.println( "client sent --- hello *** " + i++ );  
  21.         out.write( "client sent --- hello *** " + i );  
  22.         out.flush();  
  23.           
  24.         Thread.sleep( 1000 );  
  25.       }  
  26.     }  
  27.     finally  
  28.     {  
  29.       socket.close();  
  30.     }  
  31.   }  
  32. }  

 

 

view plaincopy to clipboardprint?
  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.           
  20.         while( ( info = in.readLine() ) != null )  
  21.         {  
  22.           System.out.println( info );  
  23.         }  
  24.       }  
  25.       finally  
  26.       {  
  27.         socket.close();  
  28.       }  
  29.     }  
  30.     finally  
  31.     {  
  32.       server.close();  
  33.     }  
  34.   }  
  35. }  

 

  执行上面的程序,在程序运行一会儿之后,断开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方法异常返回。

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




问题:

最近在开发中遇到一个问题,就是如何判断远端服务器是否已经断开连接,如果断开那么需要重新连接。

首先想到socket类的方法isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()等,但经过试验并查看相关文档,这些方法都是本地端的状态,无法判断远端是否已经断开连接。

然后想到是否可以通过OutputStream发送一段测试数据,如果发送失败就表示远端已经断开连接,类似ping,但是这样会影响到正常的输出数据,远端无法把正常数据和测试数据分开。

最后又回到socket类,发现有一个方法sendUrgentData,查看文档后得知它会往输出流发送一个字节的数据,只要对方Socket的SO_OOBINLINE属性没有打开,就会自动舍弃这个字节,而SO_OOBINLINE属性默认情况下就是关闭的,太好了,正是我需要的!

于是,下面一段代码就可以判断远端是否断开了连接:

try{
      socket.sendUrgentData(0xFF);
}catch(Exception ex){
      reconnect();
}



0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 雅乐之舞掉叶子怎么办 姬珊瑚发软歪了怎么办 胃胀气怎么办简单的方法 8个月的宝宝咳嗽怎么办 坐完月子掉头发怎么办 在香港买到假货怎么办 用气垫脸上浮粉怎么办 不够奶给宝宝吃怎么办 叶插发芽发根后怎么办 多肉植物摊大饼怎么办 英短蓝猫掉毛怎么办 英短蓝猫很凶怎么办 面部打伤怎么办了肿了 朋友欠钱一直拖怎么办 旧车三年不年检怎么办 遇到领导整你该怎么办 皮肤热了就瘙痒怎么办 猫爪子肉垫脏了怎么办 嘴周围干燥起皮怎么办 下嘴唇总是起皮怎么办 上嘴唇老是起皮怎么办 涂口红嘴唇起皮怎么办 上嘴唇干裂起皮怎么办 9岁儿童嘴唇干裂怎么办 上嘴唇干裂烂了怎么办 孕妇脚干的裂开怎么办 小腿皮肤干燥起皮怎么办 秋天脸干燥起皮怎么办 脚上干燥起皮怎么办 脚皮肤干燥起皮怎么办 脚有脚气脱皮又痒怎么办 脚特别干还脱皮怎么办 皮肤干燥起皮怎么办痒 脚一直脱皮怎么办不痒 我的脚老是起皮怎么办? 脚裂了怎么办 小妙招 牛不吃草不倒嚼怎么办 婴儿吸奶乳头痛怎么办 公犬的生殖肿了怎么办 大阴唇一边肿了怎么办 小阴唇比大阴唇大怎么办