基于TCP流协议的数据包通讯 - 全文
一、TCP定义
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。
二、TCP可靠性实现
TCP提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据包之前必须先建立一个TCP连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。在一个TCP连接中,仅有两方进行彼此通信。广播和多播不能用于TCP。
1.应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度将保持不变。由TCP传递给I P的信息单位称为报文段或段
2.当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
3.当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒。
4.TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。
5.既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
6.既然I P数据报会发生重复,TCP的接收端必须丢弃重复的数据。
7.TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
三、TCP连接的建立
利用TCP传输数据前,需要建立tcp连接,tcp连接的建立有3个主要过程,叫做3次握手,具体过程如下图所示:
过程:
1. 首先客户端发送一个SYN包给服务器(SYN=1,Seq为主机选择的这个连接的初始序号),然后等待应答。
2. 服务器端收到SYN包,回应给客户端一个ACK =x+1、SYN=1的TCP数据段(ACK表示确认序号有效,即收到上一个包,这里加1并不是ACK的值加1,ACK是一个标志位,这里会变成1,而x+1则是希望收到的下一个包的序列号,这个值放在包的确认序列号字段中,而只有ACK=1时,确认序列号才有效)。
3. 客户必须再次回应服务器端一个ACK确认数据段(这里的Seq为x+1)。
经过上面3个过程就建立了一个tcp连接,接着就可以发送数据了,因为建立连接使用了一个序列号x,所以发送数据的第一个字节序号为x+1。
注意:这里tcp为应用层提供全双工服务,意味数据能在两个方向上独立地进行传输,因此连接的每一段都有各自的传输数据序号(对应于上图中的x和y,这两个值是没有必然联系的)。
四、TCP协议流的理解
TCP是流协议,不像UDP那样sendto发一次消息,另一端必然会收到完整消息,或者没有收到任何消息。当用TCP send发一次消息的时候,可能另一端在某时刻可能只收到一部分消息,下一时刻才能收到另一部分。那如果一个消息很小,是否可以保证另一端在某时刻能收到这条完整消息?
调用send后,TCP将数据拷贝到缓冲区。缓冲区内可能不止一条用户消息。TCP按照一定算法,将缓冲区的数据打包到1-n个TCP报文中,交给IP层发送。TCP报文是TCP协议的最小发送单位,大小应该是可变的,并且丢失的话会重发。并不能保证一个TCP报文中必然包含一条用户消息的全部,所以即使消息很小,另一端也有可能在某时刻只收到部分
IP层将TCP报文装进IP包,然后再交给链路层发送以太帧
理论上IP包的大小应该会选择比MTU小。一旦IP包比MTU大,意味着网络上的路由要帮你缓存多个以太帧,拼出IP包后才知道如何路由到下一个节点。向下一节点路由的时候还要再拆分成多个以太帧发送。所以TCP报文应该会比选择比MTU小。
五、基于TCP流协议的数据包通讯
TCP通讯是流协议,它不像UDP那样基于包为边界的通讯方式,TCP流式协议,举个简单例子,一端用send 分别发送 100,123,120字节的数据,另一端用recv可以一下子接收到 100+123+120=343字节的数据,或者先接收 3个字节的数据,再接收余下的340字节,不管另一端怎么接收,最终是要接收到343字节的数据。而且TCP保证数据的完整性和顺序,也就是两端是数据同步的,出现任何一点的数据不一致,都会造成TCP连接的失效。
UDP则跟TCP大不一样,他是基于包边界的。所谓的包边界,就是一端分别发送 100, 123, 120字节的数据,另一端接收到也应该分别是 100,123,120字节数据的三个包,不会出现一端发送100字节的一个数据包,另一端只接收到小于100字节的数据包,或者收到大于100字节的数据包。UDP同时也不保证稳定和顺序,如发送端发送100,123,120三个包,接收端可能接收到3个包,也可能只接收到2个包,也可能一个包也收不到,收到的顺序不一定是100,123,120,可能是100,120,123,或者123,100,120等。这些TCP和UDP的属性,大家稍微查查资料就该很清楚。
UDP的这种特殊通讯方式其实跟网络底层链路层的通讯方式很接近。链路层的数据是一个数据包一个数据包的传输,并不保证数据能否达到对方,或者按照顺序到达对方。UDP只是简单把链路层和IP层的数据加了一层封装,加了端口用于识别同一个机器的不同进程,UDP数据包的收发方式,只是组合成UDP包之后,简单的发送到底层网络了事,至于底层网卡有没有发成功或者接收成功,它是一概不闻不问的。他的底层处理方式比起 TCP协议来说简单太多了。
既然UDP这么好,编程又简单,可现在网络中大部分都在使用TCP,一个非常重要的原因就是TCP提供的是可靠传输,TCP有一套复杂的底层算法来保证数据的完整和可靠,有这个理由就已经足够让TCP在大部分场所比UDP好使了。因为大部分时候,我们在开发网络通信程序,都希望能随意的接收和发送任意大小和完整的数据,如果使用UDP,还得自己写算法来保证数据的顺序和完整,整个处理过程就等于实现一个小型的TCP协议。
一些特殊场所,比如P2P,各种使用P2P的下载软件如迅雷等,这些软件和传统的服务端客户端模式不大一样,每个运行软件的机器既是客户端也是服务端,而用户的每个机器可能处于不同的网络环境中,最典型的就是大部分机器处于NAT中,这样的环境下,采用UDP是最佳选择,因为TCP的NAT穿透能力差。
当然这些软件使用UDP,他们也必须实现一套算法来保证UDP传输的完整和顺序。我们在开发TCP程序时候,最先想到的就是 请求-应答模式:就是客户端发起一个请求,然后服务端接收到请求,进行处理,接着向客户端应答这个请求。最典型和常用的就是 HTTP协议,我们浏览的所有网页,以及各种玲琅满目的网站,这些都是HTTP的功劳,HTTP协议是建立在TCP上的应用层协议,采用就是 请求-应答方式。
浏览器首先发起一个网页请求的TCP连接,web服务器通过这个TCP连接应答这个网页,并把网页内容传输给浏览器。然后浏览器可能关闭这个TCP连接,或者也可能利用这个TCP连接发起另外一个网页请求。这个请求-应答模式,也是我在使用TCP开发私有协议时候,使用的最多的模式,多得来以至于都忘记其他模式需求的存在了。
windows远程桌面:
使用远程桌面可以远程控制另一台windows机器,可以在远程桌面里做任何本地桌面上的操作,比如删除,复制文件,可以把本地文件复制到远程机器里,在复制的同时还能执行其他操作,远程机器的桌面变化实时更新到本地,等等。
但是仔细研究会发现,远程桌面只使用了一条 TCP连接,连接到被控制机器的 3389 端口。也就是在一条TCP通讯连接里,传输各种请求数据和接收各种应答数据。远程桌面使用的是 RDP协议,我们这里不讨论RDP的细节,只讨论如何在一条TCP连接中,如何做到远程桌面的各种操作。如果我们还是按照请求-应答的模式来解释远程桌面的通讯协议,显然会有很多无法处理的问题。
比如举个简单例子:
我们在远程桌面客户端点击鼠标操作,这个操作会通过3389的TCP连接发送到被控制端,如果按照请求-应答模式来工作,则必须在被控制端接收到这个鼠标操作,执行这个动作,然后回答给客户端已经执行了这个操作。如果这期间,被控制机器的桌面界面内容发生变化,则无法通知给客户端,因为一切通讯都是按照客户端发起请求,然后服务端应答的方式通讯的。即使我们使用请求-应答的方式,通过轮询定时查询被控制机器的界面内容变化情况,也无法做到实时,而且轮询慢了会严重影响视觉效果,轮询快了会严重浪费资源。
于是,我们改换一种解决问题的办法,从 UDP 通讯的特点:(按照包模式通讯)入手去解决上边的问题。假定我们在远程桌面的TCP通讯中,一切通讯的数据都定义成一个一个的单独的数据包在同一条TCP连接中传输,数据包的接收和发送分开进行,就是在同一个TCP连接中,一个线程专门接收数据包,一个线程专门发送数据包。这是可以的,因为现在的网卡都是工作在全双工状态下。所谓全双工,就是接收和发送使用各自的通道,能独立进行数据传输。
大致伪代码如下:
int tcp_socket = 客户端连接到服务端的socket或者服务端接收到客户端连接的socket。
receive_thread() //负责接收数据包的线程
{
tcp_packet = recv_packet (tcp_socket );
////TCP 是流协议,因此,我们必须至少定义一个表示包大小的头+包内容,才能保证TCP数据传输的同步。
//处理 tcp_packet 包,为了不阻塞读线程,一般是把tcp_packet交给别的线程处理。
}
send_thread()//负责发送数据包的线程
{
while(loop){
从发送队列取出一个包 tcp_packet,(发送队列,是别的线程生成的需要发送的数据包。)
send_packet( tcp_socket, tcp_packet ); //发送这个数据包。
}
}
再回到上边的问题,
远程桌面控制端(客户端)和被控制端(服务端),分别开启两个线程,一个负责接收数据包,一个负责发送数据包。当我们在远程桌面客户端点击鼠标等操作,生成一个鼠标的数据包投递到发送线程,发送线程再把它传输到被控制端,被控制端接收到这个数据包,然后执行,他如果要回复这个鼠标的执行结果,则再生成一个结果包投递到发送线程,发送线程再把这个包传输给客户端。
同时如果被控制端的界面发生改变,则生成一个界面内容改变的数据包,投递到发送线程,发送线程再传输给客户端。客户端的接收线程接收到界面内容改变的数据包,显示新的被控制端的界面内容。客户端接收到鼠标执行结果的包,知道鼠标操作是失败还是成功。
按照包的方式通讯,就能在远程桌面中传递各种复杂的动作,每个动作都封装成一个一个的数据包进行传输。接收包和发送包分开独立进行,互相不干扰,每个包是否需要应答包,根据每个包的需求决定,不是必须的。这又回到了 UDP通讯方式。那为何不干脆使用UDP代替呢? 还是上边提到的原因,TCP保证稳定和顺序,这点在远程桌面等这类要求数据必须准确的地方,是十分必要的。
TCP如何保证传输的是单独数据包?
每个数据包定义成 ”包大小+包内容“,比如4个字节表示包的大小,然后是包数据。
发送的时候,“包大小+包内容”组合到一起发送,接收的时候,先接收固定的4个字节,获取到包的size,
然后再接收size字节的数据,这样一个包就接收完成。大致伪代码如下:
send_packet(tcp_socket, packet, packet_size) //发送数据包
{
int32 size = pakcet_size; ///应该采用网络序
send(tcp_socket, &size, 4.。);
send(tcp_socket, packet, packet_size);
}
recv_packet(tcp_socket)
{
int32 size;
recv(tcp_socket, &size, 4,。。.);
char* packet = malloc(size);
recv( tcp_socket, packet, size, 。。.);
return packet;
}
- 第 1 页:基于TCP流协议的数据包通讯
- 第 2 页:windows远程桌面
本文导航
非常好我支持^.^
(1) 100%
不好我反对
(0) 0%
相关阅读:
- [电子说] 中兴通讯助力高质量共建“一带一路” 2023-10-24
- [电子说] 无线模拟信号采集器0-10v0-5v 4-20mA数据wifi通讯 2023-10-24
- [智能电网] 安科瑞AISD系列智能安全配电装置案例 2023-10-24
- [电子说] 雷迪埃 OCTIS 在通讯行业Open Ran中的应用 2023-10-23
- [电子说] 3线串行数据通讯EEPROM的使用 2023-10-23
- [电子说] 近场通讯结构解析 2023-10-23
- [电子说] 多种PLC之间跨网段通讯的解决方案 2023-10-23
- [电子说] 禧玛诺领先600Pro仪表及其整车电气通讯方案的拆解分析 2023-10-23
( 发表人:姚远香 )