python socket
名称 | 版本 |
---|---|
python | 3.11 |
本文涉及到socket的server与client通讯从简单到复杂的实现,以及一个server接收多个client,和多个server接收同一个client的案例。
socket中server和client通讯最基础的关系如下图:
1. 初级实现
server
端:server.py
import socket
import time
localhost = '127.0.0.1'
Port = 1234
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((localhost, Port))
s.listen(1)
HEADERSIZE = 10
while True:
clientsocket, address = s.accept()
print(f"Connection from {address} has been established!")
clientsocket.send(bytes("Welcome to the server!", "utf-8"))
clientsocket.close()
client
端:client.py
import socket
import time
localhost = '127.0.0.1'
Port = 1234
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((localhost, Port))
full_msg = ''
while True:
msg = s.recv(8)
if len(msg) <= 0:
break
full_msg += msg.decode("utf-8")
print(full_msg)
2. 添加header
header
可反映message
的数据长度,数据类型,协议版本等,用于后续合理规划资源。
server
端:server.py
首先,向client端发送 “Welcome to the server!”
随后,每3秒发送 “The time is! xxx”
import socket
import time
localhost = '127.0.0.1'
Port = 1234
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((localhost, Port))
s.listen(1)
HEADERSIZE = 10
while True:
clientsocket, address = s.accept()
print(f"Connection from {address} has been established!")
# send msg to client + header('HEADERSIZE' spaces)!
msg = "Welcome to the server!"
msg = f'{len(msg):<{HEADERSIZE}}' + msg
clientsocket.send(bytes(msg, "utf-8"))
while True:
time.sleep(3)
msg = f" The time is! {time.time()}"
msg = f'{len(msg):<{HEADERSIZE}}' + msg
clientsocket.send(bytes(msg, "utf-8"))
client
端:client.py
import socket
import time
localhost = '127.0.0.1'
Port = 1234
HEADERSIZE = 10
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((localhost, Port))
while True:
full_msg = ''
new_msg = True
while True:
msg = s.recv(16)
# receive a msg w/ header
if new_msg:
print(f"new msg length: {msg[:HEADERSIZE]}")
msglen = int(msg[:HEADERSIZE])
new_msg = False
full_msg += msg.decode("utf-8")
if len(full_msg) - HEADERSIZE == msglen:
print("full msg recvd")
print(full_msg[HEADERSIZE:])
new_msg = True
full_msg = ''
3. 中级实现(引用pickle库)
Python的pickle
库是用于序列化(将对象转换为字节流)和反序列化(将字节流转换为对象)Python对象的标准库。它允许将复杂的Python对象转换为字节流,以便在不同的环境中存储、传输或重新创建对象。
server
端:server.py
import pickle
localhost = '127.0.0.1'
Port = 1234
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((localhost, Port))
s.listen(1)
HEADERSIZE = 10
while True:
clientsocket, address = s.accept()
print(f"Connection from {address} has been established!")
d = {1: "Hey", 2: "There"}
msg = pickle.dumps(d)
# print(msg)
msg = bytes(f'{len(msg):<{HEADERSIZE}}', "utf-8") + msg
clientsocket.send(msg)
client
端:client.py
import pickle
localhost = '127.0.0.1'
Port = 1234
HEADERSIZE = 10
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((localhost, Port))
while True:
full_msg = b'' #b for bytes
new_msg = True
while True:
msg = s.recv(16)
# receive a msg w/ header
if new_msg:
print(f"new msg length: {msg[:HEADERSIZE]}")
msglen = int(msg[:HEADERSIZE])
new_msg = False
full_msg += msg
if len(full_msg) - HEADERSIZE == msglen:
print("full msg recvd")
# print(full_msg[HEADERSIZE:]) # before decode
d = pickle.loads(full_msg[HEADERSIZE:])
print(d) # after decode
new_msg = True
full_msg = b''
4. 高级实现(相互发送信息)
以上代码都是server向client发送信息。
实际上,在 Socket 通信中,server和client是彼此之间的消息接收者和发送者。它们之间可以相互发送消息,实现双向通信。
server
端:server.py
import select
HEADER_LENGTH = 10
IP = '127.0.0.1'
PORT = 1234
# 创建一个TCP/IP套接字对象。socket.AF_INET表示使用IPv4地址族,socket.SOCK_STREAM表示使用流式套接字。
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置套接字选项,socket.SO_REUSEADDR:表示允许重用本地地址和端口。1:表示启用SO_REUSEADDR选项。
server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1)
server_socket.bind((IP, PORT))
server_socket.listen()
sockets_list = [server_socket]
clients = {}
print(f'Listening for connections on {IP}:{PORT}...')
def receive_message(client_socket):
# 该函数用于:按消息长度来接收信息,有效避免信息阻塞等问题
try:
# 接收Header of msgs(the header contains message of length)
message_header = client_socket.recv(HEADER_LENGTH)
# header为0 -》接收不到信息就关闭
if not len(message_header):
return False
# 处理方法 和 client 发送的信息格式有关
# 解析头部信息:strip()方法去除字符串两端的空白字符,然后转换Header -> int
message_length = int(message_header.decode('utf-8').strip())
print(message_length)
# 返回破解后的msg
return {'header':message_header, 'data': client_socket.recv(message_length)}
except:
# 接收不到信息就关闭
return False
while True:
# 使用select.select()函数来监视sockets_list中的套接字,并返回准备就绪的套接字列表read_sockets(包含server_socket)和 _不在意的列表 和 exception_sockets异常的列表。
read_sockets, _, exception_sockets = select.select(sockets_list, [], sockets_list)
# 挨个处理准备就绪的列表
for notified_socket in read_sockets:
# 当遇到 server_socket -》 处理新的客户端连接
if notified_socket == server_socket:
client_socket, client_address = server_socket.accept()
user = receive_message(client_socket) # user 主要是 msg信息{’header','data'}
if user is False:
continue
# 添加已接收的socket至 sockets_list
sockets_list.append(client_socket)
# 添加:notified_socker的用户到一个clients字典
clients[client_socket] = user # key-value
print("Accepted new connection from {}:{}, username:{}".format(*client_address, user['data'].decode('utf-8')))
else:
# 处理已连接的客户端套接字:进一步删选
message = receive_message(notified_socket)
if message is False:
print("Close connection from: {}".format(clients[notified_socket]['data'].decode('utf-8')))
# 从sockets_list中移除有问题的notified_socket
sockets_list.remove(notified_socket)
# 删除
del clients[notified_socket]
continue
# 处理指定socket的信息
user = clients[notified_socket]
print(f'Received message from {user["data"].decode("utf-8")}: {message["data"].decode("utf-8")}')
# 给client反馈
for client_socket in clients:
if client_socket != notified_socket:
client_socket.send(user["header"] + user["data"] + message["header"] + message["data"])
# 异常socket处理:删除
for notified_socket in exception_sockets:
sockets_list.remove(notified_socket)
del clients[notified_socket]
client
端:client.py
import select
import errno
import sys
HEADER_LENGTH = 10
IP = '127.0.0.1'
PORT = 1234
my_username = input("Username: ") #输入用户名
#创建一个socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((IP, PORT))
# 将套接字设置为非阻塞模式,以实现非阻塞式的网络通信。
client_socket.setblocking(False)
username = my_username.encode('utf-8')
username_header = f"{len(username):<{HEADER_LENGTH}}".encode("utf-8")
client_socket.send(username_header + username)
while True:
# 输入用户要发送的信息msg
message = input(f"my_username > ")
if message:
# 发送msg -> server
message = message.encode("utf-8")
message_header = f"{len(message):<{HEADER_LENGTH}}".encode("utf-8")
client_socket.send(message_header + message)
try:
while True:
# 接收从server处发来的msg
username_header = client_socket.recv(HEADER_LENGTH)
if not len(username_header):
print("Connection closed by the server")
sys.exit()
username_length = int(username_header.decode("utf-8").strip())
username = client_socket.recv(username_length).decode("utf-8")
message_header = client_socket.recv(HEADER_LENGTH)
message_length = int(message_header.decode("utf-8").strip())
message = client_socket.recv(message_length).decode("utf-8")
print(f'{username} > {message}')
except IOError as e:
if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
print("Reading error: {}".format(str(e)))
sys.exit()
continue
except Exception as e:
print("Reading error: ".format(str(e)))
sys.exit()
server端 | client端 |
---|---|
![]() | ![]() |
5. 一点尝试
1个server - 2个client 与 2个server - 1个client 非常相似,都要用到 threading
多线程,都需要两套IP(IP要选择电脑防火墙可以通过的IP)和Port,这可以理解为由于socket通讯是双向的。在简单的应用场景中,没有太大区别。
5. 1个server对应2个client
用了一个IP和2个不同的Port。
server
端:server.py
import socket
import threading
# 定义端口号
PORT1 = 1234
PORT2 = 5678
# 处理客户端连接的函数
def handle_client(client_socket):
while True:
# 接收客户端发送的数据
data = client_socket.recv(1024)
if not data:
break
# 处理接收到的数据
# ...
# 发送响应给客户端
response = "Server response"
client_socket.send(response.encode())
# 关闭客户端连接
client_socket.close()
# 创建套接字并绑定到两个端口上
localhost = '127.0.0.1'
server_socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket1.bind((localhost, PORT1))
server_socket1.listen(1)
server_socket2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket2.bind((localhost, PORT2))
server_socket2.listen(1)
print(f"Server is listening on port {PORT1} and {PORT2}...")
# 处理客户端连接的函数
def accept_connections(server_socket):
while True:
# 等待客户端连接
client_socket, client_address = server_socket.accept()
print(f"New connection from {client_address[0]}:{client_address[1]}")
# 创建线程处理客户端连接
client_thread = threading.Thread(target=handle_client, args=(client_socket,))
client_thread.start()
# 创建线程分别处理两个端口的连接
thread1 = threading.Thread(target=accept_connections, args=(server_socket1,))
thread1.start()
thread2 = threading.Thread(target=accept_connections, args=(server_socket2,))
thread2.start()
client
端:client.py
import socket
# 定义服务器的IP和端口号
SERVER_IP = '127.0.0.1'
SERVER_PORT1 = 1234
SERVER_PORT2 = 5678
# 创建第一个客户端套接字并连接到服务器
client_socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket1.connect((SERVER_IP, SERVER_PORT1))
# 发送数据给服务器
message1 = "Hello from client 1"
client_socket1.send(message1.encode())
# 接收服务器的响应
response1 = client_socket1.recv(1024)
print(f"Response from server: {response1.decode()}")
# 关闭客户端套接字
client_socket1.close()
# 创建第二个客户端套接字并连接到服务器
client_socket2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket2.connect((SERVER_IP, SERVER_PORT2))
# 发送数据给服务器
message2 = "Hello from client 2"
client_socket2.send(message2.encode())
# 接收服务器的响应
response2 = client_socket2.recv(1024)
print(f"Response from server: {response2.decode()}")
# 关闭客户端套接字
client_socket2.close()
5.2个server对应1个client
用了2个不同的IP和Port。
server
端:server.py
import socket
import threading
# 定义端口号
PORT1 = 1234
PORT2 = 5678
localhost1 = "127.0.0.1"
localhost2 = "192.168.1.22"
# 处理客户端连接的函数
def handle_client(client_socket):
while True:
# 接收客户端发送的数据
data = client_socket.recv(1024)
if not data:
break
# 处理接收到的数据
# ...
# 发送响应给客户端
response = f"{client_socket} : Server response"
client_socket.send(response.encode())
# 关闭客户端连接
client_socket.close()
# 创建套接字并绑定到两个端口上
server_socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket1.bind((localhost1, PORT1))
server_socket1.listen(1)
server_socket2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket2.bind((localhost2, PORT2))
server_socket2.listen(1)
print(f"Server is listening {localhost1} on port {PORT1} and {localhost2} on port {PORT2}...")
# 处理客户端连接的函数
def accept_connections(server_socket):
while True:
# 等待客户端连接
client_socket, client_address = server_socket.accept()
print(f"New connection from {client_address[0]}:{client_address[1]}")
# 创建线程处理客户端连接
client_thread = threading.Thread(target=handle_client, args=(client_socket,))
client_thread.start()
# 创建线程分别处理两个端口的连接
thread1 = threading.Thread(target=accept_connections, args=(server_socket1,))
thread1.start()
thread2 = threading.Thread(target=accept_connections, args=(server_socket2,))
thread2.start()
client
端:client.py
import socket
# 定义服务器的IP和端口号
localhost1 = "127.0.0.1"
localhost2 = "192.168.1.22"
SERVER_IP1 = localhost1
SERVER_PORT1 = 1234
SERVER_IP2 = localhost2
SERVER_PORT2 = 5678
# 创建第一个服务器的套接字并连接
server_socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket1.connect((SERVER_IP1, SERVER_PORT1))
# 创建第二个服务器的套接字并连接
server_socket2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket2.connect((SERVER_IP2, SERVER_PORT2))
# 发送数据给第一个服务器
message1 = "Hello from client"
server_socket1.send(message1.encode())
# 接收第一个服务器的响应
response1 = server_socket1.recv(1024)
print(f"Response from server 1: {response1.decode()}")
# 发送数据给第二个服务器
message2 = "Hello from client"
server_socket2.send(message2.encode())
# 接收第二个服务器的响应
response2 = server_socket2.recv(1024)
print(f"Response from server 2: {response2.decode()}")
# 关闭套接字
server_socket1.close()
server_socket2.close()
参考:
1. http://t.csdnimg.cn/rj3ve
2. https://youtu.be/CV7_stUWvBQ?si=ppR4MQeKI5E5XNy5