1,socket通信
2,socket对象的参数
socket families:网络层
socket.AF_INET# IPV4
socket.AF_INET6# IPV6
socket.AF_UNIX # unix本机进程间通信
socket types:传输层
socket.SOCK_STREAM #TCP
socket.SOCK_DGRAM #UDP
socket.SOCK_RAW #原始套接字,可以伪造IP头
socket.SOCK_RDM #UDP,保证传到,但不保证顺序
3,黏包
场景:
单次接收设置1024,但是实际数据大于1024,多于1024的内容会再下次接收到
服务器连续两次send,可能会被放进缓冲区形成黏包
解决方法:
计算发送内容大小,先把大小发送给接收方,发送完大小后服务器必须加阻塞确认,防止黏包
拿到内容大小后,后续如果还有多次send多问题,就可以改用精确接收内容大小防止黏包了,无需每次阻塞确认
特别注意中文str长度1,变成bytes后长度是3
4,模拟SSH
服务器端:
import socket import os server = socket.socket() server.bind(('localhost', 19999)) # 绑定端口 server.listen(3) # 监听端口,这里不是并发 while True: conn, addr = server.accept() # 等待连接,进入阻塞状态 while True: cmd = conn.recv(1024) # 接收大小是1024 if not cmd: # linux会陷入recv死循环,建议都加上防止反复接收死循环 break cmd = cmd.decode() cmd_res = os.popen(cmd).read().encode() cmd_lens = len(cmd_res) conn.send(str(cmd_lens).encode()) client_ack = conn.recv(1024) # 加个确认防止黏包 conn.send(cmd_res) # server.close() # 这里如果加close,server不会立即关闭,会在当前client结束后再关
客户端:
import socket client = socket.socket() client.connect(('localhost', 19999)) while True: msg = input('>>: ').strip() if not msg: continue elif msg == 'exit': # 退出循环,关闭连接 break client.send(msg.encode()) # 只能send bytes,默认utf-8 cmd_res_size = client.recv(1024) cmd_res_size = int(cmd_res_size.decode()) # bytes -> str -> int client.send(b'ack') # 发送确认,解决长度与实际data之间的黏包问题 received_size = 0 received_data = b'' while received_size < cmd_res_size: data = client.recv(1024) received_size += len(data) received_data += data print(received_data.decode()) client.close()
5,socket接收文件
流程:读取文件名,检测文件是否存在,打开文件,检测文件大小,发送文件大小给客户端,等待客户端确认,边读边发,发送MD5
服务器端:
import socket import os import hashlib server = socket.socket() server.bind(('localhost', 19999)) server.listen() while True: conn, addr = server.accept() while True: data = conn.recv(1024) if not data: break cmd, filename = data.decode().split() if os.path.isfile(filename): f = open(filename, 'rb') # 'rb'打开,后面不用encode了 file_size = os.stat(filename).st_size conn.send(str(file_size).encode()) # 发送文件大小 conn.recv(1024) # 等待确认 m = hashlib.md5() for line in f: # 发送文件,f是迭代器 m.update(line) # 逐行更新计算MD5 conn.send(line) f.close() conn.send(m.hexdigest().encode()) # 将MD5发送给客户端
客户端:
使用get + 文件名,获取文件
import socket import hashlib client = socket.socket() client.connect(('localhost', 19999)) while True: cmd = input('>>: ').strip() if not cmd: continue elif cmd == 'break': break elif cmd.startswith('get'): client.send(cmd.encode()) # 只能send bytes,默认utf-8 file_size = client.recv(1024) file_size = int(file_size.decode()) # bytes -> str -> int client.send(b'ack') # 发送确认,解决长度与实际data之间的黏包问题 received_size = 0 file_name = cmd.split()[1] m = hashlib.md5() f = open(file_name + '.new', 'wb') while received_size < file_size: size = 1024 if file_size - received_size > 1024: buff = file_size - received_size # 最后一次收实际数据,防止黏包把服务器MD5写入文件 data = client.recv(size) received_size += len(data) m.update(data) f.write(data) f.close() client_file_md5 = m.hexdigest() server_file_md5 = client.recv(1024).decode() print('客户端文件MD5:', client_file_md5) print('服务器端文件MD5:', server_file_md5) client.close()
6,socketserver
对socket进行二次封装,简化了socket服务器端编写,实现服务器端并发
socketserver实现模拟SSH服务器端(客户端不变):
class TCPHandler(socketserver.BaseRequestHandler): def handle(self): while True: # 不写while True,每个连接只能处理一次命令 cmd = self.request.recv(1024) if not cmd: # cmd为空代表客户端断开了,不做非空判断客户端断开时可能会无限循环 break cmd = cmd.decode() cmd_res = os.popen(cmd).read().encode() cmd_lens = len(cmd_res) self.request.send(str(cmd_lens).encode()) self.request.recv(1024) self.request.send(cmd_res) if __name__ == '__main__': server = socketserver.TCPServer(('localhost', 9999), TCPHandler) server.serve_forever()
将TCPServer改成ThreadingTCPServer可以实现多并发