不少同窗知道在大学课程中,咱们学习的《计算机网络》一书采用的是OSI七层网络模型(OSI Model),可是OSI 七层模型是一种抽象模型,在操做系统实际实现中,采用的是TCP/IP四层网络模型,四层模型将七层模型合并为了应用层(Application Layer)、传输(Transport Layer)、网络层(Internet Layer)、链路层(Link Layer),使得网络系统在具体实现中更加简化,OSI七层网络模型与TCP/IP四层网络模型以及协议对应关系以下表所示。在计算机系统中,分层是一种很重要的编程思想,分层思想将系统的功能与责任进行了层次化划分,基本上全部系统的架构设计,都是按照层次架构做为基本架构来设计的。在计算机网络中,相同层次具备相同的协议处理方式,下层协议上层提供服务,上层协议的行为控制着下层的工做状态,层层之间责任单一,目的明确。
linux
二 、Java对于TCP/IP协议的实现
程序员
在网络程序开发中,操做系统都为咱们提供了全面 方便的应用层网络操做类与接口,使得程序员在使用过程当中无需考虑协议栈的细节,而专心于数据的传输处理过程当中。固然,操做系统也为程序员提供了能够掌控协议细节的机会,例如使用原始套接字(Raw Socket)能够控制TCP的三次握手的细节实现TCP SYN扫描(注意部分 Windows 7系统不支持原始套接字的半开扫描)。可是在大多常规网络应用开发中,咱们都直接使用系统提供的应用层接口来实现网络程序。这里咱们罗列出在Java中常见的TCP/IP协议的实现类或方法,以下表所示:编程
三 、TCP协议为何须要三次握手windows
首先咱们来看一下TCP协议三次握手的具体过程(本图选自网络):
服务器
第一次握手:创建链接。客户端发送链接请求报文段,将SYN
位置为1,Sequence Number
为x;而后,客户端进入SYN_SEND
状态,等待服务器的确认;
网络
第二次握手:服务器收到SYN
报文段。服务器收到客户端的SYN
报文段,须要对这个SYN报文段进行确认,设置Acknowledgment Number
为x+1(Sequence Number
+1);同时,本身还要发送SYN
请求信息,将SYN位置为1,Sequence Number
为y;服务器端将上述全部信息放到一个报文段(即SYN+ACK
报文段)中,一并发送给客户端,此时服务器进入SYN_RECV
状态;架构
第三次握手:客户端收到服务器的SYN+ACK
报文段。而后将Acknowledgment Number
设置为y+1,向服务器发送ACK
报文段,这个报文段发送完毕之后,客户端和服务器端都进入ESTABLISHED
状态,完成TCP三次握手。并发
那么,为何TCP协议须要三次握手?在谢希仁的《计算机网络》中是这样说的:
tcp
已失效的链接请求报文段会产生在这样一种状况下:client发出的第一个链接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以至延误到链接释放之后的某个时间才到达server。原本这是一个早已失效的报文段。但server收到此失效的链接请求报文段后,就误认为是client再次发出的一个新的链接请求。因而就向client发出确认报文段,赞成创建链接。假设不采用“三次握手”,那么只要server发出确认,新的链接就创建了。因为如今client并无发出创建链接的请求,所以不会理睬server的确认,也不会向server发送数据。但server却觉得新的运输链接已经创建,并一直等待client发来数据。这样,server的不少资源就白白浪费掉了。采用“三次握手”的办法能够防止上述现象发生。例如刚才那种状况,client不会向server的确认发出确认。server因为收不到确认,就知道client并无要求创建链接。
函数
4、Java中TCP通讯的相关实现
在上节咱们分析了TCP协议创建链接,数据传输以及关闭链接的具体实现方式,而在实际的开发中,程序员只需了解TCP协议是一种可靠的传输协议便可实现数据在客户端与服务器之间稳定的传输。在Java中,提供了Socket与SocketServer类来实现TCP服务器与客户端的相关功能,一次正常的TCP通讯其大体流程能够分为四步(BIO模式):
服务器端(ServerSocket)绑定监听端口,等待客户端的TCP的链接(ServerSocket.accept())
客户端(Socket)经过IP地址与端口链接服务器监听端口(Socket.Connect()),链接成功后服务器端返回表示该TCP链接的Socket对象。
客户端与服务器经过打开Socket对象的InputStream和OutputStream数据流,实现数据的传输工做(Java将I/O相关的操做都提供了流操做接口,网络接口操做方式也同样)。
客户端与服务器完成数据传输,关闭数据流,关闭TCP链接(Socket.close())。
下图是Socket的通行模型图:
这里有个问题,TCP的三次握手是在Socket的哪一步中实现?
在ServerSocket.accept()与Socket.Connect()的过程当中实现的,在客户端经过Connect()接口链接服务器时,操做系统底层的TCP/IP协议栈便开始了发送SYN包,回复SYN+1等TCP的三次握手过程,只有三次握手成功,ServerSocket.accept()才会返回一个合法的Scoket对象,客户端Socket.Connect()函数会正常返回,若是三次握手失败,客户端Socket.Connect()会抛出IOException异常。
这里须要注意,具体的三次握手协议细节的实现,也不是在Java中实现的,Java只是运行在JVM虚拟机上的语言,具体的实现是由宿主主机的TCP/IP协议栈实现的,Java只是经过虚拟机调用了这些宿主主机提供的方法而已。
5、TCP半关闭现象(Half-Close
)
有TCP服务器与客户端应用程序开发经验的同窗应该遇到过一个问题,那就是服务器端忽然崩溃(kill 掉服务器进程),查看系统中的网络链接时,发现TCP客户端的状态仍是处于链接状态(ESTABLISHED
),而该TCP链接实际已经失效了,这就是TCP的Half-Close 现象。若是应用程序判断与服务器链接状态的方法依赖于TCP的链接状态,客户端将会一直认为与服务器的TCP链接是正常的,只有在客户端向服务器发送数据时,才能发现TCP链接对应的套接字失效了。之因此产生Half-Close现象就是因为客户端与服务器之间没有经过四次挥手的方式关闭TCP链接,在服务器的忽然下线会形成客户端没法当即感知的问题。有同窗会问,不是有超时时间吗,一旦超时,客户端不就能够感知到服务器已经下线了吗?不错,系统的协议栈实现具备超时时间的机制,可是在windows系统下,这个超时时间默认是2小时(做者未考证linux下的keepAlive时间)。
那么如何避免Half-Close现象?
1.首先进行再使用完tcp链接后,必定要将套接字close掉。
2.添加心跳包机制,服务器与客户端之间的链接保持机制不该该依赖套接字的状态,而应该在TCP协议之上设计心跳包机制,例如每5分钟,客户端与服务器之间经过发送心跳包来感知对方的存在。
3.TCP Server应该实现JVM的关闭钩子(Runtime.addShutdownHook()),主动关闭全部TCP链接,清理占用资源,JVM关闭钩子的使用方式以下所:
总结
TCP协议做为可靠传输协议,是全部协议中最经常使用的协议,什么?最经常使用的协议不是HTTP吗?HTTP协议只是TCP协议的应用协议而已。TCP协议相关的开发难点在于服务器端的开发,须要考虑并发性能,本文以讲解了TCP的协议为主,所以只采用了BIO模式进行分析,在以后的文章中将会分析高并发的TCP服务器的实现原理。