https://mdnice.com/writing/8f86ec17792d43d7bb22190c5e9d3262
https://blog.csdn.net/baiydn/article/details/127253833
1. ショート接続 ロング接続
短い接続とは、ソケットクライアントがサーバーとの接続を確立し、データの送受信後すぐにサーバーとの接続を切断し、次のリクエストが必要な場合にサーバーとの再接続が必要になることを意味します。ソケットの短い接続は、クライアントとサーバー間の対話がそれほど頻繁ではないビジネス シナリオに適しています。
長い接続とは、ソケット接続が確立された後、接続が閉じられずに接続が維持され、データ パケットが継続的に送受信できることを意味します。ソケット永続接続は、クライアントとサーバー間の頻繁なやり取りと強力なリアルタイム パフォーマンスを必要とするビジネス シナリオに適しています。
1.1 サーバーの短い接続
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.2 サーバーの長時間接続 (複数のクライアント接続)
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 操作 (読み取りまたは書き込みなど) を呼び出すと、スレッドまたはプロセスは操作が完了するかタイムアウトになるまで待機します。この間、スレッドまたはプロセスは他のタスクを実行できません。
非ブロッキング ソケット操作は現在のスレッドまたはプロセスをブロックしませんが、ただちにエラーを返すかデータを返さないため、スレッドまたはプロセスは他のタスクを実行し続けることができます。
ソケット プログラミングでは、ノンブロッキング ソケット操作を使用すると、ソケット操作の完了を待たずにスレッドまたはプロセスが複数のソケット接続を同時に処理できるため、プログラムの同時実行性と応答性が向上します。
3. マルチスレッドまたは選択
マルチスレッドの使用select
と同時ネットワーク プログラミングを実現する方法であり、それぞれ長所と短所があります。
使用する利点select
は次のとおりです。
- 複数のスレッドを作成せずに複数の接続を処理できるため、スレッド作成とコンテキスト切り替えのオーバーヘッドが軽減されます。
- ノンブロッキング I/O 操作をサポートし、
select
関数。 - 複数のソケットの I/O イベントを 1 つのスレッドで処理して、CPU 使用率を削減できます。
マルチスレッドを使用する利点は次のとおりです。
- CPU を集中的に使用するタスクの場合、マルチスレッドによってマルチコア CPU が効果的に利用され、同時処理能力が向上します。
- 異なるリクエストを処理する場合、各スレッドは互いに独立しており、相互に影響を与えないため、同時処理能力が向上します。
- マルチスレッドを使用すると、コードの実装が簡素化され、可読性が向上します。
特定のアプリケーションシナリオに応じて、適切な同時プログラミング方法を選択する必要があります。I/O 集中型のタスクの場合は、マルチスレッドを使用する方が効率select
が良く、CPU 集中型のタスクの場合は、マルチスレッドを使用するとパフォーマンスが向上します。
4.偶数パッケージ
パケット接続の問題とは、ネットワーク通信において、データ送受信の非同期速度が原因で、送信中に複数のデータ パケットが 1 つのデータ パケットにマージされ、その結果、データ受信側がデータを正しく解析して処理できなくなることを指します。
上記のコードでは、サイクリック データ送信を使用しているため、複数のデータ パケットが短時間に連続して送信され、TCP 接続のフロー制御メカニズムが発生し、データ パケットがマージされる可能性があります。複数のデータ パケットが 1 つのデータ パケットに結合されてクライアントに送信されると、クライアントが受信するデータは元の順序で並べられず、データの混乱が生じます。この問題を回避するために、メッセージ境界識別子を使用して各メッセージに特定の識別子を追加し、受信側でメッセージを正しく分割できるようにすることができます。上記のコードでは、メッセージ境界識別子の方法を使用していますが、連続してデータを送信する際に適切な間隔が追加されていないため、データパケットのマージの問題が依然として存在します。
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)