网络编程之套接字
一:套接字基础概念#
【1】
(1)socket起源
(1)C/S架构要遵循OSI七层参考模型
(2)如果我们想自己开发C/S程序 那么就必须在自己的程序编写OSI七层参考模型 效率低下 开发难度大
(3)socket就是将OSI封装好了 我们只需要调用即可
PS:socke类似于操作系统 将复杂的操作给封装 对外提供简易的接口实现操作
(2)图解:
C/S架构基于传统的TCP/IP
C/S基于socke架构:
二:TCP工作套接字流程#
(1)简易版套接字
例如:

import socket # 获取一个服务端的对象 server = socket.socket() # 设置连接地址 以及端口号 server.bind(('127.0.0.1',8080)) # 允许最大客户端连接数 server.listen(6) # 允许客户端发送请求 conn,addr = server.accept() # conn:TCP连接通道 addr:服务端地址与端口号 print(conn) # <socket.socket fd=268, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 13436)> print(addr) # ('127.0.0.1', 13436) # 从内存读取数据大小 data = conn.recv(1024) # b'hello i am client' print(data) # conn.send(b'Hello i am service')

import socket # 获取一个客户端对象 client = socket.socket() # 与服务端连接的地址 client.connect(('127.0.0.1',8080)) # 网络通信基于二进制 client.send(b'hello i am client') data = client.recv(1024) print(data) # b'Hello i am service'
PS:该版本不能进行循环交互数据
(2)循环交互数据版
例如:

import socket # 生成服务端对象 service = socket.socket() # 设置端口 service.bind(('127.0.0.1',80)) # 设置半地址池 service.listen(6) # 进入阻塞状态 允许接入 conn ,addr = service.accept() # 循环交互数据 while True: data = conn.recv(1024) # 从内存接收最大字节 print(data) # 将客户端给我发送的数据 全部大写形式返回 conn.send(data.upper())

import socket client = socket.socket() # 连接地址+端口 client.connect(('127.0.0.1',8080)) while True: client.send(b'hello') data = client.recv(1024) print(data)
PS:虽然可以进行循环交互数据 但是客户端退出 服务端会报错
(3)解决客户端退出报错版
例如:

import socket ''' 客户端给我发送什么 我将数据转换成大写 返回给客户端 ''' service = socket.socket() # 服务端ip+port service.bind(('127.0.0.1', 8080)) # 最大访问数 service.listen(6) while True: # 当有客户端进行退出 结束下列循环 进行新的循环 conn, addr = service.accept() while True: try: data = conn.recv(1024) # 针对MAC Linux 如果 收不到数据会一直为空 防止陷入一直等待 if len(data) == 0: break print(data) conn.send(data.upper()) except ConnectionRefusedError as e: print(e) # 捕捉到报错信息 将循环断开 break # 将此次连接给关闭 conn.close()

import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: # 发送二进制 将字符串转成二进制 msg = input('输入命令>>:').strip().encode('utf-8') ''' 防止客户端发送数据为空 服务端一直等待 服务端接收不到客户端的数据 无法进行返回 客户端收不到服务端的回复 一直进行等等 ''' if len(msg) == 0: continue client.send(msg) data = client.recv(1024) print(data)
三:TCP粘包问题#
(1)产生场景:
(1)TCP属于流式协议
(2)当产生的数据流比较小 且数据流发送间隔比较短
(3)TCP会一次性将数据打包发送给对方
(4)如果发送数据比较大 而对方接能力有限 发送数据需要在内存中排队等待等待发送
(5)之后输入的内容需要等前面发送数据全部被接收之后 其才能发送
例如:

import socket service = socket.socket() service.bind(('127.0.0.1', 8080)) service.listen(6) conn,addr = service.accept() print(conn.recv(1024)) # b'hellohellohello' print(conn.recv(1024)) # b'' print(conn.recv(1024)) # b''

import socket client = socket.socket() client.connect(('127.0.0.1', 8080)) client.send(b'hello') client.send(b'hello') client.send(b'hello')
PS:如上述中 客户端一次性将数据发送给服务端
(2)解决TCP粘包问题思路:
(1)由于我们在接收数据时候 不能一次性知道对方发来的数据大小是多少
(2)我们需要有一个第三方告诉我们该发来的数据包大小是多少
(3)但是这个第三方数据大小我们不能确定
(4)因此我们需要通过一个方法将第三方数据大小给固定 同时第三方告诉我们接收数据大小
(3)struct模块
(1)作用:
(1)将数据打包成一个固定的长度
(2)数据解包的时候 还原成原始数据长度
例如:
import struct res = '1234156132156146313215613512361514531' print(len(res)) # 37 # 将数据封装 res1 = struct.pack('i',len(res)) # 打印封装之后的长度 print(len(res1)) # 4 # 进行解封装 res2 = struct.unpack('i',res1)[0] print(res2) # 37
(4)解决TCP粘包基础版
例如:

import socket, struct, subprocess service = socket.socket() service.bind(('127.0.0.1',8080)) service.listen(6) while True: # 允许外部接入 con,add = service.accept() # 与客户端循环交互 while True: # 防止客户端退出 进行异常捕获 try: cmd = con.recv(1024) if len(cmd) == 0: break # 对方传来的是二进制 需要转换成字符串获取数据 cmd = cmd.decode('utf-8') # 从管道中获取数据 obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # 将数据返回给对方 res = obj.stderr.read() + obj.stdout.read() # 制作包头 以i模式 header = struct.pack('i',len(res)) # 发送包头 con.send(header) # 发送真实数据 con.send(res) except ConnectionResetError as e: print(e) break con.close()

import socket, struct client = socket.socket() client.connect(('127.0.0.1',8080)) while True: # 输入命令 转换成二进制传输 cmd = input('请输入命令>>:').strip().encode('utf-8') if len(cmd) == 0: continue # 发送数据 client.send(cmd) # 接收包头 header = client.recv(4) # 解封包头 获取内部数据 real_size = struct.unpack('i',header)[0] # 循环打印内部接收数据 # 定义初始接收值 recv_size = 0 # 真实数据大小 real_data = b'' while recv_size < real_size: # 每次初始接收1024 data = client.recv(1024) # 真实数据大小 real_data += data # 真实接收值 不能确定最终数据就是剩余1024 所以每次接收是接收值长度 recv_size += len(data) # 转码打印 print(real_data.decode('gbk'))
(5)解决TCP粘包问题进阶版
(1)在上述版本中 虽然将数据封装成固定的大小
(2)但是封装能力有限的 当数据特别大的时候 struct模块不能封装
(3)假如我想对数据进行一些描述 例如:电影 附带演员名单 以及影评等 上述版本就不能满足了
解决办法:
(1)将数据封装在字典内部
(2)字典可以对内部数据进行描述
例如:

import socket, struct, subprocess, json service = socket.socket() service.bind(('127.0.0.1', 8080)) service.listen(6) while True: # 允许外部接入 con, add = service.accept() # 与客户端循环交互 while True: # 防止客户端退出 进行异常捕获 try: cmd = con.recv(1024) if len(cmd) == 0: break # 对方传来的是二进制 需要转换成字符串获取数据 cmd = cmd.decode('utf-8') # 从管道中获取数据 obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 将数据返回给对方 res = obj.stderr.read() + obj.stdout.read() # 定义一个字典 info = {'name': 'SR', 'file_size': len(res)} # 将字典转成字符串 统计长度 json_dict = json.dumps(info) # 定义一个字典包头 header_dict = struct.pack('i', len(json_dict)) # 发送字典包头 con.send(header_dict) # 发送字典数据 以二进制发送 con.send(json_dict.encode('utf-8')) # 发送真实数据 con.send(res) except ConnectionResetError as e: print(e) break con.close()

import socket, struct, json client = socket.socket() client.connect(('127.0.0.1', 8080)) while True: # 输入命令 转换成二进制传输 cmd = input('请输入命令>>:').strip().encode('utf-8') if len(cmd) == 0: continue # 发送数据 client.send(cmd) # 接收字典包头 dict_header = client.recv(4) # 解封字典包头 获取字典 dict_size = struct.unpack('i', dict_header)[0] # 接收字典数据 dict_byte = client.recv(dict_size) # 对方传送的二进制 进行转码 以及序列化 json_dict = json.loads(dict_byte.decode('utf-8')) # 循环打印内部接收数据 # 定义初始接收值 recv_size = 0 # 真实数据大小 real_data = b'' while recv_size < json_dict.get('file_size'): # 每次初始接收1024 data = client.recv(1024) # 真实数据大小 real_data += data # 真实接收值 不能确定最终数据就是剩余1024 所以每次接收是接收值长度 recv_size += len(data) # 转码打印 print(real_data.decode('gbk'))
PS:
服务端:
(1)制作字典包头
(2)发送字典包头
(3)发送字典数据
(4)发送真实数据
客户端:
(1)接收字典包头
(2)解封字典包头
(3)接收字典数据
(4)接收真实数据
小练习:自定义一个上传电影文件
例如:

import socket, json, struct server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(6) while True: # 循环接收客户端请求 conn,addr = server.accept() # 循环与客户端建立请求 while True: # 异常捕获 try: # 接收字典包头 header_dict = conn.recv(4) # 解封字典包头 获取内部字典信息 dict_info = struct.unpack('i',header_dict)[0] # 获取字典数据 dict_data = conn.recv(dict_info) # 进行反序列化 并进行转码 json_dict = json.loads(dict_data.decode('utf-8')) # 初始接收值 recv_size = 0 # # 真实数据大小 # real_data = b'' with open(json_dict.get('movie_name'),'wb') as file: while recv_size < json_dict.get('movie_size'): data = conn.recv(1024) file.write(data) # real_data += data recv_size += len(data) print('接收成功!') except ConnectionResetError as e: print(e) break conn.close()

import socket, os, json, struct client = socket.socket() client.connect(('127.0.0.1', 8080)) while True: # 获取文件路径 MOVIE_PATH = r'C:\Users\SR\Desktop\test' # 获取该路径下文件列表 movie_list = os.listdir(MOVIE_PATH) # 将文件打印 供用户选择 for index, movies in enumerate(movie_list, 1): print(index, movies) # 用户进行选择 choice = input('请输入电影编号>>:').strip() # 判断是否输入数字 if not choice.isdigit(): print('请输入数字!') continue choice = int(choice) # 用户选择电影成功 movie_name = movie_list[choice - 1] # 拼接绝对路径寻找电影 movie_path = os.path.join(MOVIE_PATH, movie_name) # 获取文件大小 movie_size = os.path.getsize(movie_path) # 定义字典 movie_info = { 'movie_name':movie_name, 'movie_size':movie_size, 'movie_info':'豆瓣评分满分' } # 将字典进行序列化成字符串 转换成二进制进行传输 json_dict = json.dumps(movie_info).encode('utf-8') # 转换成二进制进行传输 # json_byte = json_dict.encode('utf-8') # 定义字典包头 header_dict = struct.pack('i',len(json_dict)) # 发送字典包头 client.send(header_dict) # 发送字典 client.send(json_dict) # 循环发送真实数据 with open(movie_path,'rb') as file: for line in file: client.send(line) print('上传成功!')
四:UDP#
(1)UDP基础套接字:
(1)UDP属于数据包协议(自带包头)
(2)socket默认封装TCP协议 需要手工修改socket类型
例如:

import socket # 启用UDP协议 server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1',8080)) # UDP没有半连接池概念 data,addr = server.recvfrom(1024) # 客户端发来的数据 print(data) # b'hello' # 客户端连接的地址 print(addr) # ('127.0.0.1', 57571) # 发送给客户端的数据与地址 server.sendto(data.upper(),addr)

import socket client = socket.socket(type=socket.SOCK_DGRAM) server_address = ('127.0.0.1',8080) # 发送给服务端数据以及地址 client.sendto(b'hello',server_address) # 服务端发送数据 以及地址 data,addr = client.recvfrom(1024) print('服务端发来的数据',data) # 服务端发来的数据 b'HELLO' print('服务端发送的地址',addr) # 服务端发送的地址 ('127.0.0.1', 8080)
(2)TCP与UDP的区别
(1)UDP允许发送数据为空
例如:

import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1',8080)) while True: data,addr = server.recvfrom(1024) server.sendto(data.upper(),addr) date = data.decode('utf-8') print(date)

import socket clint = socket.socket(type=socket.SOCK_DGRAM) ser_address = ('127.0.0.1',8080) while True: cmd = input('请输入指令>>:').strip().encode('utf-8') clint.sendto(cmd,ser_address) clint.recvfrom(1024)
(2)UDP没有半连接池概念
(3)UDP没有粘包问题
例如:

import socket server = socket.socket(type=socket.SOCK_DGRAM) server.bind(('127.0.0.1',8080)) data, addr = server.recvfrom(1024) print(data) data, addr1 = server.recvfrom(1024) print(data) data, addr2 = server.recvfrom(1024) print(data) ''' b'hello' b'hello' b'hello' '''

import socket clint = socket.socket(type=socket.SOCK_DGRAM) ser_address = ('127.0.0.1',8080) clint.sendto(b'hello',ser_address) clint.sendto(b'hello',ser_address) clint.sendto(b'hello',ser_address)
(4)UDP协议支持并发
(3)socketserver模块
作用:其含有一些内置方法
(1)TCP可以通过调用一些内置方法 可以让TCP可以像UDP似的 实现并发效果
例如:

import socketserver class MySocket(socketserver.BaseRequestHandler): def handle(self): print('接收客户端请求') if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MySocket) server.serve_forever()

import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: cmd = input('请输入>>:').strip()
PS:
(1)其会时时监听某些地址
(2)当有地址发来请求的时候 其会调用handle函数处理
(2)UDP:
(1)了解即可:

class MyServer(socketserver.BaseRequestHandler): def handle(self): # print('来啦 老弟') while True: data,sock = self.request print(self.client_address) # 客户端地址 print(data.decode('utf-8')) sock.sendto(data.upper(),self.client_address) if __name__ == '__main__': """只要有客户端连接 会自动交给自定义类中的handle方法去处理""" server = socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyServer) # 创建一个基于TCP的对象 server.serve_forever() # 启动该服务对象

import socket import time client = socket.socket(type=socket.SOCK_DGRAM) server_address = ('127.0.0.1',8080) while True: client.sendto(b'hello',server_address) data,addr = client.recvfrom(1024) print(data.decode('utf-8'),addr) time.sleep(1)
PS:UDP请求连接速度特别快 需要通过time.sleep降低请求速度
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!