什么是socket
socket是应用层与传输层之间(TCP/IP通信协议)的中间软件抽象层,它是一组接口,将复杂的TCP/IP等协议隐藏在简单的接口之后,我们只要使用socket去编程,自然而然就是遵循TCP/UDP协议的。
两个家族
基于文件型的(AF_UNIX)与基于网络型的(AF_INET)
套接字工作流程
要使用套接字,肯定要知道其工作流程
基本模板
# 对于服务端 # 获得TCP套接字对象(TCP协议也称之为流式协议,UDP也称之为数据报协议) sk_obj = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 绑定IP及端口 sk_obj.bind(('ip',port)) # 开启监听模式(设置半连接池) sk_obj.listen(5) #等待请求(建立通信双向通道,conn为建立的通道对象,client..为客户端地址) conn,client_address = sk_obj.accept() # 收消息 data = conn.recv(1024) # 设置一次最大接收1024个字节 # 发消息 conn.send() # 发送的数据为bytes类型 # 关闭连接及回收资源 conn.close() sk_obj.close() # 对于客户端 # 获得TCP套接字对象 sk_obj = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 向服务端发送连接请求 sk_obj.connect((server_ip,server_port)) # 发消息 sk_obj.send() # 收消息 data = sk_obj.recv(1024) sk_obj.close()
基于socket实现简单通信
# Server import socket obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM) print('开始接收客户端的请求....') # obj.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #重用端口 obj.bind(('127.0.0.1', 8080)) obj.listen(5) while True: conn, address = obj.accept() while True: try: data = conn.recv(1024) # 等待接收(必须接收大于0个bytes数据,否则一直等待) if not data:break # 用于linux等系统客户端单方面退出,服务端收空的场景 print('来自客户端的数据>>>%s' % data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError: break conn.close() obj.close() # Client import socket obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM) obj.connect(('127.0.0.1', 8080)) while True: to_data = input('输入发送的数据>>>').strip() if not to_data: continue # 如果没有这个判断,客户端发空会卡住--->原因 ''' 我们是在应用层进行操作,发送数据的过程是,在客户端的内存中(这里是pychrm应用软件的内存)产生一个变量,并将其转换为 bytes类型,复制并发送给操作系统,操作系统根据TCP协议/IP协议/以太网协议等对数据进行封装,最后在物理层打散发送给网卡, 由网卡根据路由协议发送给对方的网卡(己方操作系统并不会立马清除数据),再一层层的解包到对方操作系统的缓存区,服务端从操作系统那里 拿到数据,并通过操作系统--网卡给对方反馈响应,对方才将数据清空(这也是TCP协议可靠的原因),那么当客户端发空的时候,操作系统 是不会发数据给网卡的,而客户端在等待接收服务端的数据,服务端也在等待客户端发送数据 ''' obj.send(to_data.encode('utf-8')) data = obj.recv(1024) print('来自服务端的数据>>>%s' % data) obj.close() 此外值得注意的是:send操作是将应用软件的数据发送给操作系统(应用软件不能直接和硬件打交道),而recv操作也是应用软件从操作系统的缓冲区拿数据
粘包问题
要知道的是,粘包问题来自于TCP,UDP没有粘包问题
TCP是流式协议,是面向连接的,当对方send一条消息时,无论底层如果分段,TCP协议层会把构成整体消息的数据段排序完后再呈现再内核缓冲区
UDP是数据报协议,面向消息的,无连接的,不存在发空(有报头),是有消息保护边界的
由发送方引起的粘包问题
nagle算法规定了在数据传输的过程中,会将数据量较小,时间间隔较短的多条消息合并发送
# Server import socket sk_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk_obj.bind(('127.0.0.1', 8080)) sk_obj.listen(5) conn, client = sk_obj.accept() data1 = conn.recv(1024) data2 = conn.recv(1024) data3 = conn.recv(1024) print(data1) print(data2) print(data3) conn.close() sk_obj.close() # Client import socket sk_obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk_obj.connect(('127.0.0.1',8080)) sk_obj.send(b'data1') sk_obj.send(b'data2') sk_obj.send(b'data3')
由接收方引起的粘包问题
接收方来不及接受操作系统缓冲区的数据,造成下一次接受还是从操作系统中获取上一次遗留的数据内容
如何解决粘包问题
粘包发生的原因就是接收端不清楚发送端发送的数据大小(少收/多收)
我们可以制作报头(用于记录数据的类型,长度等...),接受端根据报头内容,知道数据的长度,而报头的长度又可以通过struct模块
变成固定的长度
# Serve from socket import * import json import struct import subprocess socket_family = AF_INET socket_type = SOCK_STREAM sk_obj = socket(socket_family,socket_type) sk_obj.bind(('127.0.0.1',8080)) sk_obj.listen(5) print('服务端开始监听>>>') while True: conn,client_address = sk_obj.accept() while True: try: data = conn.recv(1024) print('来着客户端>>>',data.decode('utf-8')) if not data: break p = subprocess.Popen(data.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) stdout = p.stdout.read() stderr = p.stderr.read() # stdout = p.stdout.read() header = {'data_size':len(stderr)+len(stdout)} # 制作报头 header_bytes = json.dumps(header).encode('utf-8') # 将报头转换为json格式的bytes header_len = struct.pack('i',len(header_bytes)) # 转换成固定长度 conn.send(header_len) conn.send(header_bytes) conn.send(stdout) conn.send(stderr) except ConnectionResetError: break conn.close() #Client from socket import * import struct import json socket_family = AF_INET socket_type = SOCK_STREAM sk_obj = socket(socket_family,socket_type) sk_obj.connect(('127.0.0.1',8080)) while True: cmd = input('输入cmd命令>>>').strip() if not cmd: continue sk_obj.send(cmd.encode('utf-8')) header_len = sk_obj.recv(4) # 接收报头的长度信息 header_bytes = sk_obj.recv(struct.unpack('i',header_len)[0]) # 接收报头 header = json.loads(header_bytes) b = '' total = 0 while total < header['data_size']: data = sk_obj.recv(1024) b += data.decode('gbk') total += len(data) print(b)