TCP通信分为两步
第一步:建立连接来提高通信的可靠性。建立连接时,服务器和客户的TCP协议会自动记录下对方ip和端口。
当然,能够建立连接的前提是,服务器端绑定了固定的ip和端口,而且客户端知道这个固定的ip和端口,只有这样客户端才知道向谁请求连接,否则都不知道和谁连接,更不要谈连接成功了。
第二步:使用通信描述符直接收发数据,此时会自动使用TCP记录的IP和端口。
为了弄清楚TCP通信的具体过程,我们分三种情况来看。
TCP通信的具体过程
本机通信
图解分析:
客户端端程序和服务器端程序都运行在同一台主机上,进行本机通信。
服务器端调用bind函数绑定固定的ip和固定的端口,客户端的端口是自动分配的,客户端端口是变化的,每一次连接的时候客户端的端口号都可能是不一样的。由于是本机通信,所以本机ip是一样的。
当然,ip也有可能不一样,因为会出现一台计算机有多个ip的情况,但是一般情况下,同一台主机的只有一个ip。
由于是本机通信,所以客户端程序和服务器端程序公用同一个TCP/IP协议栈。
客户端调用connect函数来主动发起连接请求。
客户端调用connect函数的时候必须给定connect函数服务器固定的端口和固定的ip。
客户端调用connect函数之后,connect函数会将包含服务器端的ip和端口的应用层数据包发送给TCP传输层, (这里的TCP传输层可以理解为客户端的TCP传输层)TCP传输层就封装传输层包头然后把数据包发送给IP网络层,IP网络层继续封包。
IP网络层网络层通过检查发现源ip等于目标ip就知道进行的是本机通信,那么到达IP网络层之后就会直接返回,不会通过网络层继续向下发送数据,将数据包回发给TCP 传输层(这里的TCP传输层可以理解为服务器的TCP传输层)。
客户端调用connect函数的目的为了完成三次握手。
第一次TCP传输层把数据发送给IP网络层,IP网络层又把数据回发给TCP传输层,此时服务器端的accept函数并不会立即响应,因为请求连接的时候有三次握手,第一次发送握手收据需要由客户端connect函数启动,三次握手是由TCP通信协议自动完成的。三次握手成功之后,表示客户端和服务器连接成功。连接成功之后accept函数才会有响应。在三次握手没有完成之前,服务器端的accept函数是不会有响应的。
三次握手成功之后,服务器端会自动记录客户端和ip和端口,客户端也会自动记录服务器端的ip和端口。
如果三次握手失败,服务器端的accpet函数不会有任何响应。客户端的connect函数会错误返回并且提示客户端连接失败,客户端就需要重新连接。
连接成功,服务器端accept函数返回通信文件描述符用于与客户端进行通信,同时客户端connetc函数调用成功之后继续向下执行,然后客户端和服务器端开始互相收发数据进行通信。
局域网内跨机通信
图解说明:
客户端计算机和服务器端计算机在同一个局域网里面,也就是主机ip在同一个网段。
服务器端调用bind函数绑定固定的ip和端口,客户端的ip和端口都是自动设置的。
同样也是客户端与服务器端先建立连接,然后进行通信。
首先客户端调用connect 函数主动发起连接请求,发起连接请求之后, connect函数会将包含服务器端的ip和端口的应用层数据包发送给客户端TCP传输层,客户端TCP传输层继续封装传输层包头然后把数据包发送给客户端IP网络层,客户端IP网络层继续封包。
客户端IP网络层通过检查发现源ip不等于目标ip就知道进行的不是本机通信,然后把客户端IP网络层封装的数据包继续发送给链路层,然后发送给交换机,交换机再发送给服务器端的网卡,(关于如何封装各层数据包,如果通过客户端网卡把数据发送给交换机,然后交换机把数据发送给服务器我们在计算机网络系列博客中已经详细进行说明。)数据到达服务器端TCP传输层之后第一次握手成功,第一次握手成功之后服务器端TCP通过提取客户端的ip和端口给客户端发送回答数据,然后继续完成三次握手。
三次握手由TCP通信自动完成。
三次握手成功之后,客户端会自动记录服务器端的ip和端口,服务器端也会自动记录客户端的ip和端口。
三次握手成功之后,服务器端accept返回一个通信文件描述符进行通信,客户端connect 函数调用成功之后使用套接字文件描述符进行通信,程序继续运行,客户端和服务器端开始进行通信。
三次握手的过程中,accept函数不会响应,只有当三次握手成功之后accept函数才会响应并且返回通信文件描述符。
如果三次握手失败了,服务器端accept函数不会响应,客户端connect函数在三次握手失败之后错误返回。
连接成功之后,客户端和服务器端开始收发数据,在收发数据通信的过程中,对方在收到数据之后并不会立即把数据向上层传递,而是先回答对方,如果回答说数据是错的,那么对方就会重新发送数据,之后回答收到的数据是正确的,才会继续向上层提交数据。回答的过程由TCP通信自动完成。
数据过路由器,跨网通信
图解说明:
同样的客户端调用connect 函数发起连接请求,connect 函数封装服务器的ip和端口。
这里需要注意;connect 函数封装的服务器端的ip一定是服务器所在路由器的公网ip,因为跨网通信的过程当中涉及到net转换,所以一定会使用公网ip。
Connect 函数把应用层数据包交给客户端的TCP传输层,客户端的TCP传输层再封装之后交给客户端的IP网络层,客户端的IP网络层再封装之后继续向下交给链路层,链路层发送给网卡,网卡交给交换机,交换机交给路由器,然后进行net转换,数据从内网发送到外网的时候,把源ip转化为公网ip。
数据经过路由器之后,源ip就从私网ip转换为公网ip,然后把数据发送给广域网交换机,广域网交换机把数据发送给和客户端路由器的公网ip在同一个网段的路由器。
服务器只是路由器下面的某一台计算机,所以要找到服务器所在的计算机就要再次进行net转换,把数据的目标ip从公网ip转换为服务器的私网ip。然后通过服务器端所在的交换机把数据发送到服务器端的网卡,然后向上知道发送到服务器端的TCP传输层,第一次握手成功。
然后服务器端的TCP传输层开始回答,第一次发送给服务器端的数据里面包含有客户端的ip和端口,所以就能够进行回答。只不过数据包里面的ip是客户端所在路由器的公网ip。所以服务器端在进行第二次握手回答的时候,通过客户端所在路由器的公网ip进行回答。
数据一路返回,在服务器端所在的路由器时,通过net转换将服务器端的私网ip转化为公网ip,在数据到达客户端所在的路由器的时候,继续进行net转换。将目标ip从公网ip转换为客户端的私网ip。然后将数据一路向上发送到客户端的TCP传输层完成第二次握手。同样的原理客户端在进行回单继续完成第三次握手。
三次握手成功之后连接成功,客户端自动记录服务器端的端口号和服务器端所在路由器的公网ip,服务器端也会自动记录客户端的端口号和客户端所在路由器的公网ip。
三次握手连接成功之后开始互相收发数据,互相发送数据时把原始数据交给TCP传输层,然后传输层把自动记录的ip和端口进行封包,然后发送给IP网络层进行封包,然后向下发发送直到发送到对方的TCP传输层,对方的TCP传输层在接收到数据之后并不会直接向上发送,而是进行回答,只有回答当数据是正确的之后,才会将数据继续向上提交。如果回答数据是错误的,那么对方就需要重新发送数据。
TCP服务器/客户模式
对于TCP协议来说,只要你使用TCP协议,必然有一个服务器端,而其它的都是客户端,这一点是由TCP协议本身的特性所决定的,而不是“应用程序”来决定的,所以使用TCP协议通信时,是天然的服务器/客户模式。
也就是一个服务器端为多个客户端服务。
如果TCP客户之间想要通信,TCP客户之间无法直接通信。
在TCP的服务器/客户模式下,只能通过服务器来中转。
服务器为了同时与众多客户进行通信,可以有三种方式:
(1)多线程
(2)多进程
(3)多路IO
这三种方式的实现,我们在后面博客中会详细说明。