关于socket实现收发同步的问题与解决方案:
关于socket收发信息与多线程,多进程问题总结-CSDN博客
关于聊天室,客户端退出后发送消息导致服务器报错问题与解决方案:
关于socket收发信息与多线程,多进程问题总结(后续补充)-CSDN博客
一 问题
在客服端不采用exit退出系统,而是强制关闭系统时,则服务器会产生错误,同时失效的套接字仍存在在list1列表中,继续发送信息时就会导致系统崩溃。
二 解决方案
在发送时使用try:监测发送语句,如果报错则删除掉报错的套接字并更新list_name,然后继续执行程序。
三 具体实现
代码展示:
接收的主体部分,采用try监测,如果报错则删除当前套接字
删除部分具体执行函数
四 源代码展示
1.服务端
# 导入模块
import socket
# 创建socket对象
import threading
# 通过服务端输入消息并向所有用户广播
def read_sock(list1):
while True:
str1 = input("请输入内容")
for li in list1:
li.send(f"{str1}".encode(encoding="utf-8"))
print("发送成功")
# 接收到客户端发送来的消息,并向所有用户广播消息
def write_sock(listen_socket_1, list1, list_name, arr):
while True:
# 使用try, except对代码进行检测,如果用户强制关闭导致系统报错则执行except删除list1中已经失效的套接字,
try:
substance = listen_socket_1.recv(1024).decode(encoding="utf-8")
print("接收成功")
if substance == "exit": # 检测用户是否要退出聊天室,如果是就执行if内操作,不是则执行else
del_list(list1, list_name, arr, 1)
break
else: # 接收到客户端发送来的消息,并向所有用户广播消息
for li in list1:
print(list1)
li.send(f"{return_values(list_name, arr, 'name')}说:{substance}".encode(encoding="utf-8"))
print("发送成功")
print(substance)
# 如果报错则执行except,表示该套接字已经失效,并执行删除函数,然后退出循环
except:
del_list(list1, list_name, arr, 0) # 删除已经失效的套接字的函数
break
# del_list用来删除已经失效的套接字,并更新list_name中的信息,
# 其中state是用来确定套接字的状态,如果为1,这属于用户正常关闭套接字,该套接字目前还在生效则使用.close手动关闭
# 如果state不为1 则表示套接字已经失效,则跳过套接字关闭命令继续执行
def del_list(list1, list_name, arr, state):
use = return_values(list_name, arr, 'seria')
print(list1, list_name, arr, state)
# 通过for循环向退出用户以外的其他用户发送该用户退出的消息
# -------------------------------------------------------------
for num1 in range(len(list1)):
if num1 != use:
list1[num1].send(f"{return_values(list_name, arr, 'name')}退出聊天室".encode(encoding="utf-8"))
# ----------------------------------------------------------------------
# 用于检测套接字目前状态,1表示套接字还在生效,需要关闭,其他则表示套接字已经失效,无需执行关闭命令
if state == 1:
list1[use].close() # 关闭连接
# 对已经退出的用户套接字进行删除(同过将退出用户后面的用户向前覆盖,并删除最后一个重复用户来实现)
# ********************************************************************************
for num2 in range(use, len(list1) - 1):
list1[num2] = list1[num2 + 1]
del list1[len(list1) - 1]
# ********************************************************************************
# 下方代码是对list_name的seria进行更新,seria为( list1中的对应的编号)
for num3 in range(len(list_name)):
if list_name[num3]['seria'] == use:
list_name[num3]['seria'] = None
elif list_name[num3]['seria'] > use:
list_name[num3]['seria'] -= 1
# 该函数是对list_name中的某个指定数据进行返回,condition为要返回的内容,有(name和seria)两种
def return_values(list_name, arr, condition):
if condition == "name":
for num1 in range(len(list_name)):
if list_name[num1]["ip"] == arr:
return list_name[num1]["name"]
elif condition == "seria":
for num1 in range(len(list_name)):
if list_name[num1]["ip"] == arr:
return list_name[num1]["seria"]
# 下方代码是开启服务端,
# --------------------------------------------------------
ip = '192.168.22.90'
port = 7777
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_socket.bind((ip, port))
tcp_socket.listen(5)
list1 = [] # 用于存储连接成功的客户端套接字
list_name = [] # list_name中以字典形式存放了用户IP,name,和 list1中的对应的编号
# ---------------------------------------------------------
# 下方代码为循环接收客户端连接请求
while True:
listen_socket_1, arr = tcp_socket.accept()
list1.append(listen_socket_1)
print(f"接收到一个连接{arr}")
print(listen_socket_1)
# 该for循环为检测用户基础信息是否存在,如果存在则执行for循环内if语句,给用户的seria更新新的list1中的位置,并执行break
# 如果执行break则不会执行else中的语句,如果for循环执行完毕,
# if仍未执行,则表示该用户信息不在list_name中,需要用户手动出入名称
# ********************************************************************************
for num in range(len(list_name)):
if list_name[num]["ip"] == arr[0]:
list_name[num]["seria"] = len(list1)-1
for li in list1:
li.send(f"欢迎{list_name[num]['name']}进入聊天室".encode(encoding="utf-8"))
break
else:
listen_socket_1.send(f"IP{arr[0]}请输入姓名".encode(encoding="utf-8"))
substance = listen_socket_1.recv(1024).decode(encoding="utf-8")
list_name.append({"ip": arr[0], "name": substance, "seria": len(list1)-1})
for li in list1:
li.send(f"欢迎{substance}进入聊天室".encode(encoding="utf-8"))
# ********************************************************************************
print(f"现有人员{list_name}")
# 下方代码通过多线程方式接收客户端消息或向客户端发送消息
if __name__ == '__main__':
p1 = threading.Thread(target=read_sock, args=(list1,))
p2 = threading.Thread(target=write_sock, args=(listen_socket_1, list1, list_name, arr[0]))
p1.setDaemon(True)
p1.start()
p2.start()
2.客户端
# 导入模块
import socket
# 创建socket对象
import threading
# 客户端向服务端发送来的消息,并向所有用户广播消息
def read_sock(tcp_socket):
while True:
global str1
str1 = input()
tcp_socket.send(f"{str1}".encode(encoding="utf-8"))
print("发送成功")
if str1 == "exit":
break
return 1
# 接收到服务端发送来的消息,并进行显示
def write_sock(tcp_socket):
while True:
substance = tcp_socket.recv(1024).decode(encoding="utf-8")
print(substance)
if str1 == "exit":
break
# 下方代码为客户端向服务端发送连接申请
# --------------------------------------------------------
str1 = "" # 创建公共字符用于接收客户端向服务端发送的消息,如果是exit,则同时退出收和发,这里采用的多线程可以共享全局的方法
ip = '192.168.22.90'
port = 7777
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_socket.connect((ip, port))
# ---------------------------------------------------------
# 下方代码通过多线程方式接收服务器消息或向服务端发送消息
if __name__ == '__main__':
p1 = threading.Thread(target=read_sock, args=(tcp_socket,))
p2 = threading.Thread(target=write_sock, args=(tcp_socket,))
p2.setDaemon(True)
p1.start()
p2.start()
p1.join()
p2.join()
tcp_socket.close()
五 功能拓展与个人思路
1.创建多个聊天室,让客户端选择要去几号聊天室
可以使用多个list表来进行接收,根据客户端返回的聊天室房间号来添加list中,
或者通过一个list中嵌套list来实现区分聊天室房间号
2.使用@【姓名】来对聊天室的某个用户私聊,使在聊天室内除@人以为的其他用户无法收到信息
通过正则或者字符串切片获取到@后的姓名,并与list_name中的用户名进行比较如果存在则先该用户单独发送信息,如果不存在则向发送用户提示该用户不存在
3.其他
无了!!!!!
这里只是做功能拓展,不再做具体实现,如果有其他问题也可积极讨论