python socket编程
Socket 是什么?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
套接字的工作流程
socket()模块函数用法
服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字
基于TCP的套接字使用
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
服务端
ss = socket() #创建服务器套接字 ss.bind() #把地址绑定到套接字 ss.listen() #监听链接 inf_loop: #服务器无限循环 cs = ss.accept() #接受客户端链接 comm_loop: #通讯循环 cs.recv()/cs.send() #对话(接收与发送) cs.close() #关闭客户端套接字 ss.close() #关闭服务器套接字(可选)
客户端
cs = socket() # 创建客户套接字 cs.connect() # 尝试连接服务器 comm_loop: # 通讯循环 cs.send()/cs.recv() # 对话(发送/接收) cs.close() # 关闭客户套接字
粘包的处理。
tcp协议又称流协议,与udp不同,udp协议是数据报协议,会在每个数据包加报头信息的,所以不会出现粘包现象。
tcp出现粘包现象有两种情况:
发送端等缓冲满了才发送信息,发送数据时间较短,数据包较小的数据包,会和在一起发送出去。
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
处理粘包的方法
为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端,对端在接收时,先从缓存中取出定长的报头,然后再取真实数据
举例
# -*- coding:utf-8 -*- import socket import subprocess import struct phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #默认是tcp连接 phone.bind(('127.0.0.1',8080)) #绑定ip和端口 phone.listen(5) #指定连接次数 while True: a,b=phone.accept() #监听 a 套接字链接,b 客户端的ip和port while True: try: data=a.recv(1024) #接收的数据大小 if not data:break #拿到命令执行结果 obj = subprocess.Popen(data.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout_res=obj.stdout.read() stderr_res=obj.stderr.read() #先发送报头 baotou=len(stderr_res)+len(stdout_res) #统计结果大小 daxiao=struct.pack('i',baotou) #变成4个字节的固定长度 a.send(daxiao) #发送长度信息 #发送数据信息 a.send(stdout_res+stderr_res) #发送消息 except AttributeError: break a.close() phone.close()
# -*- coding:utf-8 -*- import socket import struct phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) #使用的ip端口 while True: a=input('>>').strip() if not a.strip():continue phone.send(a.encode('gbk')) #发送信息 #先接收报头信息 data=phone.recv(4) #接收报头信息 shuju=struct.unpack('i',data)[0] #解压报头信息 #接收真是数据 d=b'' #收集接收的数据 recv=0 #收集长度信息 while recv < shuju: cmd=phone.recv(1024) d+=cmd recv+=len(cmd) print(d.decode('gbk')) #转换成gdk (windows字符集) phone.close()
改进版
自定义报头信息
#_*_coding:utf-8_*_ import socket import subprocess import struct import json phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议 phone.bind(('127.0.0.1',8080)) phone.listen(5) while True: conn,client_addr=phone.accept() #(套接字链接,客户端的ip和port) print(client_addr) while True: #通信循环 #收消息 cmd=conn.recv(1024) # 1024最大的限制 if not cmd:break #针对linux系统 #执行,拿到执行结果 obj = subprocess.Popen(cmd.decode('gbk'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout_res=obj.stdout.read() stderr_res=obj.stderr.read() # 制作报头 header_dic = { 'filename': 'a.txt', 'total_size': len(stdout_res)+len(stderr_res), 'md5': 'xxxxxxxxx' } head_json = json.dumps(header_dic) head_bytes = head_json.encode('utf-8') #先发报头长度 conn.send(struct.pack('i',len(head_bytes))) #先发报头 conn.send(head_bytes) #再发真是的数据 # conn.send(stdout_res+stderr_res) conn.send(stdout_res) conn.send(stderr_res) break #挂电话 conn.close() #关机 phone.close()
#_*_coding:utf-8_*_ import socket import struct import json #买手机 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #SOCK_STREAM代表TCP协议 #发起电话链接 phone.connect(('127.0.0.1',8080)) while True: #发消息 cmd= input('>>: ').strip() if not cmd:continue phone.send(cmd.encode('gbk')) #先收报头长度 struct_res=phone.recv(4) header_size=struct.unpack('i',struct_res)[0] #再收报头 head_bytes=phone.recv(header_size) head_json=head_bytes.decode('utf-8') head_dic=json.loads(head_json) #print(head_dic) #最收消息 cmd_res=b'' recv_size=0 total_size=head_dic['total_size'] while recv_size < total_size: recv_data=phone.recv(1024) cmd_res+=recv_data recv_size+=len(recv_data) print(cmd_res.decode('gbk')) #关机 phone.close()
ps:脚本使用到了struct模块 该模块可以把一个类型,如数字,转成固定长度的bytes