python socket
- 1. 初级实现
- 2. 添加header
- 3. 中级实现(引用pickle库)
- 4. 高级实现(相互发送信息)
- 5. 一点尝试
- 5. 1个server对应2个client
- 5.2个server对应1个client
名称 | 版本 |
---|---|
python | 3.11 |
本文涉及到socket的server与client通讯从简单到复杂的实现,以及一个server接收多个client,和多个server接收同一个client的案例。
socket中server和client通讯最基础的关系如下图:
1. 初级实现
server
端:server.py
import socket
import timelocalhost = '127.0.0.1'
Port = 1234s = 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 timelocalhost = '127.0.0.1'
Port = 1234s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((localhost, Port))full_msg = ''
while True:msg = s.recv(8)if len(msg) <= 0:breakfull_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 timelocalhost = '127.0.0.1'
Port = 1234s = 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}}' + msgclientsocket.send(bytes(msg, "utf-8"))while True:time.sleep(3)msg = f" The time is! {time.time()}"msg = f'{len(msg):<{HEADERSIZE}}' + msgclientsocket.send(bytes(msg, "utf-8"))
client
端:client.py
import socket
import timelocalhost = '127.0.0.1'
Port = 1234
HEADERSIZE = 10s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((localhost, Port))while True:full_msg = ''new_msg = Truewhile True:msg = s.recv(16)# receive a msg w/ headerif new_msg:print(f"new msg length: {msg[:HEADERSIZE]}")msglen = int(msg[:HEADERSIZE])new_msg = Falsefull_msg += msg.decode("utf-8")if len(full_msg) - HEADERSIZE == msglen:print("full msg recvd")print(full_msg[HEADERSIZE:])new_msg = Truefull_msg = ''
3. 中级实现(引用pickle库)
Python的pickle
库是用于序列化(将对象转换为字节流)和反序列化(将字节流转换为对象)Python对象的标准库。它允许将复杂的Python对象转换为字节流,以便在不同的环境中存储、传输或重新创建对象。
server
端:server.py
import picklelocalhost = '127.0.0.1'
Port = 1234s = 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") + msgclientsocket.send(msg)
client
端:client.py
import picklelocalhost = '127.0.0.1'
Port = 1234
HEADERSIZE = 10s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((localhost, Port))while True:full_msg = b'' #b for bytesnew_msg = Truewhile True:msg = s.recv(16)# receive a msg w/ headerif new_msg:print(f"new msg length: {msg[:HEADERSIZE]}")msglen = int(msg[:HEADERSIZE])new_msg = Falsefull_msg += msgif len(full_msg) - HEADERSIZE == msglen:print("full msg recvd")# print(full_msg[HEADERSIZE:]) # before decode d = pickle.loads(full_msg[HEADERSIZE:])print(d) # after decodenew_msg = Truefull_msg = b''
4. 高级实现(相互发送信息)
以上代码都是server向client发送信息。
实际上,在 Socket 通信中,server和client是彼此之间的消息接收者和发送者。它们之间可以相互发送消息,实现双向通信。
server
端:server.py
import selectHEADER_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 -> intmessage_length = int(message_header.decode('utf-8').strip())print(message_length)# 返回破解后的msgreturn {'header':message_header, 'data': client_socket.recv(message_length)}except:# 接收不到信息就关闭return Falsewhile 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_listsockets_list.append(client_socket)# 添加:notified_socker的用户到一个clients字典clients[client_socket] = user # key-valueprint("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_socketsockets_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 sysHEADER_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:# 输入用户要发送的信息msgmessage = 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处发来的msgusername_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()continueexcept 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 = 1234SERVER_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