socket编程
socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议
Ip层的ip地址可以唯一标识主机,而TCP层协议和端口可以唯一标识主机的一个进程,这样可以利用IP地址+协议+端口号唯一标识网络中的一个进程
socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。socket是一种“打开-读写-关闭”模式的实现,服务器和客户端各自维护一个“文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
套接字工作流程
服务端先初始化socket,然后与端口绑定(bind),对端口进行接听(listen),调用accept阻塞,等待客户端连接。这时如果有个客户端初始化一个socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器的连接就建立了。客户端发送数据请求,服务器接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
from socket import *s=socket(AF_INET,SOCK_STREAM) #创建套接字s.bind(('127.0.0.1',8080)) #绑定IP地址和端口(0-65535)s.listen(5)conn,client_addr=s.accept() #链接成功后返回一个链接对象:(链接对象,客户端IP和端口)#print(conn,client_addr)data=conn.recv(1024) #每次最大收取1024bytesconn.send(data.upper())conn.close() #关闭客户端套接字s.close() #关闭服务器端套接字
服务端套接字函数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() 关闭套接字
面向锁的套接字方法 s.setblocking() 设置套接字的阻塞与非阻塞模式 s.settimeout() 设置阻塞套接字操作的超时时间 s.gettimeout() 得到阻塞套接字操作的超时时间 面向文件的套接字的函数 s.fileno() 套接字的文件描述符 s.makefile() 创建一个与该套接字相关的文件
基于TCP的套接字
#TCP服务端from socket import *s=socket(AF_INET,SOCK_STREAM)s.bind(('127.0.0.1',8080))s.listen(5)while True: #建立和多个客户端链接 print('starting...') conn,client_addr=s.accept() #阻塞情况1 print(client_addr) while True: try: data=conn.recv(1024) #阻塞情况2 if not data:break #针对linux断开链接后会一直收空消息的处理 print('客户端消息',data) conn.send(data.upper()) except ConnectionResetError: #非正常断开链接处理 break conn.close()
#TCP客户端from socket import *c=socket(AF_INET,SOCK_STREAM)c.connect(('127.0.0.1',8080))while True: msg=input("message:").strip() if not msg:continue #客户端发送空消息处理 c.send(msg.encode('utf-8')) data=c.recv(1024) print(data)c.close()
#模拟ssh通信from socket import *import subprocessimport structserver=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8081))server.listen(5)print('starting...')conn,client_addr=server.accept()print(client_addr)while True: try: cmd=conn.recv(8096) if not cmd:break obj=subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout=obj.stdout.read() stderr=obj.stderr.read() #制作固定长度的报头 total_size=len(stdout)+len(stderr) headers=struct.pack('i',total_size) # 发送命令的长度 conn.send(headers) #发送命令的执行结果 conn.send(stdout) conn.send(stderr) except ConnectionResetError: breakconn.close()server.close()
#模拟ssh通信from socket import *import structclient=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8081))while True: cmd=input("message:").strip() if not cmd:continue client.send(cmd.encode('utf-8')) #接收命令长度 headers=client.recv(4) total_size=struct.unpack('i',headers)[0] #接收命令结果 recv_size=0 data=b'' while recv_size < total_size: recv_data=client.recv(1024) data+=recv_data recv_size+=len(recv_data) print(data.decode('gbk'))client.close()
粘包现象及解决方案
只有TCP有粘包现象,UDP没有。粘包问题主要是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的。
#服务端from socket import *import timeserver=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8081))server.listen(5)print('starting...')conn,client_addr=server.accept()res1=conn.recv(1)print('res1:',res1)time.sleep(6)res2=conn.recv(10)print('res2',res2)conn.close()server.close()
#客户端from socket import *import timeclient=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8081))client.send('hello'.encode('utf-8'))time.sleep(5)client.send('world'.encode('utf-8'))client.close()
解决粘包现象
from socket import *import subprocessimport structimport jsonserver=socket(AF_INET,SOCK_STREAM)server.bind(('127.0.0.1',8081))server.listen(5)print('starting...')conn,client_addr=server.accept()print(client_addr)while True: try: cmd=conn.recv(8096) if not cmd:break obj=subprocess.Popen(cmd.decode('utf-8'),shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) stdout=obj.stdout.read() stderr=obj.stderr.read() #制作固定长度的报头 headers = { 'filepath': 'a.txt', 'md5': 'fdgre343fg', 'total_size': len(stderr)+len(stdout) } headers_json = json.dumps(headers) headers_bytes = headers_json.encode('utf-8') #发送报头的长度 conn.send(struct.pack('i',len(headers_bytes))) # 发送报头 conn.send(headers_bytes) #发送命令的执行结果 conn.send(stdout) conn.send(stderr) except ConnectionResetError: breakconn.close()server.close()
from socket import *import structimport jsonclient=socket(AF_INET,SOCK_STREAM)client.connect(('127.0.0.1',8081))while True: cmd=input("message:").strip() if not cmd:continue client.send(cmd.encode('utf-8')) #接收报头长度 headers_size=struct.unpack('i',client.recv(4))[0] #接收报头 headers_bytes=client.recv(headers_size) headers_json=headers_bytes.decode('utf-8') headers_dic=json.loads(headers_json) total_size=headers_dic['total_size'] #接收命令结果 recv_size=0 data=b'' while recv_size < total_size: recv_data=client.recv(1024) data+=recv_data recv_size+=len(recv_data) print(data.decode('gbk'))client.close()