socket套接字简介
对于实现编写C\S架构的程序来实现数据交互的需求,我们需要编写代码对OSI七层进行操作。因为OSI七层是所有C\S架构的程序都必须要有的过程,所以就有相应的socket模块。而socket套接字只是一门技术,是底层原理,我们写代码的时候基本看不到。socket模块提供了一种简单的方式对OSI七层进行操作。
socket模块
对于C\S架构的程序,我们都应该先考虑服务端,也就是有了服务器,客户端才能进行访问。
服务端
| import socket |
| server = socket.socket() |
| """ |
| 通过查看源码得知 |
| 括号内不写参数默认就是基于网络的遵循TCP协议的套接字 |
| """ |
| server.bind(('127.0.0.1', 8080)) |
| """ |
| 服务端应该具备的特征 |
| 固定的地址 |
| ... |
| 127.0.0.1是计算机的本地回环地址 只有当前计算机本身可以访问 |
| """ |
| server.listen(5) |
| """ |
| 半连接池(暂且忽略 先直接写 后面讲) |
| """ |
| sock, addr = server.accept() |
| """ |
| listen和accept对应TCP三次握手服务端的两个状态 |
| """ |
| print(addr) |
| data = sock.recv(1024) |
| print(data.decode('utf8')) |
| sock.send('你好啊'.encode('utf8')) |
| """ |
| recv和send接收和发送的都是bytes类型的数据 |
| """ |
| sock.close() |
| server.close() |
客户端
| import socket |
| |
| |
| client = socket.socket() |
| client.connect(('127.0.0.1', 8080)) |
| |
| client.send(b'hello sweet heart!!!') |
| data = client.recv(1024) |
| print(data.decode('utf8')) |
| |
| client.close() |
服务端与客户端首次交互。一边是recv那么另一边必须是send 两边不能相同 否则就'冷战'了
通信循环
通信循环要解决的问题主要就是消息固定
并且只能进行一次交互
的问题
解决方式:用获取用户输入的方式解决消息固定的问题,用循环解决一次交互的问题
| |
| |
| import socket |
| |
| server = socket.socket() |
| server.bind(('127.0.1.1', 9090)) |
| server.listen(5) |
| sock, addr = server.accept() |
| while True: |
| data = sock.recv(1024) |
| print(data.decode('utf8')) |
| msg = input('请输入要回复的信息>>>:').strip() |
| sock.send(msg.encode('utf8')) |
| |
| |
| |
| import socket |
| |
| client = socket.socket() |
| client.connect(('127.0.1.1', 9090)) |
| while True: |
| msg = input('请输入要发送的信息:>>>').strip() |
| client.send(msg.encode('utf8')) |
| data = client.recv(1024) |
| print(data.decode('utf8')) |
| client.close() |
代码优化和链接循环
| 1.发送消息不能为空 |
| 统计长度并判断即可 |
| 2.反复重启服务端可能会报错>>>:address in use |
| 这个错在苹果电脑报的频繁 windows频率较少 |
| from socket import SOL_SOCKET,SO_REUSEADDR |
| server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) |
| 3.链接循环 |
| """ |
| 如果是windows 客户端异常退出之后服务端会直接报错 |
| 处理方式 |
| 异常处理 |
| 如果是mac或linux 服务端会接收到一个空消息 |
| 处理方式 |
| len判断 |
| """ |
| 客户端如果异常断开 服务端代码应该重新回到accept等待新的客人 |
| |
| |
半连接池
| listen(5) |
| |
| |
| |
| 设置的最大等待人数 >>>: 节省资源 提高效率 |
黏包问题
| data1 = conn.recv(1024) |
| print(data1) |
| data2 = conn.recv(1024) |
| print(data2) |
| data3 = conn.recv(1024) |
| print(data3) |
| |
| client.send(b'hello') |
| client.send(b'jason') |
| client.send(b'kevin') |
| """ |
| 三次打印的结果 |
| b'hellojasonkevin' |
| b'' |
| b'' |
| """ |
| |
| 会将数据量比较小并且时间间隔比较短的数据整合到一起发送 |
| 并且还会受制于recv括号内的数字大小(核心问题!!!) |
| 流式协议:跟水流一样不间断 |
| |
| """ |
| 问题产生的原因其实是因为recv括号内我们不知道即将要接收的数据到底多大 |
| 如果每次接收的数据我们都能够精确的知道它的大小 那么肯定不会出现黏包 |
| """ |
| |
| 思路: |
| 困扰我们的核心问题是不知道即将要接收的数据多大 |
| 如果能够精准的知道数据量多大 那么黏包问题就自动解决了!!! |
struct模块(黏包问题的解决方式)
| 方向:精准获取数据的大小 |
| |
| |
| import struct |
| |
| data1 = 'hello world!' |
| print(len(data1)) |
| res1 = struct.pack('i', len(data1)) |
| print(len(res1)) |
| ret1 = struct.unpack('i', res1) |
| print(ret1) |
| |
| |
| data2 = 'hello baby baby baby baby baby baby baby baby' |
| print(len(data2)) |
| res2 = struct.pack('i', len(data2)) |
| print(len(res2)) |
| ret2 = struct.unpack('i', res2) |
| print(ret2) |
| |
| |
| """ |
| pack可以将任意长度的数字打包成固定长度 |
| unpack可以将固定长度的数字解包成打包之前数据真实的长度 |
| |
| |
| 思路: |
| 1.先将真实数据打包成固定长度的包 |
| 2.将固定长度的包先发给对方 |
| 3.对方接收到包之后再解包获取真实数据长度 |
| 4.接收真实数据长度 |
| """ |
黏包问题的代码实现
| |
| import json |
| import socket |
| import struct |
| import os |
| |
| server = socket.socket() |
| server.bind(('127.0.0.1', 9090)) |
| server.listen(5) |
| while True: |
| sock, addr = server.accept() |
| while True: |
| data_dict = { |
| 'file_name': '演员.flac', |
| 'file_des': 'XXX', |
| 'file_size': os.path.getsize(r'C:\Users\Administrator\Music\薛之谦 - 演员.flac') |
| } |
| dict_json = json.dumps(data_dict) |
| dict_bytes = dict_json.encode('utf8') |
| dict_pack = struct.pack('i', len(dict_bytes)) |
| sock.send(dict_pack) |
| sock.send(dict_bytes) |
| with open(r'C:\Users\Administrator\Music\薛之谦 - 演员.flac', 'rb') as f: |
| for line in f: |
| sock.send(line) |
| |
| |
| import json |
| import socket |
| import struct |
| |
| client = socket.socket() |
| client.connect(('127.0.0.1', 9090)) |
| |
| dict_header_len = client.recv(4) |
| dict_real_len = struct.unpack('i', dict_header_len)[0] |
| dict_bytes = client.recv(dict_real_len) |
| dict_data = json.loads(dict_bytes) |
| print(dict_data) |
| recv_size = 0 |
| with open(dict_data.get('file_name'), 'wb') as f: |
| while recv_size < dict_data.get('file_size'): |
| data = client.recv(1024) |
| recv_size += len(data) |
| f.write(data) |
| |
| client.close() |
| |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人