基于TCP网络通信
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
简易版网络通信模拟xshell
服务端
# 服务端server
import subprocess
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 拿到一个socket对象
# 调用sock.setsockopt设置这个socket选项,本例中把socket.SO_REUSEADDR设置为1,表示服务器端进程终止后,
# 操作系统会为它绑定的端口保留一段时间,以防其他进程在它结束后抢占这个端口。
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1", 8080)) # 绑定唯一一个软件的地址
# 调用sock.listen通知系统开始侦听来自客户端的连接,参数是在队列中的最大连接数。
phone.listen(5)
print("starting...")
while True: # 服务器循环
conn,addr=phone.accept() # 卡在这里直到,客户端返回一个元组,里面包含了socket对象以及客户端的地址
# print("客户端对象", conn)
print("客户端的ip地址", addr)
while True: # 通信循环
try:
data = conn.recv(1024)
print("客户端发送的消息是", data)
# conn.send(data.upper())
res = subprocess.Popen(data.decode("gbk"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
data1 = res.stderr.read()
data2 = res.stdout.read()
conn.send(data1)
conn.send(data2)
except Exception:
break
conn.close() # 关闭客户端套接字对象
phone.close() # 关闭服务端套接字对象
客户端
# 客户端Client
import socket
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建客户端套接字对象
phone.connect(("127.0.0.1", 8080)) # 尝试连接服务器
while True: # 通讯循环,没有监听循环
msg = input(">>:").strip()
if not msg: continue
phone.send(msg.encode("utf-8"))
data2 = phone.recv(1024) # 指定从缓存中取数据的最大字节
print(data2.decode("gbk"))
phone.close() # 关闭客户端套接字对象
基于UDP网络通信
UDP服务端
# UDP通信之服务端
import socket
udpserver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# udpserver.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
udpserver.bind(("127.0.0.1",8080))
while True: # 通信循环
data, client_addr = udpserver.recvfrom(1024)
print(data.decode("utf-8")) # dada
print(client_addr) # ('127.0.0.1', 60167)
udpserver.sendto(data.upper(), client_addr)
UDP客户端
# UDP通信之客户端
import socket
udpclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_ip_port = ("127.0.0.1", 8080)
while True:
inp = input(">>:")
udpclient.sendto(inp.encode("utf-8"), server_ip_port)
data,server_addr = udpclient.recvfrom(1024)
print(data.decode("utf-8"))
粘包现象
只有TCP有粘包现象,UDP永远不会粘包
在上述简易通信模型里,在客户端执行ipconfig,在执行cls。发现并未一次取完ipconfig的执行结果,执行cls时,仍打印ipconfig的结果,那是因为ipconfig执行结果大于1024字节,而客户端收数据时,只收取1024个字节。切客户段和服务端都是从操作系统的缓存中拿数据。
粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
用json和struct解决方案
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
# 把数字转换成四个字节的bytes,数字须小于int最大长度
struct.pack('i',12345678) # b'Naxbcx00'
解决粘包
服务端
# 服务端(解决粘包问题)
import subprocess
import socket
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买手机
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1", 8080)) # 绑定手机卡
phone.listen(5)
print("starting...")
while True:
conn,addr=phone.accept() # c端连接成功返回一个元组,里面包含了socket对象以及客户端的地址
# print("电话线路是", conn)
print("客户端的手机号是", addr)
while True:
try:
data = conn.recv(1024)
print("客户端发送的消息是", data)
# conn.send(data.upper())
res = subprocess.Popen(data.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
data1 = res.stderr.read() # dos错误的运行结果
data2 = res.stdout.read() # dos正确的运行结果
data_size = len(data1)+len(data2) # 得到原始数据总大小
print(data_size)
data_dic = {"size": data_size} # 为避免粘包,自定制报头
data_json = json.dumps(data_dic) # 序列号报头
data_json_bytes = data_json.encode("utf-8") # 序列化并转成bytes,用于传输
print(data_json)
data_len = struct.pack("i", len(data_json_bytes)) # 打包得到报头长度
# part1: 先发送报头的长度
conn.send(data_len)
# part2: 发送报头
conn.send(data_json_bytes)
# part3: 发送原始数据
conn.send(data1)
conn.send(data2)
except Exception:
break
conn.close()
phone.close()
客户端
# 客户端(解决粘包问题)
import socket
import struct
import json
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(("127.0.0.1", 8080))
while True:
msg = input(">>:").strip()
if not msg: continue
phone.send(msg.encode("utf-8"))
head_dic_len = phone.recv(4) # part1: 接受报头的长度
head_dic_size = struct.unpack("i", head_dic_len)[0] # 取出报头长度
head_json = phone.recv(head_dic_size) # part2: 接受报头
head_dic = json.loads(head_json.decode("utf-8")) # 反序列化拿到报头数据
data_size = head_dic["size"] # 拿出原始数据大小
recv_size = 0
recv_data = b""
while data_size > recv_size:
data = phone.recv(1024) # part3: 接受原始数据
recv_size += len(data)
recv_data += data
print(recv_data.decode("gbk"))
phone.close()
socketserver实现并发
TCP服务端
# 基于socketserver并发TCP通信
# 服务端
import socketserver
class FtpServer(socketserver.BaseRequestHandler):
def handle(self):
# TCP下的request就是conn,客户端套接字
print(self.request) # return self.socket.accept()
while True:
data=self.request.recv(1024)
print(data.decode("utf-8"))
self.request.send(data.upper())
if __name__ == '__main__':
obj = socketserver.ThreadingTCPServer(("127.0.0.1",8080),FtpServer)
print(obj.server_address)
# print(obj.RequestHandlerClass)
# print(obj.socket)
obj.serve_forever() # 链接循环
TCP客户端
# 于socketserver并发TCP通信
# 客户端
import socket
server_addr = ("127.0.0.1",8080)
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(server_addr)
while True: # 通讯循环
inp = input(">>:")
phone.send(inp.encode("utf-8"))
data = phone.recv(1024)
print(data)
并发UDP服务端
# UDP通信并发
# 服务端
import socketserver
class UdpServer(socketserver.BaseRequestHandler):
def handle(self):
# UDP协议下的request=(rec_data, self.socket)
# data, client_addr = self.socket.recvfrom(self.max_packet_size)
# return (data, self.socket), client_addr
print(self.request[0]) # (data, self.socket)
self.request[1].sendto(self.request[0].upper(), self.client_address)
if __name__ == '__main__':
server_ip_port = ("127.0.0.1", 8080)
obj = socketserver.ThreadingUDPServer(server_ip_port, UdpServer)
print(obj.socket)
obj.serve_forever()
并发UDP客户端
# UDP通信并发
# 客户端
import socket
udpclient = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_ip_port = ("127.0.0.1", 8080)
while True:
inp = input(">>:")
udpclient.sendto(inp.encode("utf-8"), server_ip_port)
data,server_addr = udpclient.recvfrom(1024)
print(data.decode("utf-8"))
作业
简易FTP实现
服务端
# ftp上传和下载(面向对象)
# 服务端
import socket
import json
import os
import struct
class FtpServer:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_address = False
max_packet_size = 8192
coding = "utf-8"
request_queue_size = 5
server_dir = r"C:\Users\Zou\PycharmProjects\py_fullstack_s4\day37\粘包"
def __init__(self, server_address, bind_and_activate=True):
"""构造函数"""
self.server_address = server_address
self.phone = socket.socket(self.address_family,self.socket_type)
if bind_and_activate:
try:
self.server_bind()
self.server_activate()
except:
self.server_close()
raise
def server_bind(self):
"""绑定socket对象"""
if self.allow_reuse_address:
self.phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
self.phone.bind(self.server_address) # 绑定地址和端口号
self.server_address = self.phone.getsockname()
def server_activate(self):
"""服务端对象listen"""
self.phone.listen(self.request_queue_size)
def server_close(self):
"""关闭服务端socket对象"""
self.phone.close()
def get_request(self):
"""服务端开始监听accept"""
return self.phone.accept()
def close_request(self, request):
"""关闭客户socket对象"""
request.close()
def run(self):
while True: # 连接循环
self.conn, self.client_addr = self.get_request()
print("客户端地址:", self.client_addr)
while True: # 通信循环
try:
head_struct = self.conn.recv(4)
print(head_struct)
if not head_struct:break
head_len = struct.unpack("i",head_struct)[0] # 拿到报头长度
head_json = self.conn.recv(head_len).decode(self.coding) # 拿到json字符串
head_dic = json.loads(head_json)
print(head_dic) # 打印用户字典
# head_dic = {"cmd":"put", "file_name":"a.txt", "file_size":12345}
cmd = head_dic["cmd"]
if hasattr(self, cmd):
func = getattr(self,cmd)
func(head_dic)
except Exception:
break
def put(self, dic):
"""文件上传函数"""
file_path = os.path.normpath(os.path.join(
self.server_dir,
dic["file_name"]
))
file_size = dic["file_size"]
recv_size = 0
print("-------",file_path)
with open(file_path,"wb") as f:
while recv_size < file_size:
recv_data = self.conn.recv(self.max_packet_size)
f.write(recv_data)
recv_size += len(recv_data)
print("recvsize:%s filesize:%s"% (recv_size, file_size))
tcpserver1 = FtpServer(("127.0.0.1",8080))
tcpserver1.run()
客户端
# FTP客户端(面向对象)
import socket
import struct
import json
import os
class FtpClient:
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
allow_reuse_address = False
max_packet_size = 8192
coding = "utf-8"
request_queue_size = 5
def __init__(self, server_address, connect=True):
self.server_address = server_address
self.phone = socket.socket(self.address_family, self.socket_type)
if connect:
try:
self.client_connect()
except Exception:
self.client_close()
def client_connect(self):
self.phone.connect(self.server_address)
def client_close(self):
self.phone.close()
def run(self):
"""客户端主逻辑"""
while True:
inp = input(">>:").strip() # put a.txt
if not inp:continue
l = inp.split()
cmd = l[0]
if hasattr(self, cmd):
func = getattr(self, cmd)
func(l)
def put(self,l):
cmd = l[0]
filename = l[1]
if not os.path.isfile(filename):
print("%s文件不存在" % filename)
return
filesize = os.path.getsize(filename)
# head_dic = {"cmd":"put", "file_name":"a.txt", "file_size":12345}
head_dict = {"cmd":cmd, "file_name":filename, "file_size":filesize}
head_json = json.dumps(head_dict).encode(self.coding)
head_len = struct.pack("i", len(head_json))
self.phone.send(head_len)
self.phone.send(head_json)
send_size = 0
with open(filename, "rb") as f:
for line in f:
self.phone.send(line)
send_size += len(line)
print(send_size)
else:
print("文件上传成功")
client = FtpClient(("127.0.0.1",8080))
client.run()