Python学习笔记--网络通信--socket
1.socket里面的,AF_INET和AF_UNIX有什么区别?
- AF_INET用于真实的两台机器进行通信。
- AF_UNIX用于本地自己跟自己通信。
- 参考资料:http://www.langdebuqing.com/
2.socket 通信示例?
- 服务端:
- 代码:
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互 # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP print(phone) # <socket.socket fd=436, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0> # 2.绑定电话卡 phone.bind(("127.0.0.1",8080)) # 绑定ip和端口 # 为了让客户端访问,这里一般固定后就不变了. # 3.开机 phone.listen(5) # __backlog = 5 半连接池数量为5 # 4.接受链接信息 conn,client_addr = phone.accept() # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址. print(conn) #<socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, # laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 1873)> print(client_addr) # ('127.0.0.1', 1873) # 5.收发消息 data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型 print(data) # b'hello' conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。 # 6.挂电话 conn.close() # 7.关机 phone.close()
- 客户端:
- 代码:
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互 # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP # 2. 打电话 phone.connect(("127.0.0.1",8080)) # 3.发,收数据 phone.send("hello".encode("utf-8")) # phone.send(b"hello") # 这样也可以 data = phone.recv(1024) # 接收数据 同样设置为1024 print(data.decode("utf-8")) # 返回的也是byte类型,需要解码 # 4.关机 phone.close()
3.socket通信,升级--加上循环。
- 服务端:
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.绑定电话卡 phone.bind(("127.0.0.1",8080)) # 3.开机 phone.listen(5) # 4.接受链接信息 conn,client_addr = phone.accept() # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址. # 5.收发消息 while True: data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型 print(data) # b'hello' conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。 # 6.挂电话 conn.close() # 7.关机 phone.close()
-
- 客户端:
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # AF_INET和AF_UNIX区别,AF_INET远程交互,AF_UNIX本地交互 # SOCK_STREAM 流式协议 = TCP ; SOCK_DGRAM 数据报协议 = UDP # 2. 打电话 phone.connect(("127.0.0.1",8080)) # 3.发,收数据 # 建立循环 while True: msg = input("请输入信息:") phone.send(msg.encode("utf-8")) data = phone.recv(1024) # 接收数据 同样设置为1024 print(data.decode("utf-8")) # 返回的也是byte类型,需要解码 # 4.关机 phone.close()
-
4.socket通信--升级2--解决客户端发空报错。
- 服务端代码不动:
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.绑定电话卡 phone.bind(("127.0.0.1",8080)) # 3.开机 phone.listen(5) # 4.接受链接信息 conn,client_addr = phone.accept() # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址. # 5.收发消息 while True: data = conn.recv(1024) # 最大接收字节数 1024 ; 注意 这里的data是byte类型 print(data) # b'hello' conn.send(data.upper()) # 回消息 ,这里为了演示,把data的大写返回去。 # 6.挂电话 conn.close() # 7.关机 phone.close()
-
- 客户端--增加为空判断
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 打电话 phone.connect(("127.0.0.1", 8080)) # 3.发,收数据 # 建立循环 while True: msg = input("请输入信息:") if len(msg) == 0: continue # 跳过本次 phone.send(msg.encode("utf-8")) # print("1111111111") 调试1 data = phone.recv(1024) # 接收数据 # 发空消息,会阻塞这里 # print("222222222") 调试2 print(data.decode("utf-8")) # 4.关机 phone.close()
-
5.服务端,应该具备的能力是?
- 一直提供服务
- 并发的能力
6.socket通信--升级3--解决服务端一直接受“空”问题。
- 服务端:
- 增加长度判断
- 代码:
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.绑定电话卡 phone.bind(("127.0.0.1",8080)) # 3.开机 phone.listen(5) # 4.接受链接信息 conn,client_addr = phone.accept() # 返回两个值. conn是链接通路,是三次握手的成果;client_addr是客户端的地址. # 5.收发消息 while True: try: # win 其他版本, 会报错 data = conn.recv(1024) if len(data) == 0: # linux win11 会收到空 b'' break print(data) # b'hello' conn.send(data.upper()) except Exception: break # 6.挂电话 conn.close() # 7.关机 phone.close()
- 客户端:
-
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 打电话 phone.connect(("127.0.0.1", 8080)) # 3.发,收数据 # 建立循环 while True: msg = input("请输入信息:") if len(msg) == 0: continue # 跳过本次 phone.send(msg.encode("utf-8")) # print("1111111111") 调试1 data = phone.recv(1024) # 接收数据 # 发空消息,会阻塞这里 # print("222222222") 调试2 print(data.decode("utf-8")) # 4.关机 phone.close()
-
7.socket通信--升级4--解决服务端只能提供一次服务。
- 想让服务端一直提供服务。
- 服务端:
- 增加循环。
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.绑定电话卡 phone.bind(("127.0.0.1", 8080)) # 3.开机 phone.listen(5) # 4.接受链接信息 # 循环执行 while True: conn, client_addr = phone.accept() # 5.收发消息 while True: try: # win 其他版本, 会报错 data = conn.recv(1024) if len(data) == 0: # linux win11 会收到空 b'' break print(data) # b'hello' conn.send(data.upper()) except Exception: break # 6.挂电话 conn.close() # 7.关机 phone.close()
- 客户端:
- 代码:
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 打电话 phone.connect(("127.0.0.1", 8080)) # 3.发,收数据 # 建立循环 while True: msg = input("请输入信息:") if len(msg) == 0: continue # 跳过本次 phone.send(msg.encode("utf-8")) # print("1111111111") 调试1 data = phone.recv(1024) # 接收数据 # 发空消息,会阻塞这里 # print("222222222") 调试2 print(data.decode("utf-8")) # 4.关机 phone.close()
8.如何写一个执行远程命令的C/S程序?
- 服务端--调用subprocess来执行
-
import socket import subprocess # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2.绑定电话卡 phone.bind(("127.0.0.1", 8080)) # 3.开机 phone.listen(5) # 4.接受链接信息 # 循环执行 while True: conn, client_addr = phone.accept() # 5.收发消息 while True: try: # win 其他版本, 会报错 cmd = conn.recv(1024) if len(cmd) == 0: # linux win11 会收到空 b'' break cmd = cmd.decode("utf-8") # 先解码 # 再执行 obj = subprocess.Popen(cmd,shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE ) res1 = obj.stdout.read() res2 = obj.stderr.read() conn.send(res1+res2) # 把两个值都返回去 except Exception: break # 6.挂电话 conn.close() # 7.关机 phone.close()
- 客户端--注意解码方式(win用gbk,linux用utf-8)
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 打电话 phone.connect(("127.0.0.1", 8080)) # 3.发,收数据 # 建立循环 while True: msg = input("请输入信息:") if len(msg) == 0: continue # 跳过本次 phone.send(msg.encode("utf-8")) data = phone.recv(1024) # 接收数据 # 发空消息,会阻塞这里 print(data.decode("gbk")) # 在win解码用gbk # 4.关机 phone.close() # !!!注意,这里输入tasklist会出现不全。输入dir可以展示
- 问题1,是输入tasklist会出现显示不全的现象。
- 问题2,是输入tasklist后再输入dir会发现显示的上次剩余的内容。(这可以帮助理解【流协议】)
- 我的解决:可以尝试recv(1024) -->recv(更大的数字)
9.梳理--服务端
- ①实例化服务
- ②绑定ip+port
- ③建立监听池
- ④连接循环(接收连接)(类比,饭店迎宾)
- ⑤通信循环(收发数据) (类比,服务员)
- ⑥通信结束+连接结束。
- ⑦补充通信连接的异常。
- 示例代码(演示):
-
from socket import * server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) server.listen(5) while True: # 链接循环 conn, clint_addr = server.accept() print("客户端的地址") while True: # 通信循环 data = conn.recv(1024) # 收 conn.send(回复的数据) # 关闭通信 conn.close() # 整体关机 server.close()
- 运行代码:
-
from socket import * server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) server.listen(5) # 链接循环(接收) while True: conn, client_addr = server.accept() # 通信循环 while True: try: data = conn.recv(1024) # 收 conn.send(数据) # 发 except Exception: break # 结束通信 conn.close() # 结束链接 server.close() # 补充异常(为了简洁省略了win7 win10 的data为0的判断)
10.梳理--客户端
- ①实例化客户端
- ②连接远程ip+port
- ③通信循环
- ④补充异常输入为空的判断
- 代码:
-
from socket import * # 实例化客户端 client = socket(AF_INET, SOCK_STREAM) # 连接远程的ip+port client.connect(('127.0.0.1', 8080)) # 通信循环 while True: cmd = input('>>>').strip() if len(cmd) == 0: continue client.send(cmd.encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8')) # 补充异常输入为空的判断: 在10,11行体现
11.梳理--服务器--增加执行命令功能
- ①导入subprocess模块的Popen,PIPE
- ②实例化Popen,shell打开,管道打开
- ③从返回值里读取结果。
- 代码
from socket import * from subprocess import PIPE, Popen # *====增加执行命令的功能======* server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) server.listen(5) # 链接循环(接收) while True: conn, client_addr = server.accept() # 通信循环 while True: try: data = conn.recv(1024) # 收 # *====增加执行命令的功能======* cmd = data.decode("utf-8") obj = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) res_out = obj.stdout.read() # 读出管道的输出 res_err = obj.stderr.read() # 读出管道的错误输出 # 因为这里res_out是byte类型,所以可以直接发送 # *==========================* conn.send(res_out+res_err) # 发 byte类型 except Exception: break # 结束通信 conn.close() # 结束链接 server.close() # 补充异常(为了简洁省略了win7 win10 的为0的判断)
- 客户端补充一行解码格式为gbk
-
from socket import * # 实例化客户端 client = socket(AF_INET, SOCK_STREAM) # 连接远程的ip+port client.connect(('127.0.0.1', 8080)) # 通信循环 while True: cmd = input('>>>').strip() if len(cmd) == 0: continue client.send(cmd.encode('utf-8')) data = client.recv(1024) print(data.decode('gbk')) # *===执行命令的机器是win,改编码为gbk======* # 补充异常输入为空的判断: 在10,11行体现
12.解决粘包--基础
- 服务端:
-
from socket import * from subprocess import PIPE, Popen # *====增加执行命令的功能======* import struct # 导入把数据包变为固定长度的模块 server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) server.listen(5) # 链接循环(接收) while True: conn, client_addr = server.accept() print("对方的ip地址为:", client_addr) # 通信循环 while True: try: data = conn.recv(1024) # 收 if len(data) == 0: continue # *====增加执行命令的功能======* cmd = data.decode("utf-8") obj = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) res_out = obj.stdout.read() # 读出管道的输出 res_err = obj.stderr.read() # 读出管道的错误输出 # 因为这里res_out是byte类型,所以可以直接发送 # *==========================* res_size = len(res_out) + len(res_err) print("长度为:", res_size) # ①先发数据头,包含数据长度 header = struct.pack("i", res_size) conn.send(header) # ②再发数据 conn.send(res_out) # 发 byte类型 conn.send(res_err) except Exception: break # 结束通信 conn.close() # 结束链接 server.close() # 补充异常(为了简洁省略了win7 win10 的为0的判断)
- 客户端:
-
from socket import * import struct # 实例化客户端 client = socket(AF_INET, SOCK_STREAM) # 连接远程的ip+port client.connect(('127.0.0.1', 8080)) # 通信循环 while True: cmd = input('>>>').strip() if len(cmd) == 0: continue client.send(cmd.encode('utf-8')) # *===增加数据头的接收======* header = client.recv(4) total_size = struct.unpack("i",header)[0] print("客户端接收到的header为:",total_size) #(437,),这里要取[0] print("="*50) # *===增加数据长度的判断======* # total_size = 437 # 假设总大小为total_size recv_size = 0 res = b'' while recv_size < total_size: data = client.recv(1024) recv_size += len(data) res += data # 累加到res res = res.decode("gbk") # 再统一解码gbk print(res) # *=======================* # 补充异常输入为空的判断: 在10,11行体现
13.解决粘包--升级(终极版)
- 服务端:
from socket import * from subprocess import PIPE, Popen # *====增加执行命令的功能======* import struct # 导入把数据包变为固定长度的模块 import json server = socket(AF_INET, SOCK_STREAM) server.bind(('127.0.0.1', 8080)) # 根据需要修改ip server.listen(5) # 链接循环(接收) while True: conn, client_addr = server.accept() print("对方的ip地址为:", client_addr) # 通信循环 while True: try: data = conn.recv(1024) # 收 if len(data) == 0: continue # *====增加执行命令的功能======* cmd = data.decode("utf-8") obj = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) res_out = obj.stdout.read() # 读出管道的输出 res_err = obj.stderr.read() # 读出管道的错误输出 # print(res_out.decode("gbk")) # *==========================* # 计算总长度 total_size = len(res_out) + len(res_err) # ①先构建header_dic header_dic = { "filename": "a.txt", "total_size": total_size, "md5": "123456789" } header_json = json.dumps(header_dic) # 转换为json header_byte = header_json.encode("utf-8") # 转换为byte header_len = len(header_byte) # 求出长度 header_pack = struct.pack("i", header_len) # 压成包,发送 # ②先发4字节 conn.send(header_pack) # ③再发送header_byte conn.send(header_byte) # ④最后发送真正的数据 conn.send(res_out) conn.send(res_err) except Exception: break # 结束通信 conn.close() # 结束链接 server.close() # 补充异常(为了简洁省略了win7 win10 的为0的判断)
- 客户端
import json from socket import * import struct # 实例化客户端 client = socket(AF_INET, SOCK_STREAM) # 连接远程的ip+port client.connect(('127.0.0.1', 8080))# 这里根据需要修改 # 通信循环 while True: cmd = input('>>>').strip() if len(cmd) == 0: continue client.send(cmd.encode('utf-8')) # *===增加数据头的接收======* # ①接收对面的4字节 header = client.recv(4) # ②解包 header_size = struct.unpack("i",header)[0] # # ③接收header_byte header_byte = client.recv(header_size) # ④解码为json header_json = header_byte.decode("utf-8") # ⑤转换为python的字典 header_dic = json.loads(header_json) # ⑥拿到字典里的total_size total_size = header_dic["total_size"] # ⑦循环拿取数据 recv_size = 0 res = b'' while recv_size < total_size: data = client.recv(1024) recv_size += len(data) res += data # 累加到res res = res.decode("gbk") # 再统一解码gbk,linux就用utf-8 print(res) # *=======================*
14.后台运行?
- python3 xxx.py &
15.查看端口状态
-
netstat - an | grep 8080
16.什么是粘包问题?
- 主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
- 简单来讲就是【界限不清楚】
17.为何tcp是可靠传输,udp是不可靠传输?
- tcp会发送一个ack=1来确认消息。而udp不会。
- 所以,基于这一点,我们认为tcp是可靠的。
- 也就是说,这里的可靠与不可靠的界限是,是否有确认消息的过程。
18.什么是并发,什么是并行?
- 4个服务员,并行数最大是4个,并发N个
19.什么时候用到struct模块?
- 数据需要压缩为定长的时候,用到struct模块
- 代码:
-
import struct # 压缩数据 res = struct.pack("i",1234567) print(res) # b'\x87\xd6\x12\x00' # 解压数据 data = struct.unpack("i",res) print(data) # (1234567,)
-
20.UDP实现方式?示例代码。
- 服务端:
-
from socket import * server = socket(AF_INET,SOCK_DGRAM) server.bind(('127.0.0.1',9999)) # 通信 while True: res = server.recvfrom(1024) print(res) # 结果是 (b'123', ('127.0.0.1', 62180))
- 客户端
-
from socket import * client = socket(AF_INET,SOCK_DGRAM) # 通信 while True: msg = input(">>>") # 输入 123 测试一下 client.sendto(msg.encode("utf-8"),('127.0.0.1',9999))
21.UDP协议--如何实现收发消息?
- 服务端
-
from socket import * server = socket(AF_INET,SOCK_DGRAM) server.bind(('127.0.0.1',9999)) # 通信 while True: res,addr = server.recvfrom(1024) print("接受到的消息为:",res) # 回消息 server.sendto(res.upper(),addr)
- 客户端
-
from socket import * client = socket(AF_INET, SOCK_DGRAM) # 通信 while True: msg = input(">>>") # 输入 123 测试一下 client.sendto(msg.encode("utf-8"), ('127.0.0.1', 9999)) res, addr = client.recvfrom(1024) print("客户端收到消息:", res.decode("utf-8"))
22.UDP需要处理空数据吗?
- 不需要。
- UDP传输数据,是以数据报的形式传递。
- 数据报的意思是,每次传输数据都会带上自己的IP信息。
- 也就是说,每次数据内容可以是空,但数据头永远有信息。
- 所以,也就是说,不用特殊处理空数据的情况。
23. socektserver模块实现并发?
- 服务端--改造
-
import socketserver # 导入socketserver # 第一步:处理通信循环 class MyRequestHandler(socketserver.BaseRequestHandler): def handle(self) -> None: # 下面代码借用原来的,把conn改成self.request # socketserver把conn封装了成了对象 while True: try: data = self.request.recv(1024) # 改 if len(data) == 0: break print(data) self.request.send(data.upper()) # 改 except Exception: break self.request.close() # 改 # 第二步:处理连接循环 # IO密集型,推荐用线程(后续会讲为什么) server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MyRequestHandler, bind_and_activate=True) # bind_and_activate 意思是绑定并监听 server.serve_forever() # 一直提供服务
- 客户端--不用动
-
import socket # 1.买手机 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 打电话 phone.connect(("127.0.0.1", 8080)) # 3.发,收数据 # 建立循环 while True: msg = input("请输入信息:") if len(msg) == 0: continue # 跳过本次 phone.send(msg.encode("utf-8")) data = phone.recv(1024) # 接收数据 # 发空消息,会阻塞这里 print(data.decode("utf-8")) # 4.关机 phone.close()
- 效果:
参考资料:https://www.cnblogs.com/linhaifeng/articles/6129246.html#_label9
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!