python socket通讯 学习记录

名称版本
python3.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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值