关于聊天系统客户端强制关闭导致服务端报错问题解决(完结篇)

关于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.其他

无了!!!!!

这里只是做功能拓展,不再做具体实现,如果有其他问题也可积极讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值