socket通信模块
1 原理
1.1 模型
应用层协议需要必须传输数据,需要把数据封装为TCP/UDP包来传输,这个对TCP/UDP的封装就是socket通信。在socket里,包括send和receive。
一个服务器上最多开通的port为65535个,一个ServerAPP监听在它的ip:port上,然后client 给这个ip:port发送socket send通信,信息里封装自己的ip:port(client的port是随机分配的),ServerAPP收到后, 返回信息给client的ip:port。clinet就收到了消息。
1.2 socket families
socket.AF_UNIX 本机之间通信
socket.AF_INET ipv4网络通信
1.3 socket types
socket.SOCK_STREAM tcp通信,最常用。
socket.SOCK_DGRAM udp通信
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。就是说前两个是在传输层,这个是在网络层,甚至可以伪造ip地址。
1.4 基本实现
客户端:
先声明一个socket实例
然后连接到一个server
然后发数据。
然后接受server发过来的数据(是bytes类型,需要解码)
import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('localhost',3939)) client.send(b'in client,to server') data = client.recv(1024) print(data.decode()) client.close()
服务端:
先声明socket实例(地址族和类型)
accept后就处于阻塞状态,等待client。conn就是为连接过来的client开启的实例。
(这时客服端断开,server会收到一个null,会进入死循环。)
然后使用recv方法,将接受的数据赋值给data。recv(n)中n的值,官方建议最大8192(bytes)。
服务端后面把相关数据send给client的conn实例。
>>>
这样的服务端只能并发接受一个client的连接,并且这个连接断开后服务端自己也停止运行了。
import socket server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('localhost',3939)) server.listen() conn,addr = server.accept() print(conn,addr) data = conn.recv(1024) print('receive: %s' %(data)) send_data = '在服务器,给客户端'.encode() conn.send(send_data)
1.5 服务端响应多个client(还是单并发)
二层循环中,if not data是为了应对client断开时发送过来的null导致的死循环
外层循环是为了当一个client断开后,服务端重新accept阻塞,然后把新client请求复制给conn。
在bind时,0.0.0.0代表本机的ip和127.0.0.1。如果bind ip,则客户端listen 127.0.0.1无法访问。如果bind 127.0.0.1,则网络中其他ip无法访问。0.0.0.0则都能。
服务端接受到的数据,用subprocess.Popen调用shell。
服务端:
import socket import subprocess server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('0.0.0.0',3937)) server.listen() while True: conn,addr = server.accept() while True: data = conn.recv(1024) if not data: print('conn lost') break print('执行指令: %s' % (data)) p = subprocess.Popen(data.decode(), stdout=subprocess.PIPE, shell=True) stdout = p.stdout.read() if len(stdout) == 0: send_data = 'cmd has no output...' else: send_data = stdout print('data size: %d' %(len(send_data))) conn.send(str(len(send_data)).encode()) conn.send(send_data)
1.6 根据数据包长度接受多包完整数据
客户端:
#!/usr/bin/env python import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('192.168.10.12',3937)) while True: msg = input('>>').strip() if len(msg) == 0:continue #防止空输入产生卡死 client.send(msg.encode()) #bytes类型发送 rece_total_size = int(client.recv(1024).decode('gbk')) #返回数据的长度 #print(rece_total_size) rece_current_size = 0 #计算已经收到的长度 rece_current_data = b'' #收集已经接受的数据段 while rece_current_size != rece_total_size: #每个数据包接受后都要进行判断,长度一致说明接受已完成。 data = client.recv(1024) rece_current_data += data rece_current_size += len(data) #print('rece_current_size: %d' %(rece_current_size)) print(rece_current_data.decode('gbk')) #打印最终接受的数据
1.7 粘包
1.5中这两条send语句,在win没有问题,但在linux上会被合并,然后从缓冲区里一次性发给client。这种紧挨着的send语句被一次性发送的现象叫粘包。
一种解决办法是加入防粘包信号
服务端:
import socket import subprocess server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(('localhost',3937)) server.listen() while True: conn,addr = server.accept() while True: data = conn.recv(1024) if not data: print('conn lost') break print('执行指令: %s' % (data)) p = subprocess.Popen(data.decode(), stdout=subprocess.PIPE, shell=True) stdout = p.stdout.read() if len(stdout) == 0: send_data = 'cmd has no output...' else: send_data = stdout print('data size: %d' %(len(send_data))) conn.send(str(len(send_data)).encode()) conn.recv(1024) #接受防粘包信息 conn.send(send_data)
客户端:
#!/usr/bin/env python import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('192.168.10.12',3935)) while True: msg = input('>>').strip() if len(msg) == 0:continue #防止空输入产生卡死 client.send(msg.encode()) #bytes类型发送 rece_total_size = int(client.recv(1024).decode()) #返回数据的长度 client.send(b'1') #发送防粘包信号 #print(rece_total_size) rece_current_size = 0 #计算已经收到的长度 rece_current_data = b'' #收集已经接受的数据段 while rece_current_size != rece_total_size: #每个数据包接受后都要进行判断,长度一致说明接受已完成。 data = client.recv(1024) rece_current_data += data rece_current_size += len(data) #print('rece_current_size: %d' %(rece_current_size)) print(rece_current_data.decode()) #打印最终接受的数据
1.8 传文件
socket文件传输效率低,容易丢包,实际上传文件用RabbitMQ消息队列。
2 socketserver模块
实际上就是对socket的封装,这里模块里包含一些简化TCP、UDP操作的类。
class socketserver.TCPServer() #使用tcp协议,在服务端和客户端之间提供连续的数据流。单线程。
class socketserver.ForkingTCPServer() #多进程
class socketserver.ThreadingTCPServer() #多线程
2.1 TCPServer
import socketserver class MyTCPHandler(socketserver.BaseRequestHandler): #首先要定义一个处理基类 def handle(self): #对这个基类进行重写 while True: try: self.data = self.request.recv(1024).strip() #self是一个实例,下面有requset这个子类,它里面有recv和send方法。 print(self.client_address[0]) #实例的连接客户端地址,client_address[0]是ip,[1]是port print(self.data) #recv的内容赋值给data self.request.send(self.data.upper()) except: #当客户端断开时会抛出一个异常,所以要进行异常处理 print('%s disconnected..' %(self.client_address[0])) break if __name__ == '__main__': HOST,PORT = '0.0.0.0',3934 conn = socketserver.TCPServer((HOST,PORT),MyTCPHandler) #每个请求过来,TCPServer就会去实例化MyTCPHandler这个基类,然后用它的handler去和客户端交互。 conn.serve_forever() #serve_forver表示不停息的响应。
2.2 ForkingTCPServer
if __name__ == '__main__': HOST,PORT = '0.0.0.0',3934 conn = socketserver.ForkingTCPServer((HOST,PORT),MyTCPHandler) conn.serve_forever()
没有启动客户端时:
# ps aux | grep python3 | grep -v grep root 40981 0.0 0.7 132580 7340 pts/1 S+ 14:38 0:00 python3 ts.py
启动每个客户端,就会多一条进程
[root@yhzk01 scripts]# ps aux | grep python3 | grep -v grep root 40981 0.0 0.7 132580 7340 pts/1 S+ 14:38 0:00 python3 ts.py root 41069 0.0 0.4 132580 4820 pts/1 S+ 14:46 0:00 python3 ts.py root 41070 0.0 0.4 132580 4820 pts/1 S+ 14:46 0:00 python3 ts.py root 41071 0.0 0.4 132580 4820 pts/1 S+ 14:46 0:00 python3 ts.py
2.3 ThreadingTCPServer
if __name__ == '__main__': HOST,PORT = '0.0.0.0',3934 conn = socketserver.ThreadingTCPServer((HOST,PORT),MyTCPHandler) conn.serve_forever()