首先要清楚的几点是
- socket服务本质是一个应用服务,属于应用层。
- 数据之间的传输依靠的是底层的硬件,如网卡,位于物理层,传输的是字节。
- 应用层要想获得物理层的数据必须通过操作系统,当socket要传输或者接收网络数据,必须由操作系统中间调度,socket只是单纯的搬运缓冲区的数据。
- 网络之间的数据传输依靠的是tcp/udp等协议,socket服务只是单纯的搬运工,要将二者区别开看。
- tcp的传输是字节流,udp传输的是数据报,两者在数据传输上的区别是udp已经定制好报头,而这些报头也扮演着分隔符的功能,而字节流更像流水,没有界限,所以会socket搬运的时候只是按照规定的长度搬运,不管这串数据之间是否关联,这就造成了粘包,而在udp中,由于有报头作为分隔符就明确的知道报与报的关系,不会造成相互倾占。
- send()与recv()没有一一对应的关系,sendto()与recvfrom()由于有报头所以存在一一对应关系,具体的是,如果有3个发送,却有4个接受,那么程序会卡在第4个接受上。
socket.recv() 与socket.recvfrom() 面对发送消息为空时:
前者会被阻塞,后者会正常发送(不过送的内容为空),为何?因为前面已经提到了,udp协议会为每一次发送消息定制报头,虽然发送消息为空,但是加上了报头数据,所以接收方的缓冲区依然会接收到数据,反观前者发送为空,缓冲区就一直为空,相当于没有任何发送,接收程序依然在等待。
客户端异常断开服务和正常socket.close()的区别
正常关闭会使得recv不再阻塞,会一直搬运缓冲区的数据,而缓冲区为空,所以搬运一直的是空(b''
),可以用一个if判断解决。
if msg: #如果为空
break #跳出通讯的循环,不再搬运数据
如果是异常断开(即程序异常断开,socket实例没有关闭),则服务端会报错,解决办法:
try:
'这里写正常时执行的代码'
except Exception as e:
print(e)
'这里写处理办法' #这样服务端程序就不会因为报错而断开连接
为tcp协议定制报头
有时候,发送量远远大于一次接受的量(一般接受不超过8k字节),而缓冲区也小于要发送的数据量,一般对数据做拆分处理。
.sendall()
这个方法不会将没有存进缓冲区的数据丢失,而是暂存,等待有空间,剩下的再依次进入缓冲区域。- 定制报头要用到两个模块:
struct
和pickle
模块