这篇文章主要介绍TCP/IP的一些基本知识,后面几篇继续深入一点探究。
本篇主要包括下面这些知识:
TCP/IP是什么
socket介绍
socket通信流程
socket中TCP三次握手建立连接
socket中TCP的四次挥手释放连接
1.TCP/IP是什么
首先看一个引出TCP/IP协议族的问题,网络之间的进程如何进行通信?
在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。TCP/IP协议族帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
上面的例子告诉我们TCP/IP是用来干什么的,即它是用来让网络之间的进程通信时使用的。那么什么是TCP/IP?TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。它定义了主机如何连入因特网及数据如何在它们之间传输的标准。
从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
运输层:TCP,UDP
网络层:IP,ICMP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的。
图1
IOS参考模型和TCP/IP模型的对比:
图2
看完上面的图表,对TCP/IP协议族应该有了一个大概的了解,它是指涉及到通讯过程中的一系列的协议,包括网络接口处,网络层,传输层的协议。为什么要将网络通讯划分成这么多层次呢?因为它比较复杂,如果只用一个层来实现整个通讯流程,那么这个层次会非常复杂,既不利于维护,也不利于扩展。从软件工程的角度来考虑这个问题就很好理解了。不得不说最开始设计TCP/IP的人真是天才啊,知道要分层很容易想到,但是如何分层又是一个大问题,最早的那批先辈们帮我们解决了这个问题。我们现在只需要学习各个分层的作用就可以了。
2.socket介绍
看完上面的图1中TCP/IP的介绍,但是都没有socket的影子,那么它在哪儿呢?
图3
首先必须明确socket不是某一层的协议,它是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组编程接口(即API),在设计模式中,socket就是门面模式(又称为外观模式,Facade),它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
3.socket通信流程
socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的
上面是文字描述,对应于socket api中的函数如下图:
也就是说客户端和服务端的流程是这样子的:
客户端的流程如下:
创建套接字(socket)
向服务器发出连接请求(connect)
和服务器端进行通信(send/recv)
关闭套接字
服务器端的流程如下:
创建套接字(socket)
将套接字绑定到一个本地地址和端口上(bind)
将套接字设为监听模式,准备接收客户端请求(listen)
等待客户请求到来;当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept)
用返回的套接字和客户端进行通信(send/recv)
返回,等待另一个客户请求。
关闭套接字。
上面就是编写socket程序时客户端和服务器端的基本步骤,每一个socket程序的基本步骤都是上面那几步。
4.socket中TCP三次握手建立连接
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接。
第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
服务器socket与客户端socket建立连接的部分其实就是大名鼎鼎的三次握手。
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
故客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
5.socket中TCP的四次挥手释放连接
上面介绍了socket中TCP的三次握手建立过程,及其涉及的socket函数。现在我们介绍socket中的四次握手释放连接的过程,请看下图:
图示过程如下:
某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK。
————————————————
下面推荐两个比较好的Socket详解:https://blog.csdn.net/tian779278804/article/details/50922354
https://blog.csdn.net/msdnwolaile/article/details/50735326
socket编程时,server端口可以通过构造函数指定,ServerSocketserver=newServerSocket(20006)那么当回传client时,相应的通信端口是多少?
client 请求到服务器时,服务器会通过accept方法获取到客户端的socket,
Socket client = server.accept(); // 开启监听
所以不需要知道client的端口号,就能通信
追问
谢谢提醒。我发现调用 server.accept().getPort() 方法可以查看客户端socket端口。 但是我又糊涂了,这个Socket client与客户端中的 Socket client = new Socket("127.0.0.1",20001);有什么区别?
追答
1. 服务器或客户端,双方开启socket都各需要一个端口的,这是因为,每台机器都要一个端口,才能和外界进行交互。比如这里,服务器开启的是20006,客户端开启的是20001. 2. 客户端需要知道服务器的IP和端口(20006),才能连接到服务器 3. 服务器accept到客户socket请求时,可获取客户端的socket信息,但只是用于辅助。 通过socket句柄已经可以进行通信了,不用再创建新的socket去访问client。 4. 至于你说的 Socket client对象有什么不同 Socket client = new Socket("127.0.0.1",20001); // 客户端创建socket Socket client = server.accept(); // 服务端接收到客户端的socket 可以理解为在不同机器上,用两个相同名字(client)的不同变量,描述了同一个数据链路socke