网络编程
1.socket概念
socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议.
2.tcp协议和udp协议
TCP是可靠的,面向连接的协议,传输效率低,全双工通信(发送缓存和接收缓存),面向字节流.
使用TCP的应用:Web浏览器,电子邮件,文件传输程序.
UDP是不可靠的,无法连接的服务,传输效率高(发送前延迟小),一对一,一对多,多对一,多对多,面向报文,尽最大努力服务,无拥塞控制.
使用UDP的应用:域名系统(DNS),视频流,IP语音
3.基于TCP协议的socket
tcp是基于连接的,必须先启动服务端,然后再启动客户端去连接服务端
server端:
1 import socket 2 3 server = socket.socket() 4 server.bind(("127.0.0.1",8000))#把地址绑定到套接字 5 server.listen(5) #监听链接 6 conn,addr = server.accept()#接收客户端的链接 7 ret = conn.recv(1024)#接收客户端的信息,最大接收字节数为1024 8 print(ret)#打印客户端信息 9 conn.send(b"hello")#向客户端发送信息 10 conn.close()#关闭客户端套接字 11 server.close()#关闭服务器套接字
client端:
1 import socket 2 client = socket.socket() #创建客户端套接字 3 client.connect(("127.0.0.1",8000))#连接服务端 4 client.send(b"hello")#向服务端发送信息 5 ret = client.recv(1024)#从服务端接收信息 6 print(ret) 7 client.close()#关闭客户端套接字
在python2中,服务端和客户端的send()和recv()都是字符串
在python3中,服务端和客户端的send()和recv()都是字节
服务端:
accept,阻塞,等待客户端来连接
recv,阻塞,等待客户端发来 数据
客户端:
connect,阻塞,一直在连接,直到连接成功才会往下执行
recv,阻塞,等待服务端发来数据
4.黏包
1.subprocess模块
subprocess.Popen介绍
1 import subprocess 2 3 res = subprocess.Popen(cmd.decode("utf-8"), 4 shell = True, 5 stderr = subprocess.PIPE, 6 stdout = subprocess.PIPE) 7 8 res的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()和res.stderr.read()读出的就是GBK编码,在接收端需要用GBK解码,且只能从管道里读一次结果
同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种现象就是黏包.
基于tcp协议实现的黏包
1 服务端: 2 import socket 3 import subprocess 4 5 tcp_socket_server = socket.socket() 6 tcp_socket_server.bind(("127.0.0.1",8000)) 7 tcp_socket_server.listen(5) 8 9 while True: 10 conn,addr = tcp_socket_server.accept() 11 print("客户端",addr) 12 13 while True: 14 cmd = conn.recv(1024) 15 if len(cmd) == 0: 16 break 17 res = subprocess.Popen(cmd.decode("utf-8"),shell = True, 18 stdout = subprocess.PIPE, 19 stdin = subprocess.PIPE, 20 stderr = subprocess.PIPE) 21 22 stderr = res.stderr.read() 23 stdout = res.stdout.read() 24 conn.send(stderr) 25 conn.send(stdout)
1 客户端 2 import socket 3 client = socket.socket() 4 client.connect(("127.0.0.1",8000)) 5 6 while True: 7 cmd = input(">>>:").strip() 8 if len(cmd) == 0: 9 continue 10 elif cmd == "quit": 11 break 12 client.send(cmd.encode("utf-8")) 13 data = client.recv(1024) 14 15 print(data.encode("utf-8"))
只有TCP有黏包现象,UDP永远不会黏包
5.黏包成因
TCP协议的拆包机制
当发送端缓冲区的长度大于网卡的MTU时,TCP会将这次发送的数据拆成几个数据包发送出去.
MTU是Maximum Transmission Unit的缩写,意思是网络上传送的最大数据包,MTU的单位是字节,如果本机的MTU比网管的MTU大,大的数据包就会被拆开来送,这样会产生很多数据包碎片,增加丢包率,降低网络速度.
基于tcp协议特点的黏包现象成因
基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束.
此外,发送方引起的黏包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够的数据后才发送一个TCP段,若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了黏包数据.
发生黏包的两种情况:
1.发送方的缓存机制:发送方需要等缓冲区满了才发送出去,造成黏包(发送数据时间间隔很短,数据量很小,会合到一起,产生黏包)
2.接收方的缓存机制:接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只接受了一小部分,服务端下次再接收的时候还是从缓冲区拿上次遗留的数据,产生黏包)
总结:黏包现象只发生在TCP协议中,从表面上看,黏包问题主要是因为发送方和接收方的缓存机制,tcp协议面向通信流的特点.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的.
解决方案:
1.
这种方法是让发送端在发送数据前,先把自己将要发送的字节流总大小发给接收端,然后接收端来一个死循环接受完所有数据.
2.
借助struct模块,这个模块可以把要发送的数据长度转换成固定长度的字节,这样客户端每次接收消息之前只要先接收这个固定长度字节的内容看一看接下来要接受的信息的大小,那么最终接收的数据只要达到这个消息就停止,就能刚好不多不少的接收完整的数据.
struct模块可以把一个类型,如数字,转换成固定长度的字节
1 import struct 2 a = struct.pack("i",12345) 3 print(a,type(a)) 4 5 6 b'90\x00\x00' <class 'bytes'>
发送时:先发送struct转换好的数据长度(4个字节),再发送数据
接收时,先接收4个字节使用struct转换成数字来获取要接受的数据长度,再按照长度接收数据.
也可以将报头做成字典,字典中包含要发送的真实数据的信息,然后json序列化,最后用struct将序列化后的数据长度打包成4个字节
发送时:先发送报头长度,再编码报头内容然后发送,最后发送真实内容
接收时:先接收报头长度,用struct取出来,根据取出来的长度收取报头内容,然后解码,反序列化,最后从反序列化的结果中取出待取数据的详细信息,然后取真实的数据内容.