python之使用socket和threading多线程,编写全双工多人聊天服务器

能在局域网内实现多人聊天

一、先讲一讲服务器端

其中有个get模块是自己写的
需要把目录标记为源

在网上看过不少,但是大多数都不能用。。或者很粗糙,没有做什么异常处理
我写的这个经过了许多的测试,做了比较全面的异常处理,应该不会爆红字

说一下服务器端的编写思路:

主要思想
显然对于多用户来说,我们肯定不选择用户之间直接连接,否则用户一多,就成了复杂的网状结构,显然即占用端口,又对网络资源有很高的消耗,效率低下。
所以我们编写服务器作为中转站,这样每个用户都只需要和服务器建立一个连接,然后服务器接受每个用户发来的消息,然后转发给所有其他在线用户,这样便是星型结构,十分方便且利于管理。

在这里插入图片描述

  1. 我们要利用python的特点,用一个类来封装,条理清晰,也方便以后的调用
  2. 有三个初始化属性
    (1)一个socket套接字
    (2)一个元组,包含本机IP和指定的端口(不要急着绑定,防止持续占用端口)
    (3)作为服务器端,要统计在线用户,显然我们要用字典,而不是列表
  3. 如何与多用户建立连接
    (1)开启服务后,绑定端口(这里不确定端口是否被占用,所以加一个捕获异常语句),绑定成功后开始监听请求,一旦收到连接请求,就正式开始accept
    (2)因为服务器要连接多个用户,所以必须保持监听状态,这里用while,每建立一个连接,就开启一个线程(Thread模块上场),专门为这位用户服务,然后这个线程就一边玩去了。回头继续监听接下来的连接请求。
    (3)每当有一个用户连接,便把与当前用户建立的socket放入存放用户的字典,key是用户名(客户端在连接时会要求用户输入一个用户名)然后统计当前用户数量,并打印出来。
  4. 接下来说一说每一个线程
    (1)每个线程将会一直循环接收当前用户发来的消息,并且在服务端显示信息。
    (2)接受到消息后,用户字典中的每一个socket都会立刻把此信息发送给自己对应的用户,这就实现了服务器的转发功能。
  5. 最后来考虑异常处理
    (1)某个用户自行退出时,服务器端将会在recv函数这报错ConnectionResetError
    所以我们要做的就是捕获以上信息,然后服务器端将会向所有用户转发,“xxx用户已退出聊天
    然后从用户字典中移除该用户的socket。
    (2)当服务器被强行关闭时,用户端也会报错,我们同样捕获后,告知用户“服务器已关闭”,随后客户端将自动关闭

下面的代码基本是按照上述思路一步步写的,正是因为我自己没找到很好的教程文章,所以掉坑无数。。于是今日写了这边文章,希望能帮到大家

后文将会有客户端代码的讲解

import socket
import get  # 自己写的
import threading
import os


class ChatSever:

    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.addr = (get.get_ip(), 10000)
        self.users = {}

    def start_sever(self):
    	try:
        	self.sock.bind(self.addr)
        except Exception as e:
        	print(e)
        self.sock.listen(5)
        print("服务器已开启,等待连接...")
        print("在空白处输入stop sever并回车,来关闭服务器")

        self.accept_cont()

    def accept_cont(self):
        while True:
            s, addr = self.sock.accept()
            self.users[addr] = s
            number = len(self.users)
            print("用户{}连接成功!现在共有{}位用户".format(addr, number))

            threading.Thread(target=self.recv_send, args=(s, addr)).start()
            
    def recv_send(self, sock, addr):
        while True:
            try:  # 测试后发现,当用户率先选择退出时,这边就会报ConnectionResetError
                response = sock.recv(4096).decode("gbk")
                msg = "{}用户{}发来消息:{}".format(get.get_time(), addr, response)

                for client in self.users.values():
                    client.send(msg.encode("gbk"))
            except ConnectionResetError:
                print("用户{}已经退出聊天!".format(addr))
                self.users.pop(addr)
                break

    def close_sever(self):
        for client in self.users.values():
            client.close()
        self.sock.close()
        os._exit(0)


if __name__ == "__main__":
    sever = ChatSever()
    sever.start_sever()
    while True:
        cmd = input()
        if cmd == "stop sever":
            sever.close_sever()
        else:
            print("输入命令无效,请重新输入!")

get模块如下

import socket
import datetime


def get_ip():
    """用来搞到IP"""
    host = socket.gethostname()
    ip = socket.gethostbyname(host)
    return ip


def get_time():
    """得到发送时间"""
    now = datetime.datetime.now()
    send_time = now.strftime("%Y-%m-%d %H:%M:%S")
    return send_time

二、客户端:

如果你理解了服务端,那么客户端想必你早已胸有成竹
就是一个socket,建立连接后,扔两个线程,一个发消息,一个接收消息。

import socket
import threading
import os
import get

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addr = (get.get_ip(), 10000)
s.connect(addr)


def recv_msg():  #
    print("连接成功!现在可以接收消息!\n")
    while True:
        try:  # 测试发现,当服务器率先关闭时,这边也会报ConnectionResetError
            response = s.recv(1024)
            print(response.decode("gbk"))
        except ConnectionResetError:
            print("服务器关闭,聊天已结束!")
            s.close()
            break
    os._exit(0)


def send_msg():
    print("连接成功!现在可以发送消息!\n")
    print("输入消息按回车来发送")
    print("输入esc来退出聊天")
    while True:
        msg = input()
        if msg == "esc":
            print("你退出了聊天")
            s.close()
            break
        s.send(msg.encode("gbk"))
    os._exit(0)


threads = [threading.Thread(target=recv_msg), threading.Thread(target=send_msg)]
for t in threads:
    t.start()

欢迎改进意见orz

发布了28 篇原创文章 · 获赞 74 · 访问量 1664

猜你喜欢

转载自blog.csdn.net/CxsGhost/article/details/103319864