socket 通信
TCP版本:
# 最终版本,解决了 TCP 协议中的粘包问题
# 客户端
import socket
import struct
import json
client = socket.socket() # 先生成一个客户端对象
client.connect(('127.0.0.1', 8080)) # 绑定服务端 # 里面接收一个值 - 元祖 元祖里面传两个值,分别为IP地址以及端口号
while True:
msg = input('>>> ').strip().encode('utf-8') # 首先输入命令,并且转化为字节
if len(msg) == 0:
continue # 如果字节长度为0 ,说明没有内容传入,就结束本次循环,重新输入指令
client.send(msg)
# 接收服务端传来的字典报头
d_head = client.recv(4)
# 解析字典报头,获取字典长度信息
d_len = struct.unpack('i', d_head)[0] # 解析报头必须后面添加索引[0]
# 接收字典数据,并反序列化字典
d_bytes_data = client.recv(d_len)
d = json.loads(d_bytes_data)
# 接收真实结果
real_data = b''
recv_size = 0
while recv_size < d['file_size']: # 字典d里面有真实结果的字节长度信息
data = client.recv(1024)
real_data += data # 拼接真实结果
recv_size += len(data) # 因为最后接收的不一定是1024,所以以每次接收的长度增值运算
print(real_data.decode('gbk')) # 因为windows终端默认使用的是GBK格式,所以以GBK解码
# 服务端
import socket
import subprocess
import json
import struct
server = socket.socket() # 先生成一个服务端对象
server.bind(('127.0.0.1', 8080)) # 里面接收一个值 - 元祖 元祖里面传两个值,分别为IP地址以及端口号 绑定自身IP
server.listen(5) # 半连接池
while True: # 连接循环
conn, addr = server.accept() # 服务器一直处于等待用户访问状态
while True: # 通信循环
try:
cmd = conn.recv(4) # 接收用户的传来的信息
if len(cmd) == 0:
break # 判断用户传来的指令是否为空,空的话不执行命令,继续等待新的命令
# 通过subprocess模块在终端执行用户传来的命令
obj = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
res = obj.stdout.read() + obj.stderr.read() # 接收执行命令后的结果内容(包括正确的信息和错误的信息)
d = {'file_size': len(res)} # 将结果内容的长度封在字典中,可以避免数字太大不能接收的情况
d_json = json.dumps(d) # 基于网络传输必须是二进制数据,所以要先序列化
# 要想传输字典,先告诉客户端字典的字节长度,所以先生成字典的报头,在传输字典,避免黏包问题
header = struct.pack('i', len(d))
conn.send(header) # 传送报头
conn.send(d_json) # 传送序列化之后的字典
conn.send(res) # 客户端得到字典后就可以获取真实结果的字节长度了,所以此时再将真实结果传送过去
except ConnectionResetError:
break # 报错则跳出通信循环
conn.close()
例子:用TCP协议往服务端上传一个本地文件
# 以上传本地视频为例 # 本地视频位置:r'C:\Users\赵帅平\Desktop\d
# 客户端
import os
import socket
import json
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8888))
while True:
# 循环打印电影列表
MOVIE_DIR = r'C:\Users\赵帅平\Desktop\day28\视频'
movie_list = os.listdir(MOVIE_DIR)
for index, movie in enumerate(movie_list, 1):
print(index, movie)
# 让用户进行选择
choice = input('please choose a movie to upload >>> ').strip()
if choice.isdigit():
choice = int(choice) - 1
if choice in range(0, len(movie_list)):
movie = movie_list[choice]
# 拼接要上传电影的绝对路径
movie_path = os.path.join(MOVIE_DIR, movie)
# 获取电影的大小
movie_size = os.path.getsize(movie_path)
# 定义一个字典,将电影大小添加进去
d = {
'file_name': '常山赵子龙.mp4', # 自己定义的名字,可以不定义
'movie_size': movie_size
}
# 序列化字典,并生成一个字典报头
d_json = json.dumps(d)
d_bytes = d_json.encode('utf-8')
header = struct.pack('i', len(d_bytes))
# 发送字典报头
client.send(header)
# 发送序列化后的字典
client.send(d_bytes)
# 服务端接收到字典后就可以获取要长传的电影字节大小信息,所以上传电影
with open(movie_path, 'rb') as f:
for line in f:
client.send(line)
# 等待客户端的返回结果
res = client.recv(17).decode('utf-8')
a = 'upload complete !'
if res == a:
print('%s 上传成功!' % d.get('file_name'))
else:
print('choice is out of movie_list, please try again...')
continue
else: print('choice must be number...')
# 服务端
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(5) # 半连接池
while True:
conn, addr = server.accept()
while True:
try:
# 接收字典报头
head = conn.recv(4)
# 解析字典报头,获取字典字节长度
d_len = struct.unpack('i', head)[0]
# 接收序列化后的字典
d_bytes = conn.recv(d_len)
# 反序列化字典
d = json.loads(d_bytes.decode('utf-8'))
# 循环写入
recv_size = 0
with open(d.get('file_name'), 'ab') as f:
while recv_size < d['movie_size']:
data = conn.recv(1024)
recv_size += len(data)
f.write(data)
print('%s 接收上传完毕!' % d.get('file_name'))
conn.send(b'upload complete !')
except ConnectionResetError as e:
print(e)
break
conn.close()
UDP版本:
# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
# UDP协议没有双向通道,所以不需要建立连接,直接进行通信循环
# 但需要指定要发送的地址:IP + port
server_address = ('127.0.0.1', 9001)
while True:
client.sendto(b'hello', server_address) # 发送两个参数,一个为消息内容,一个为地址信息 且用sendto 发送
data, server_addr = client.recvfrom(1024) # 接收两个值, 一个是服务端消息,一个是服务端地址
print('来自服务端的消息:', data.decode('utf-8')) # 来自服务端的消息: HELLO
print('服务端地址: ', server_addr) # 服务端地址: ('127.0.0.1', 9001)
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # ()内默认为TCP协议,传参以后就是UDP协议
server.bind(('127.0.0.1', 9001))
# UDP协议没有半连接池,所以不需要设置半连接池
# UDP协议没有双向通道,所以不需要accept()
# 直接进入通信循环
while True:
data, addr = server.recvfrom(1024) # 接收两个值,一个为客户端数据,一个是客户端IP地址+端口 # 注意是recvfrom
print(data.decode('utf-8')) # hello
print(addr) # ('127.0.0.1', 58709)
server.sendto(data.upper(), addr) # 发送时候也是需要发送两个值,一个是要发送的内容,一个是要发去的地址+端口 注意是sendto
例子:用UDP协议写一个简易的QQ聊天
# 仅是本机客户端与本机服务端之间的交互
# 客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 9990)
while True:
msg = input('>>>')
msg = '''这是来自客户端的消息:
%s''' % msg
client.sendto(msg.encode('utf-8'), server_address)
data, server_addr = client.recvfrom(1024)
print(data.decode('utf-8'))
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 9990))
while True:
data, addr = server.recvfrom(1024)
print(data.decode('utf-8'))
msg = input('>>>')
msg = '''这是来自服务端的消息:
%s''' % msg
server.sendto(msg.encode('utf-8'), addr)
socketserver的模块用法(了解):
TCP & UDP 协议使用socketserver模块
# 客户端用不着此模块,所以还是按照之前的正常编码
import socket
import time
# # TCP协议写法:
# client = socket.socket() # 括号内默认为TCP协议,不用指定
# client.connect(('127.0.0.1', 8888))
#
# while True:
# client.send(b'hello')
# data = client.recv(1024)
# print(data.decode('utf-8'))
# UDP协议写法:
client = socket.socket(type=socket.SOCK_DGRAM) # UDP协议里面括号内需要指定协议
server_address = ('127.0.0.1', 8888) # UDP协议不是基于通道通信,所以不用建立连接关系,直接定义IP地址以及port
while True:
client.sendto(b'hello', server_address) # UDP协议里面sendto相当于TCP协议里面的send
data, addr = client.recvfrom(1024) # UDP协议里面 recvfrom 相当于TCP协议里面的 recv 并且接收的是两个值:一个为数据,一个为服务端地址
print(data.decode('utf-8'))
print(addr)
time.sleep(1) # 因为太快,所以让CPU暂停一秒
# 服务端
import socketserver
# TCP协议服务端写法:
# class MyServer(socketserver.BaseRequestHandler): # 继承
# def handle(self): # 只能为handle
# while True:
# data = self.request.recv(1024) # 接收一个值 用request.recv接收
# print(self.client_address) # 客户端地址 内置有属性
# print(data.decode('utf-8'))
# self.request.send(data.upper()) # 传一个值
#
#
# if __name__ == '__main__':
# """只要有客户端连接 会自动交给自定义类中的handle方法去处理"""
# server = socketserver.ThreadingTCPServer(('127.0.0.1', 8888), MyServer)
# server.serve_forever() # 启动该服务对象
# UDP协议服务端写法:
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
while True:
data, sock = self.request # 用request,自带接收效果,同时接收两个值;一个为消息数据,一个类似于conn
print(data.decode('utf-8')) # 打印消息
print(self.client_address) # 内置有客户端的地址属性
sock.sendto(data.upper(), self.client_address) # 传两个值
if __name__ == '__main__':
"""只要有客户端连接 会自动交给自定义类中的handle方法去处理"""
server = socketserver.ThreadingUDPServer(('127.0.0.1', 8888), MyServer)
server.serve_forever() # 启动该服务对象