Python学习笔记--网络通信--socket
1.socket里面的,AF_INET和AF_UNIX有什么区别?
- AF_INET用于真实的两台机器进行通信。
- AF_UNIX用于本地自己跟自己通信。
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()
- 效果: