程序设计十二:线上聊天室(网络编程+多线程)

程序设计十二:网络编程

在这里插入图片描述

1.Manager类

使用Manager类实现服务器功能:

_recv()内部方法接收消息,

_broadcast()方法向所有用户广播,

_private_chat()方法发送私信(定向转发),

chat()方法实现收发消息的完整过程,并实现所有聊天记录的保存

class Manager:
    def __init__(self, conn, addr, username=''):  # 实质是建立了一个客户端对象,addr从accept中获取
        self.socket = conn
        self.ip = addr[0]
        self.port = addr[1]
        self.identify = '{}-{}'.format(self.ip, self.port)
        self.username = username

    def _recv(self, conn):  # 接收消息
        while True:
            data = conn.recv(BS).decode("utf-8")
            if not data or data == '再见!':
                break
            else:
                return data

    def _broadcast(self, message, sender):  # 发送广播
        for identify in clients.keys():
            if sender != usernames[identify]:
                clients[identify].socket.send(('%s %s:%s' % (get_time(), sender, message)).encode("utf-8"))

    def _private_chat(self, message, sender, receiver):  # 发送私信
        for identify in clients.keys():
            if usernames[identify] == receiver:
                clients[identify].socket.send(('%s %s[私信]:%s' % (get_time(), sender, message)).encode("utf-8"))
                break

    def chat(self):  # 与客户端交互:收发消息
        log = open('server_log.txt', 'a')
        log.write('\n#### 服务器数据 ####\n')
        print('{}尝试连接'.format(self.identify))
        log.write('{}尝试连接\n'.format(self.identify))
        name = self._recv(self.socket)
        if not name:
            return
        self.username = name
        clients[self.identify] = self  # 客户列表中加入客户 { identify:client }
        usernames[self.identify] = self.username
        # 处理连接成功信息
        print('用户 %s 已连接!' % self.username)
        log.write('用户 %s 已连接!\n' % self.username)
        self.socket.send("您已连接".encode("utf-8"))
        user_connect_message = ('我已进入聊天室')
        self._broadcast(user_connect_message, self.username)
        # 开始聊天
        while True:
            message = self._recv(self.socket)
            if not message:
                break
            elif message.split(' ')[0] == '@':  # 私信
                receiver = message.split(' ')[1]
                self._private_chat(message, self.username, receiver)
                print("%s (%s) %s private chat with %s: %s" %
                      (get_time(), self.identify, self.username, receiver, message))
                log.write("%s (%s) %s private chat with %s: %s\n" %
                          (get_time(), self.identify, self.username, receiver, message))
            else:  # 广播
                print("%s (%s) %s: %s" % (get_time(), self.identify, self.username, message))
                log.write("%s (%s) %s: %s\n" % (get_time(), self.identify, self.username, message))
                self._broadcast(message, self.username)
        # 断开连接:信息发送、用户删除、连接终止
        print("%s(%s) 断开连接" % (self.ip, self.port))
        log.write("%s(%s) 断开连接\n" % (self.ip, self.port))
        user_exit_message = ('再见!\n%s已退出聊天室' % self.username)
        self._broadcast(user_exit_message, self.username)
        clients.pop(self.identify)
        usernames.pop(self.identify)
        log.close()
        self.socket.close()

2.Chatter类

Chatter类实现用户端,send()发送消息并写入日志,recv()接收消息并写入日志。

class Chatter:
    def __init__(self, conn):
        self.socket = conn

    def send(self):
        username = input('用户名:')
        self.socket.send(username.encode('utf-8'))
        log.write('\n##### 用户 %s 聊天记录#####\n'%username)
        while not exit_event.is_set():
            message = input('')
            log.write(message+'\n')
            self.socket.send(message.encode('utf-8'))
            if message == '再见!':
                exit_event.set()

    def recv(self):
        while not exit_event.is_set():
            message = self.socket.recv(BS).decode('utf-8')
            print(message)
            log.write(message+'\n')

3.服务器的实现

创建多个线程,实现用同一个端口服务多个用户,代码如下:

def main():
    if len(sys.argv) != 3:
        print('usage: python my_server.py ip port')
    else:
        ip = sys.argv[1]
        port = int(sys.argv[2])
        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server.bind((ip, port))  # 设置绑定监听的ip和port
        server.listen(10)
        print("服务器已开启,正在监听{}".format(server.getsockname()))

        while True:
            conn, addr = server.accept()  # 接受客户端连接,获取其地址
            client = Manager(conn, addr)  # 创建客户
            t = Thread(target=client.chat, args=())
            t.start()

4.用户端的实现

每个用户端启用两个线程,分别处理接收和发送代码如下:

def main():
    if len(sys.argv) != 3:
        print('usage: python my_client.py ip port')
    else:
        ip = sys.argv[1]
        port = int(sys.argv[2])
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            client.connect((ip, port))
            chat1 = Chatter(client)
            chat2 = Chatter(client)

            send_thread = Thread(target=chat1.send, args=())
            recv_thread = Thread(target=chat2.recv, args=())
            send_thread.start()
            recv_thread.start()
            send_thread.join()
            recv_thread.join()
        finally:
            print("连接已被关闭")
            client.close()

5.多机测试结果

启动多台电脑作为用户,连接服务器,构成聊天室,测试结果如下:

服务器窗口截图:

在这里插入图片描述

用户一窗口截图:

在这里插入图片描述

用户二窗口截图:

在这里插入图片描述

用户三窗口截图:

在这里插入图片描述

从上述结果可以看到:

  1. 每个人都可以向聊天室发送消息,消息会广播给除自己以外的其他人
  2. 用户之间可以发送私信,私信格式为 @ <receiver> <message>且私信不会被其他人看到
  3. 用户发送'再见!'时会自动退出聊天室,此时聊天室会发送广播告知用户已经退出

6.聊天记录保存

用户退出聊天室之后,会自动生成'client_log.txt'文件,保存该用户的聊天记录,上述跨机演示结束后,自动保存的聊天记录如下:

用户1

在这里插入图片描述

用户2

在这里插入图片描述

同时,服务器也会保存所用的聊天数据,并按照用户分类储存,截图如下:

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值