socket通信模型、socket中的accept()阻塞与read()阻塞

Socket整体流程

    Socket编程主要涉及到客户端和服务端两个方面,首先是在服务器端创建一个服务器套接字(ServerSocket),并把它附加到一个端口上,服务器从这个端口监听连接。端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。

  客户端请求与服务器进行连接的时候,根据服务器的域名或者IP地址,加上端口号,打开一个套接字。当服务器接受连接后,服务器和客户端之间的通信就像输入输出流一样进行操作。

  如果一个程序创建了一个socket,并让其监听80端口,其实是向TCP/IP协议栈声明了其对80端口的占有。以后,所有目标是80端口的TCP数据包都会转发给该程序(这里的程序,因为使用的是Socket编程接口,所以首先由Socket层来处理)。所谓accept函数,其实抽象的是TCP的连接建立过程。  accept函数返回的新socket其实指代的是本次创建的连接,而一个连接是包括两部分信息的,一个是源IP和源端口,另一个是宿IP和宿端口。所以,accept可以产生多个不同的socket,而这些socket里包含的宿IP和宿端口是不变的,变化的只是源IP和源端口。这样的话,这些socket宿端口就可以都是80,而Socket层还是能根据源/宿对来准确地分辨出IP包和socket的归属关系,从而完成对TCP/IP协议的操作封装。TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。就像操作系统会提供标准的编程接口,比如  Win32编程接口一样,TCP/IP也必须对外提供编程接口,这就是Socket编程接口。

客户端上的使用

  getInputStream()方法可以得到一个输入流,客户端的Socket对象上的getInputStream方法得到输入流其实就是从服务器端发回的数据。
  getOutputStream()方法得到的是一个输出流,客户端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给服务器端的数据。

服务器端上的使用

  getInputStream()方法得到的是一个输入流,服务端的Socket对象上的getInputStream方法得到的输入流其实就是从客户端发送给服务器端的数据流。

  getOutputStream()方法得到的是一个输出流,服务端的Socket对象上的getOutputStream方法得到的输出流其实就是发送给客户端的数据。

ServerSocket类的accept()阻塞

ServerSocket的accept()方法是侦听并接受到此套接字的连接,就是一直等待连接,此方法在连接传入之前一直阻塞(即后面的代码不会往下执行)。直到接受到有socket的连接,然后创建并返回新的Socket对象。

read()阻塞

从socket上读取对端发过来的数据一般有两种方法: 

1)按照字节流读取

2)按照字符流读取

这段代码执行以后会发现read()方法发生了阻塞,经过查找资料发现:

read() 是一个阻塞函数,如果客户端没有声明断开outputStream那么它就会认为客户端仍旧可能发送数据,所以就会一直阻塞而不是返回-1,所以System.out.println("服务器");这行代码在连接断开之前就一直不会执行,因为在while ((len = is.read(buf)) != -1) 这里阻塞了。

像read()这种阻塞读取函数还有BufferedReader类种的 readLine()、DataInputStream种的readUTF()等。


这个特性使得编程非常方便也很高效。 
但是这样也有一个问题,就是如何让程序从这两个方法的阻塞调用中返回。

 总结一下,有这么几个方法:

 1、发送完后调用Socket的shutdownOutput()方法关闭输出流,这样对端的输入流上的read操作就会返回-1。  注意不能调用socket.getInputStream().close()。这样会导致socket被关闭。 

       当然如果不需要继续在socket上进行读操作,也可以直接关闭socket。  但是这个方法不能用于通信双方需要多次交互的情况。

1 os.write("sender say hello socket".getBytes());
2 os.flush();
3 client.shutdownOutput();  //调用shutdown 通知对端请求完毕

       这个解决方案缺点非常明显,socket任意一端都依赖于对方调用shutdownOutput()来完成read返回 -1,如果任意一方没有执行shutdown函数那么就会出现问题。所以一般我们都会在socket请求时设置连接的超时时间 socket.setSoTimeout(5000);以防止长时间没有响应造成系统瘫痪。

1 while (true) {
2      server = serverSocket.accept();
3      System.out.println("server socket is start……");
4      server.setSoTimeout(5000);
5      .....
6  }

参考文章:

https://blog.csdn.net/anthony_ju/article/details/82192135

https://www.cnblogs.com/swordfall/p/10781281.html

https://www.cnblogs.com/gaoqiri/p/10055610.html

https://blog.csdn.net/yanchuang1/article/details/48049259

原文地址:https://www.cnblogs.com/FengZeng666/p/12488827.html