网路编程之socket与 socketserver、黏包
socket与socketerver才是我们学习python中网络编程的重中之重在介绍他们两个之前我先介绍一些相关知识
一.socket 概念
咱们现在ois模型中找到socket所承担的角色
socket处于应用层与传输层之间的软件抽象层,是一组接口,在设计模式中,socket其实就是一个门面模式,他把复杂的TCP\IP协议都隐藏在socket接口后门,对于用户来说一组简单的接口就是全部,让socket去组织数据以符合指定的协议,很相似与一个模块的功能
二.套接字(socket)
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
我们在网络基础里面举的例子就是TCP协议利用套接字做的简单的网络连接
三 黏包
黏包的成因
当发送端缓冲区的长度大于网卡的MTU时,TCP会将这次发送的数据拆成几个数据包发送出去,MTU是Maximum Transmission Unit的缩写意思是网络上传送的最大数据包,MTU 的单位是字节,大部分网络设备的MTU都是1500,如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多的数据包碎片,增加丢包率,降低网络速度.
基于TCP的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道文件的字节流是从何开始,在何处结束
此外,发送方引起的黏包是有TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送,一个TCP段,若连续几次需要send 的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了黏包的数据,
但是要注意一点是在UDP中就不会发生黏包
因为UDP是无连接的,面向消息的,提供高效服务,说白了它发出去的东西它是不管的,收到也好收不到也好跟他没关系,所以它才效率高
在它的内部块不使用优化算法,由于UDP支持的是一对多的模式,所以接受端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的 UDP包,在每一个TDP包中就有了消息头,这样对于接受端来说就容易进行分区处理了,即面向消息的通道是有消息保护边界的.
对于空消息:TCP是基于数据流的,于是收发消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容,也可以被发送,udp协议会帮助你封装上消息头发送过去
不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom必须对唯一sendinto接受完字节的数据就算完成所以数据根本不会 黏包只会丢失,不可靠
补充说明
用UDP协议发送时,用sendto函数最大能发送数据的长度为:65535- IP头(20) – UDP头(8)=65507字节。用sendto函数发送数据时,如果发送数据长度大于该值,则函数会返回错误。(丢弃这个包,不进行发送) 用TCP协议发送时,由于TCP是数据流协议,因此不存在包大小的限制(暂不考虑缓冲区的大小),这是指在用send函数时,数据长度参数不受限制。而实际上,所指定的这段数据并不一定会一次性发送出去,如果这段数据比较长,会被分段发送,如果比较短,可能会等待和下一次数据一起发送。
总结
黏包现象只发生在TCP协议中:
1.从表面上看,黏包问题主要是因为发送方和接受方的缓存机制,TCP协议面向流通信的特点
2.实际上主要还是因为接收方不知道消息之间的界限,不知道一次性取多少字节的数据造成的
黏包的解决方案
问题的根源在于,接受端不知道发送端将要传输的字节流长度,所以解决黏包的方法就是围绕,如何让发送端在发送数据前,将自己将要发送的字节流大小让接收端知道,然后接受端来一个死循环接收完所有的数据
服务端
import socket server = socket.socket() server.bind(("127.0.0.1"8001)) server.listen(5) while True: conn,addr = s.accept () while True: msg = conn.recv(1024) if not data : break res = subprocess.Popen msg.decode("utf8"),shell = True , stdin = subpross.PIPE, stderr = subprocess.PIPE, stdout = subprocess.PIPE) err = res.stderr.read() if err: ret = err else: ret = res.studout.read() data_lenhth =len(ret) conn.send(str(data_length).encode("utf8")) data = conn.recv (1024).decode("utf8") if data == "recv_ready": conn,sendall(ret) conn.close()
客户端
import socket,time client = socket.socket client.connect(("127.0.0.1),8001) while True: msg = input(">>>").strip() if len(msg) == 0 :continue if msg =="quit ":break s.send (msg.encode("utf8")) length= int(s.recv(1024).decode("utf8")) s.send("recv_ready".encode(utf8)) send_size = 0 recv_size =0 data = b"" while recv_size <length: data+=s.recv(1024) recv_size += len(data) print(data.decode("utf9")
虽然这样做解决了黏包的问题,但是随之而来的新问题有产生了在程序运行速度远大于网络传输速度,所以在发送一段字节前先用send发送该字节流的长度,这种方式会放大网络延迟带来的性能损耗
所以我们引出struct模块
这个模块可以把一个类型转换成固定的长度的Bytes