我希望为既充当服务器又充当客户端的东西编写一个脚本。对于服务器部分,我决定创建一个 SSL 套接字
accept()
循环,它将永远侦听新连接,并使其成为非阻塞,以便客户端部分可以工作,我决定使用 select
。
这是脚本:
from socket import (socket,
AF_INET,
SOCK_STREAM,
create_connection,
SOL_SOCKET,
SO_REUSEADDR)
from ssl import (SSLContext,
PROTOCOL_TLS_SERVER,
PROTOCOL_TLS_CLIENT)
import select
import threading
import time
from tqdm.auto import tqdm
def handle_client(client, address):
request_bytes = b"" + client.recv(1024)
if not request_bytes:
print("Connection closed")
client.close()
request_str = request_bytes.decode()
print(f"we've received {request_str}")
ip = '127.0.0.1'
port = 8443
server_context = SSLContext(PROTOCOL_TLS_SERVER)
server_context.load_cert_chain('cert_ex1.pem', 'key_ex1.pem')
initial_counter = 1
server_socket = socket(AF_INET, SOCK_STREAM)
server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server_socket.bind((ip, port))
server_socket.listen(5)
print("forming lists for select...")
inputs = [server_socket]
print(f"we now have list inputs: {inputs}")
outputs = []
while True:
print(f"checking: inputs still has a single socket in it: {type(inputs[0])}")
readable, writable, exceptional = select.select(inputs, outputs, inputs, 1)
print(f"readable is {readable}, \n writable is {writable}, \n exceptional is {exceptional}")
for s in tqdm(readable):
if s is server_socket:
print("wrapping server socket in SSL...")
with server_context.wrap_socket(server, server_side=True) as tls:
connection, client_address = tls.accept()
print("making the connection non-blocking...")
connection.setblocking(0)
inputs.append(connection)
print("starting a thread that'd handle the messages...")
threading.Thread(target=handle_client, args=(connection, client_address)).start()
else:
print(f"dealing with socket {s}")
hostname='example.org'
client_context = SSLContext(PROTOCOL_TLS_CLIENT)
client_context.load_verify_locations('cert_ex1.pem')
with create_connection((ip, port)) as client:
with client_context.wrap_socket(client, server_hostname=hostname) as tls:
print(f'Using {tls.version()}\n')
print("client is sending data...")
tls.sendall(int.to_bytes(initial_counter, 4, 'little'))
while True:
data = tls.recv(1024*8)
if not data:
print("Client received no data")
break
new_data = int.from_bytes(data, 'little')
print(f'Server says: {new_data}')
new_data = int.to_bytes(new_data+1, 4, 'little')
print("sleeping for 0.15...")
time.sleep(0.15)
tls.sendall(new_data)
运行此脚本的问题是,它正确创建了套接字,将仅包含此套接字的列表传递给
select.select()
,但随后 select()
返回 3 个空列表。
connection.setblocking(0)
,或者其他任何东西)?这是在服务器端使用非阻塞套接字的示例 - 客户端仍然生成到不同的线程中,但这是在“空闲回调”中完成的,所以现在您可以看到您可以接受客户端和客户端数据,并且仍然偶尔会这样做同一程序中的其他工作。
我跳过了
ssl
位,因为我手头没有您的证书,但请务必只将两个套接字都包装一次...
import random
import select
import socket
import threading
import time
hostname = "example.org"
ip = "127.0.0.1"
port = 8443
client_counter = 0
def server(idle_callback):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((ip, port))
server_socket.listen(5)
# server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# server_socket = server_context.wrap_socket(server_socket, server_side=True)
clients = []
while True:
sockets = [server_socket, *clients]
readable, writable, exceptional = select.select(sockets, [], sockets, 0.1)
for s in readable:
if s is server_socket:
connection, client_address = server_socket.accept()
connection.setblocking(False)
clients.append(connection)
print(f"new connection from {client_address}")
else: # must be a client socket
try:
msg = s.recv(1024)
print(f"{s}: received {msg}")
if msg.startswith(b"The time is"):
s.sendall(b"The eagle flies at midnight...\n")
else:
s.sendall(f"Sorry, I don't understand {msg}\n".encode())
except ConnectionError as exc:
print(f"{s}: {exc}")
s.close()
clients.remove(s)
continue
for x in exceptional:
print(f"exceptional condition on {x}")
x.close()
clients.remove(x)
idle_callback()
def client():
global client_counter
client_counter += 1
client_id = client_counter
print(f"[{client_id}] Starting client...")
with socket.create_connection((ip, port)) as client:
# client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# with client_context.wrap_socket(client, server_hostname=hostname) as client:
# print(f'Using {client.version()}\n')
while True:
if random.random() < 0.1:
print(f"[{client_id}] Time to go...")
break
if random.random() < 0.5:
client.sendall(f"The time is {time.asctime()}\n".encode("utf-8"))
else:
client.sendall(b"Hello, server\n")
data = client.recv(1024 * 8)
if not data:
print(f"[{client_id}] Client received no data")
break
print(f"[{client_id}] Server says: {data}")
time.sleep(0.5)
def idle_callback():
if random.random() < 0.1:
threading.Thread(target=client).start()
def main():
server(idle_callback)
if __name__ == "__main__":
main()