客户端之间通信
上一篇文章中了解了 socket对象的一些接收数据和发送数据的方法,以及服务端与多个客户端之间的通信,未能实现不同客户端之间的通信;现在还是基于TCP协议链接的模式来实现不同客户端之间实现通信;主要是服务端根据客户端发送过来的数据进行分析,然后分发给其他客户端。
服务端
服务端和上篇文章中大致思路是一样的;都是利用多线程模式来实现和多个客户端进行连接;
利用clients_dict变量来存储不同客户端的信息:客户端昵称,客户端连接对象。
# demo_socket_server_1.py文件
import logging
import socket
import threading
import re
logging.basicConfig(level=logging.DEBUG,
format="%(asctime)s>%(message)s", datefmt="%Y-%m-%d %H:%M:%S")
logger = logging.getLogger(__name__)
class ServerClass(object):
"""docstring for ServerClass"""
__HOST = "127.0.0.1"
__PORT = 9898
def __init__(self):
# 创建 套接字对象 socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
# 参数:family:地址簇 type:套接字类型
self.__TCP_SOCKET = socket.socket(
family=socket.AF_INET, type=socket.SOCK_STREAM)
# 服务端的地址 address
self.__ADDR = (ServerClass.__HOST, ServerClass.__PORT)
# 存放 客户端 信息
self.clients_dict = dict()
def start_server(self):
"""启动服务端方法"""
with self.__TCP_SOCKET as sock:
# 将套接字绑定到 address
sock.bind(self.__ADDR)
# 启动一个服务器用于接受连接 socket.listen([backlog])
sock.listen(5)
logger.info("等待连接...")
# 循环控制 用来等待客户端连接 以及保持连接状态
while True:
# 接受一个连接 返回值是一个 (conn, address)
conn, addr = sock.accept()
# 多个客户端连接需要开启新的线程或者是进程
th = threading.Thread(target=self.conn_client,
args=(conn, addr), daemon=True)
th.start()
然后是启动线程所对应的方法:
主要实现三个功能:
- 所有连接的客户端需要先注册一个昵称,并验证昵称是否合法,验证通过就相当于登录成功了,可以进行交互了,并把客户端信息添加到clients_dict 变量;给已经在线的客户端发送上线信息
- 接收客户端连接发送的数据
- 对接收到的数据进行发送
- 客户端断开连接从clients_dict 变量删除客户端信息;给在线的客户端发送下线信息
# demo_socket_server_1.py文件
def conn_client(self, conn, addr):
"""连接客户端,并进行收发数据"""
with conn:
# 如果有新的连接进来就 发送数据;
message = ["hi! %s %s \n" % addr, "请先输入你的昵称:"]
message = "".join(message)
conn.sendall(message.encode("utf-8"))
# 获取客户端昵称
client_username = self.login_client(conn)
logger.info("正在与 %s 建立连接...", client_username)
# 循环控制 使用accept()方法返回的套接字对象 收发数据
while True:
try:
recv_data = conn.recv(1024).decode("utf-8")
if (not recv_data) or (recv_data in ("quit", "exit")):
logger.info("与 %s,断开连接...", client_username)
# 客户端退出
self.quit_client(client_username)
break
# 根据收到的数据 进行判断该怎么处理数据
self.send_msg_by_type(client_username, recv_data)
except ConnectionResetError as e:
logger.error("与 %s 意外断开连接...",
client_username, exc_info=True)
self.quit_client(client_username)
break
登录和验证客户端昵称的方法:
# demo_socket_server_1.py文件
def login_client(self, conn):
"""
客户端上线
验证客户端昵称是否合法;
通知其他客户端 client_username 上线了
添加客户端信息到clients_dict
"""
client_username = self.ver_username(conn)
message = f"{
client_username} 上线了"
if self.clients_dict != {
}:
self.send_msg(message, *self.clients_dict.keys())
self.add_client(client_username, conn)
return client_username
def ver_username(self, conn):
"""
接收客户端昵称并验证是否合法
如果验证不通过 递归接着验证
"""
client_username = conn.recv(1024).decode("utf-8")
if client_username in self.clients_dict.keys():
message = f"{
client_username} 已经存在了"
conn.sendall(message.encode("utf-8"))
self.ver_username(conn)
elif len(client_username) > 5:
message = f"{
client_username} 这个昵称太长了,不能超过5个字符"
conn.sendall(message.encode("utf-8"))
self.ver_username(conn)
elif re.search(r"[^0-9a-zA-Z]", client_username):
message = f"{
client_username} 昵称只能是数字或者字母,或者数字和字母组合"
conn.sendall(message.encode("utf-8"))
self.ver_username(conn)
else:
message = f"OK,设置成功! 可以使用 {
client_username} 这个昵称\n输入 -help 查看帮助文档"
conn.sendall(message.encode("utf-8"))
return client_username
def add_client(self, client_username, conn):
"""增加客户端信息"""
self