上文地址:关于socket收发信息与多线程,多进程问题总结-CSDN博客
一 错误问题原因以及个人分析
在客户端申请关闭连接后,再次重新运行客户端发送信息会出现报错情况,分别为以下两种错误:
[WinError 10054]一个现有的连接被远程主机强制关闭
以及:
[WinError 10038] 在一个非套接字上尝试了一个操作
1.报错原因
因为服务端需要接收多个客户端发来的连接请求,所以这里采用的列表的方式存储客户端套接字。
代码如下:
当客户端申请关闭后,服务器同意关闭,双方结束通讯,但套接字仍保留在list1中未被删除,当有人在发送消息的时候,服务器就会遍历列表,向列表中的每个客户端发送消息,当发送到已经断开连接的套接字时就会产生[WinError 10038] 在一个非套接字上尝试了一个操作这种错误。
2.解决方法
在关闭套接字后可以对list1中的套接字进行删除,同时因为对list1进行修改,对应的list_name中数据也需要进行修改(list_name中以字典形式存放了用户IP,name,和 list1中的对应的编号),这用就不会出现上述错误了。
代码展示:
二 代码展示
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:
substance = listen_socket_1.recv(1024).decode(encoding="utf-8")
print("接收成功")
if substance == "exit": # 检测用户是否要退出聊天室,如果是就执行if内操作,不是则执行else
use = return_values(list_name, arr, 'seria')
# 通过for循环向退出用户以外的其他用户发送该用户退出的消息
# -------------------------------------------------------------
for num1 in range(len(list1)):
if num1 != use:
list1[num1].send(f"{return_values(list_name, arr, 'name')}退出聊天室".encode(encoding="utf-8"))
# ----------------------------------------------------------------------
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
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)
# 该函数是对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.其他问题
在客服端不采用exit退出系统,而是强制关闭系统时,则服务器会产生错误,同时失效的套接字仍存在在list1列表中,继续发送信息时就会导致系统崩溃。
2.个人思路
在发送时使用try:监测发送语句,如果报错则删除掉报错的套接字并更新list_name,然后继续执行程序,(因为报错为失效套接字,所以跳过本次发送也是没有问题的,并向列表内的下一个套接字发送消息)
(待实现)