目录
1、TCP编程流程
2、粘包
1、TCP编程流程
socket() 是创建套接字,返回值为监听套接字描述符,有了套接字才能通过网络进行数据的传输。创建套接字的参数要指定服务类型,TCP协议使用的是流式服务(SOCK_STREAM)。
bind() 是指定套接字使用的IP和端口。IP地址 是自己主机的地址。端口 是一个16位整型值, 一般0~1024为知名端口,一般用户不能随便使用;1024~4096为保留端口,一般不使用;4096以上为临时端口,可以使用。在Linux系统上,1024以内的端口号只有root用户可以使用。
listen() 是创建监听队列。监听队列有两种,一个是存放未完成三次握手的连接,一种是存放已完成三次握手的连接。该函数的第二个参数在Linux系统上是指已完成三次握手队列的长度,在Unix系统上是未完成和已完成三次握手队列总和的大小。
accept() 是处理放在 listen() 创建的已完成三次握手队列中的连接。每处理一个连接,accept()返回一个该连接对应的连接套接字描述符,如果已完成三次握手队列为空,则accept阻塞。
connect() 一般由客户端程序执行,需要指定连接的服务端的IP和端口。该方法执行后,会进行三次握手,建立连接
三次握手(建立连接)
send() 是用来向TCP连接的对端发送数据。send()执行成功,说明成功将数据写入发送端的发送缓冲区中,并不能说明已经发到对端。返回值为实际写入发送缓冲区的数据长度。
recv() 是用来接收TCP连接的对端发送的数据。recv()从自己的接收缓冲区中读取数据,如果接收缓冲区中没有数据,就会阻塞。返回值为实际读取的字节数。
recv()返回值为0是对端关闭TCP连接的唯一标识。
close() 可用来关闭文件的,该处用于关闭套接字和TCP连接。
关闭TCP链接:自己端执行该方法后,在对端未执行close()时,进行两次挥手,此时处于半连接状态;当对端也执行close()后,再进行后两次挥手,最终完成四次挥手。
四次挥手(断开连接)
2、粘包
对6.2服务端代码中的recv()函数的参数从127改为1,即一次recv一个字符。
客户端发送一个 "hello" ,服务端将会把hello每个字符换行打印一次,客户端收到一个 "ok" ;当客户端再发送数据后,将会收到四个 "ok" 。如下图:
原因:首先明确程序的循环运行速度要小于服务端将数据从其发送缓冲区传递到客户端的接收缓冲区。recv()和send()都是从接收缓冲区和发送缓冲区中读取数据,并不意味着把数据发给对方或者从对方处接收到数据。
当服务端recv()第一个字符 ‘h’ 后,此时客户端的recv()正处于阻塞状态(客户端的接收缓冲区为空),send()一个 "ok" 到发送缓冲区并传递给客户端的接收缓冲区,还没等到服务端recv()第二个字符 ‘e’ 后send()第二个 "ok" ,客户端就已经recv()了其接收缓冲区中的 “ok” ,此时客户端的接收缓冲区只有一个 “ok” 。客户端recv()后阻塞在fgets()函数处,等待输入数据。等待过程中,服务端也已经将剩下的四个字符对应的四个 "ok" 陆陆续续发送到客户端的接收缓冲区中,当客户端刚recv()第二个数据后,就会立即读取接收缓冲区中的四个 "ok" ,将其打印出来。
查看缓冲区的数据字节大小命令:netstat -natp
出现这种数据粘连在一起无法区分第几次发送的情况即为粘包。
粘包的概念:TCP粘包就是指发送方发送的若干包数据到达接收方时粘成了一包
产生粘包的原因:
1、TCP是基于字节流的,虽然应用层和传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一连串无结构的字节流,没有边界; 2、在TCP的首部没有表示数据长度的字段,基于上面两点,在使用TCP传输数据时,才有粘包或者拆包现象发生的可能。
粘包/拆包发生的原因:
发生TCP粘包或拆包有很多原因,现列出常见的几点: 1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。 2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。 3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。 4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
TCP 字节流的特点:发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,应用程序对数据的发送和接收是没有边界限制的。
粘包对于数据下载没有影响,只需要将数据全部收到就可以;而在交互时需要发送次数与接收次数对应。
粘包的简单解决办法:
1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2、在数据前后加上标志,对方接收到数据后进行检查数据是否完整。3、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。