你应该了解的python socket

https://mdnice.com/writing/8f86ec17792d43d7bb22190c5e9d3262
https://blog.csdn.net/baiydn/article/details/127253833

1. 短连接 长连接

短连接就是socket客户端与服务端建立一个连接,在收发完数据后就立刻关闭与服务端的连接,如果需要进行下一次请求,则需要重新连接服务端。socket短连接适用于客户端与服务端交互并不是很频繁的业务场景。
  长连接则是在建立socket连接后,一直保持连接,不关闭连接,可以持续收发数据包。socket长连接适用于客户端与服务端交互频繁、实时性很强的业务场景。

1.1server短连接

import socket

# 创建 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定地址和端口号
server_socket.bind(('localhost', 8000))

# 监听端口号
server_socket.listen()

while True:
    # 等待客户端连接
    client_socket, addr = server_socket.accept()
    
    # 接收客户端请求
    request_data = client_socket.recv(1024)
    
    # 处理客户端请求,返回响应
    response_data = 'Hello, world!'.encode()
    
    # 发送响应给客户端
    client_socket.sendall(response_data)
    
    # 关闭连接
    client_socket.close()

1.2server长连接(多个client连接)

import socket

# 设置服务器信息
HOST = '127.0.0.1'
PORT = 8080
BUFFER_SIZE = 1024

# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 绑定地址
server_socket.bind((HOST, PORT))

# 监听端口
server_socket.listen(10)

# 服务器循环
while True:
    # 等待连接
    client_socket, address = server_socket.accept()
    print(f"Connected by {
      
      address}")

    # 设置连接为非阻塞模式
    client_socket.setblocking(False)

    # 定义缓冲区
    data = b''

    # 连接循环
    while True:
        try:
            # 读取数据
            recv_data = client_socket.recv(BUFFER_SIZE)
            if recv_data:
                # 添加数据到缓冲区
                data += recv_data
            else:
                # 连接已断开
                break
        except socket.error:
            # 未读取到数据
            pass

        # 处理数据
        if len(data) >= BUFFER_SIZE:
            # 数据分片,每次处理BUFFER_SIZE大小的数据
            data_list = [data[i:i + BUFFER_SIZE] for i in range(0, len(data), BUFFER_SIZE)]
            for chunk in data_list:
                # 处理数据
                process_data(chunk)

            # 清空缓冲区
            data = b''

        # 发送心跳包
        client_socket.sendall(b'ping')

    # 关闭连接
    client_socket.close()

2. 阻塞和非阻塞 (针对长连接)

在Socket编程中,阻塞和非阻塞是指在读取或写入Socket数据时是否会阻塞当前线程或进程的执行。

当一个线程或进程调用一个阻塞式的Socket操作(如读或写),该线程或进程会一直等待,直到操作完成或超时。在这期间,该线程或进程无法执行其他任务。

而非阻塞式的Socket操作则不会阻塞当前线程或进程,而是立即返回一个错误或无数据,让该线程或进程可以继续执行其他任务。

在Socket编程中,使用非阻塞Socket操作可以提高程序的并发性和响应性能力,因为它可以使得一个线程或进程可以同时处理多个Socket连接,而不需要等待任何一个Socket操作完成。

3.多线程还是select

使用 select 和使用多线程都是实现并发网络编程的方式,它们各有优缺点。

使用 select 的优点是:

  1. 能够处理多个连接,无需创建多个线程,减少线程创建和上下文切换的开销;
  2. 可以支持非阻塞 I/O 操作,通过 select 函数的阻塞和就绪状态判断,实现异步 I/O 操作;
  3. 可以在一个线程中处理多个套接字的 I/O 事件,降低 CPU 占用率。

使用多线程的优点是:

  1. 对于 CPU 密集型任务,多线程能够有效利用多核 CPU,提高并发处理能力;
  2. 在处理不同的请求时,各个线程之间相互独立,互不影响,提高并发处理能力;
  3. 使用多线程能够简化代码实现,增加可读性。

需要根据具体的应用场景选择合适的并发编程方式。如果是 I/O 密集型任务,使用 select 会更加高效;如果是 CPU 密集型任务,使用多线程能够发挥更好的性能。

4.连包

连包问题是指在网络通信中,由于数据发送和接收的速度不同步,导致多个数据包在传输过程中合并成一个数据包,从而导致数据接收方无法正确地解析和处理数据。

在上述代码中,由于使用了循环发送数据的方式,导致在短时间内连续发送多个数据包,这可能会引起TCP连接的流量控制机制,从而导致数据包的合并。当多个数据包被合并成一个数据包发送到客户端时,客户端接收到的数据就不是按照原来的顺序排列的,从而出现数据错乱的问题。为了避免这个问题,可以采用消息边界标识符的方式,将每个消息加上特定的标识符,以便在接收方正确地分割消息。在上述代码中,已经使用了消息边界标识符的方式,但是由于在连续发送数据时没有加入合适的间隔,导致数据包合并的问题仍然存在。

5.最终

# encoding=utf-8
import threading
import socket
import struct

class Serv:
    def __init__(self):
        self.client_socket_list=[]

    def tcp_server_start(self, ip, post):
        """
        功能函数,TCP服务端开启的方法
        :return: None
        """
        self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 取消主动断开连接四次握手后的TIME_WAIT状态
        self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            address = (ip, int(post))
            self.tcp_socket.bind(address)
            self.tcp_socket.listen()
            # 设定套接字为非阻塞式
            self.tcp_socket.setblocking(False)
            self.sever_th = threading.Thread(target=self.tcp_server_concurrency)
            self.sever_th.start()
            print('TCP服务端正在监听端口:%s\n' % str(ip))
        except Exception as ret:
            msg = '请检查IP,端口\n'
            print(msg)

    def tcp_server_concurrency(self):
        """
        功能函数,供创建线程的方法;
        使用子线程用于监听并创建连接,使主线程可以继续运行,以免无响应
        使用非阻塞式并发用于接收客户端消息,减少系统资源浪费,使软件轻量化
        :return:None
        """
        while True:
            try:
                client_socket, client_address = self.tcp_socket.accept()
                client_socket.setblocking(False)
            except Exception:
                pass  # 因为是非堵塞,所以有可能会出现socket异常的情况
            else:
                # 将创建的客户端套接字存入列表,client_address为ip和端口的元组
                self.client_socket_list.append((client_socket, client_address))
                msg = 'TCP服务端已连接IP:%s端口:%s\n' % client_address
                print(msg)
                # 轮询客户端套接字列表,接收数据
            for client, address in self.client_socket_list:
                try:
                    self.recv_msg(client)
                except Exception:
                    pass

    def tcp_send(self, msg):
        try:
            for client, address in self.client_socket_list:
                client.sendall(msg)
                # msg = 'TCP服务端已发送{}\n'.format(msg)
                # print(msg)
        except Exception as e:
            print(e)
            self.client_socket_list.remove((client, address))

    def handle_message(self,msg):
        # 处理接收到的消息
        print(len(msg))
        print(f"Received message: {
      
      msg}")

    def recv_msg(self,sock):
        # 接收数据并解析消息
        buffer_size = 1024  # 缓存区大小
        recv_buffer = b''  # 接收缓存区
        star_sequence = b'star'  # 消息开始标识
        stop_sequence = b'stop'  # 消息结束标识
        msg_start = False  # 标识消息开始
        while True:
            data = sock.recv(buffer_size)
            if not data:
                # 连接已断开
                break
            recv_buffer += data
            while True:
                if not msg_start:
                    star_pos = recv_buffer.find(star_sequence)
                    if star_pos >= 0:
                        # 找到消息开始标识
                        recv_buffer = recv_buffer[star_pos + len(star_sequence):]
                        msg_start = True
                    else:
                        break
                if msg_start:
                    stop_pos = recv_buffer.find(stop_sequence)
                    if stop_pos >= 0:
                        # 找到一个完整的消息
                        msg = recv_buffer[:stop_pos]
                        self.handle_message(msg)
                        recv_buffer = recv_buffer[stop_pos + len(stop_sequence):]
                        msg_start = False
                    else:
                        # 没有找到完整的消息
                        if len(recv_buffer) > buffer_size:
                            # 缓存区已满,报告解析失败
                            recv_buffer=b''
                            raise ValueError("Message too long")
                        break

se=Serv()
se.tcp_server_start('127.0.0.1',2000)

猜你喜欢

转载自blog.csdn.net/qq_33228039/article/details/130576783