推荐先看:
TCP/IP、HTTP、Socket、WebSocket-CSDN博客
用了这么久,WebSocket和Socket的区别,你真的知道吗?-CSDN博客
python socket和websocket对待client并发的处理:
用socketserver.ThreadingTCPServer处理socket的多并发,与 from ws4py.client.threadedclient import WebSocketClient相比:
(1)socketserver.ThreadingTCPServer 在server端启动后,会一直轮询查selector中是否有可执行的socket,当监听到client的连接时,就会把client放到selector队列中,如果selector中存在client可执行了,就会在server端为每个可执行的client开启一个线程处理读写任务,任务执行完后就关闭socket client。
这个socket server是否端口复用,执行任务的线程是否守护线程,都可自定义。默认是端口不可复用,线程是非守护线程。
(2)ws4py下的WebSocketClient 是直接由客户端自己控制,当client连接上server后,client端会直接开启一个守护线程处理读写任务。守护线程不会主动关闭。
1、socket
1.1 简介
博文推荐:Socket的长连接和短连接
说明:socket本身无长短,因使用方式的不同被分为长连接、短连接。
- 长连接:一旦建立了socket连接,就不会轻易断开,除非客户端或服务端主动发起关闭,否则连接一直开着。这种情况下,只要一方有消息发来,不管发起几次,另一方都能立马接受。也就是可以一次建立,多次交流。
- 短连接:一旦建立了socket连接,完成一来一回的信息交流后,就必须关闭。如果还要没有更多的消息要传递,那只能再次重建连接。每次连接也只能是一轮消息交流,即一方只有1次发送消息的机会,另一方只有1次接受此消息的机会。也就是一次建立,一次交流。
1.2 socket案例
(1)socketserver_server.py
# Autor cf
#!/usr/bin/env Python
# coding=utf-8
import socketserver
import traceback
import time
class MySockServer(socketserver.BaseRequestHandler): # 定义一个类
# 在handle()之前被调用,主要的作用就是执行处理请求之前的初始化相关的各种工作。默认不会做任何事。
def setup(self): #
print("开始初始化工作~~~")
# handle(self)处理与请求相关的工作。默认也不会做任何事。
def handle(self):
print ('Got a new connection from client:', self.client_address)
print ('Got a new connection to server:', self.server)
while True:
data = self.request.recv(1024).decode('UTF-8', 'ignore').strip() # 需要通过self的方法调用数据接收函数
if not data:
break
print('recv:', data)
self.request.send(data.upper().encode("utf8")) # 需要通过self的方法调用数据发送函数
# 在handle()方法之后会被调用,他的作用就是执行当处理完请求后的清理工作,默认不会做任何事
def finish(self):
print('准备睡觉啦~~~~~')
time.sleep(5)
print('睡醒了~~~~~')
print("任务处理完成,开始收尾工作~~~")
HOST = '192.168.10.4' #定义侦听本地地址口(多个IP地址情况下)
PORT = 12345 #Server端开放的服务端
# ForkingMixIn利用多进程(分叉)实现异步。
# ThreadingMixIn利用多线程实现异步。
# ThreadingTCPServer继承了ThreadingMixIn和TCPServer
# 调用SocketServer模块的多线程并发函数,这里的多线程是每次来一个请求都new thread去处理的
s = None
try:
# allow_reuse_address=True允许服务器重用地址,含义:即使端口已被占用,仍不影响该端口的使用
# socketserver.ThreadingTCPServer.allow_reuse_address = True
# 这里构造器传递的参数是TCPServer初始化需要的信息
s = socketserver.ThreadingTCPServer((HOST, PORT), MySockServer)
s.serve_forever() #持续接受客户端的连接,会把连接放到selector
except Exception as e:
traceback.print_exc()
finally:
print('server finally...........')
s.shutdown()
(2)client.py 短链接
- socket 短连接, 需要满足:socket client 和 server 完成一来一回的 “一轮次” 消息交流后,就必须关闭。
# Autor cf
#!/usr/bin/env Python
# coding=utf-8
import socket
import time
import traceback
HOST, PORT = '192.168.10.4' , 12345
data = "the weather is warm, let's go out for shopping"
m = 0
while(True):
sock = None
print('start time = ', time.time())
try:
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to server and send data
sock.connect((HOST, PORT))
m = m +1
sock.sendall(bytes(str(m) + data, "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
except Exception as e:
print('end time = ', time.time())
print(traceback.print_exc())
break
finally:
print('client finally close')
sock.close()
(3)client2.py 长连接
- socket 长连接, 需要满足:socket client 和 server 一直保持连接状态;
- 由于连接一直开启,所以每次通信,根据指定的消息结束符(如EOF、exit等)来判断一次消息是否发送结束;
- 虽然长连接的状态是指“一直保持连接”,但也不是说永远永远都不能端。实际应用中,socket client的关闭时刻可以根据不同的应用场景来设计。比如:只在异常发生时 或 客户端长时间无动态 或 客户端主动发起关闭(如关闭聊天窗口)等。
# Autor cf
#!/usr/bin/env Python
# coding=utf-8
'''
socket 长连接, 需要满足以下条件:
(1) socket client 在while(True)模式下一直发消息;
(2) 每次通信,根据指定的消息结束符(如EOF、exit等)来判断一次消息是否发送结束;
(3) socket client的关闭可以根据应用场景设计不同的关闭时刻。比如:只在异常发生时 或 客户端长时间无动态 或 客户端主动发起关闭(如关闭聊天窗口)等。
'''
import socket
import time
import traceback
data = "the weather is warm, let's go out for shopping"
m = 0
HOST, PORT = '192.168.10.4' , 12345
# Create a socket (SOCK_STREAM means a TCP socket)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to server and send data
sock.connect((HOST, PORT))
print('start time = ', time.time())
try:
while (True):
m = m +1
sock.sendall(bytes(str(m) + data, "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")
print("Sent: {}".format(data))
print("Received: {}".format(received))
except Exception as e:
print('end time = ', time.time())
sock.close() # 测试sock长连接
raise Exception(e, 'client通信失败')
2、WebSocket案例
(1) webserver.py
# Autor cf
#!/usr/bin/env Python
# coding=utf-8
import asyncio
import websockets
# 检测客户端权限,用户名密码通过才能退出循环
async def check_permit(websocket):
while True:
recv_str = await websocket.recv()
cred_dict = recv_str.split(":")
if cred_dict[0] == "admin" and cred_dict[1] == "123456":
response_str = "congratulation, you have connect with server\r\nnow, you can do something else"
await websocket.send(response_str)
return True
else:
response_str = "sorry, the username or password is wrong, please submit again"
await websocket.send(response_str)
# 接收客户端消息并处理,这里只是简单把客户端发来的返回回去
async def recv_msg(websocket):
while True:
recv_text = await websocket.recv()
response_text = f"your submit context: {recv_text}"
await websocket.send(response_text)
# 服务器端主逻辑
# websocket和path是该函数被回调时自动传过来的,不需要自己传
async def main_logic(websocket, path):
await check_permit(websocket)
await recv_msg(websocket)
# 把ip换成自己本地的ip
start_server = websockets.serve(main_logic, '192.168.10.4', 12345)
print('start_server是:',start_server)
# 如果要给被回调的main_logic传递自定义参数,可使用以下形式
# 一、修改回调形式
# import functools
# start_server = websockets.serve(functools.partial(main_logic, other_param="test_value"), '10.10.6.91', 5678)
# 修改被回调函数定义,增加相应参数
# async def main_logic(websocket, path, other_param)
loop = asyncio.get_event_loop()
loop.run_until_complete(start_server)
loop.run_forever()
(2) webclient.py
1、继承关系(子->父):CG_Client -> WebSocketClient -> WebSocketBaseClient -> WebSocket
2、WebSocketClient在初始化__init__()方法中触发了WebSocketBaseClient的初始化;
WebSocketBaseClient在初始化__init__()方法中又触发了WebSocket的初始化。3、这里重写的opend、closed、received_message方法均来自WebSocket。
4、用ws4py中的WebSocketClient启动socket连接,比websockets.connect直接启动要稳定些。
# Autor cf
#!/usr/bin/env Python
# coding=utf-8
from ws4py.client.threadedclient import WebSocketClient
class CG_Client(WebSocketClient):
def opened(self):
cred_text = input("please enter your username and password: ")
self.send(cred_text)
def closed(self, code, reason=None):
print("Closed down:", code, reason)
def received_message(self, resp):
print('recieved:', resp)
# ws.close() # 这里不执行close,client会一直运行
if __name__ == '__main__':
ws = None
try:
# (1)自定义WebSocketClient的子类,ws是自定义子类的一个实例,每个实例底层会另起一个守护线程,守护线程默认最大空闲存活时间默认0.1*60=6s。
ws = CG_Client('ws://192.168.10.4:12345')
# (2)connect()来自WebSocketBaseClient,该方法默认会先启动一个端口可复用的TCPSocket,再调用self.handshake_ok()
# 注意:
# 这里的self指的是最外层的调用对象,也就是ws, 而不是WebSocketBaseClient对象!由于ws最先继承WebSocketClient,所以
# 这里触发的将是最外层父类WebSocketClient的handshake_ok(),而不是WebSocketBaseClient的handshake_ok();
# (3)前者在handshake_ok()中触发了守护线程的启动,调用守护线程的run();
# 这里守护线程调用的run()是WebSocket中的run();
# (4)在WebSocket的run()中又调用了self.opend(), 这里就回到了ws.opend()了。
ws.connect()
# run_forever会把守护线程join到主线程下,所以主线程要等待守护线程结束才结束。
ws.run_forever()
except KeyboardInterrupt:
ws.close()