zoukankan      html  css  js  c++  java
  • 套接字实现基于网络的简易聊天室

    套接字实现基于网络的简易聊天室

    客户端还有bug,代码还能继续优化,日后有时间再修改。

    服务端代码

    import  socketserver
    import hmac
    import os
    
    secret_key=b'wenli bang bang bang'
    
    
    # 验证客户端合法性
    def conn_auth(conn):
    
        print('开始验证客户端合法性')
        #生成32位的随机数
        msg=os.urandom(32)
        #发送信息
        conn.sendall(msg)
        #进行加盐
        h=hmac.new(secret_key,msg)
        #转为数字型
        digest=h.digest()
        response = conn.recv(len(digest))
        return hmac.compare_digest(response,digest)
    
    
    
    
    
    # 继承一个类,每次来一个连接就实例化一个连接,实例与实例之间互相没有影响
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
            global renshu
            print('conn is:', self.request)
            print('Client addr is:', self.client_address)
            conn = self.request
    
            #验证客户端合法性
            if not conn_auth(conn):
                print('该链接不合法,关闭连接')
                conn.close()
    
            Client_list.append(conn)
    
    
             # 在线人数
            renshu = len(Client_list)
    
            print('链接合法,开始通信')
    
    
            #进行信息通信
            while True:
                try:
                    # 收信息
                    data = conn.recv(1024)
                    if not data: break
                    print(data.decode('utf-8'))
    
                    # 发信息
                    for i in range(renshu):
                        print(Client_list,renshu)
                        print(1)
                        if conn is Client_list[i]: continue
                        Client_list[i].sendall(data)
                        print(conn,Client_list[i])
                except Exception as e:
                    print(e)
                    break
            conn.close()
    
    
    
    if __name__ == '__main__':
        ip=input('输入服务端的ip地址:')
        port=4444
        Client_list=[]
        renshu=0
        #ThreadingTCPServer相当于外层循环,Myserver相当于里层循环
        s=socketserver.ThreadingTCPServer((ip,port),Myserver)
        #启动服务
        print('服务端已启动')
        s.serve_forever()
    

    客户端

    from socket import *
    import socket
    import threading
    import hmac
    
    
    secret_key=b'wenli bang bang bang'
    
    
    #验证客户端合法性
    def conn_auth(conn):
        print('开始进行通信认证')
        msg=conn.recv(32)
        h=hmac.new(secret_key,msg)
        digest=h.digest()
        conn.sendall(digest)
    
    
    
    #接收信息
    def recevie(s2,buffer_size):
        while True:
            reponse = s2.recv(buffer_size)
            if not reponse:continue
            print('
    -->',reponse.decode('utf-8'))
    
    #发送信息
    def send(s2,name):
        while True:
            data = input('-->')
            if not data: continue  # 如果客户端输入信息为空,则继续下一次循环
            if data == 'quite': break
            data = name + ':' + data
            s2.send(data.encode('utf-8'))
        s2.close()
    
    
    #通信
    def commucation(s2,buffer_size,name):
        t1 = threading.Thread(target=recevie,args=(s2,buffer_size))
        t2 = threading.Thread(target=send,args=(s2,name))
        t1.start()
        t2.start()
        t1.join()
        t2.join()
    
    
    def main(ip,port):
        ip_port = (ip,port)
        buffer_size = 1024
        s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s2.connect(ip_port)  # 连接服务端#
        #进行基于连接的认证
        conn_auth(s2)
        print('验证通过')
    
        #输入姓名
        name = ''
        while name == '':
            name = input('请输入您的名字:')
    
        #通讯
        commucation(s2,buffer_size,name)
    
    
    
    if __name__ == '__main__':
        ip=input('输入你的机器的ip地址:')
        port=4444
        main(ip,port)
    

      

    服务端

    import asynchat
    import asyncore
    
    
    # 定义端口
    PORT = 6666
    
    
    class CommandHandler:
        """
        命令处理类
        """
    
        def unknown(self, session, cmd):
            # 响应未知命令
            # 通过 asynchat.async_chat.push 方法发送消息
            session.push(('Unknown command {} 
    '.format(cmd)).encode("utf-8"))
    
        def handle(self, session, line):
            line = line.decode()
            # 命令处理
            if not line.strip():
                return
            parts = line.split(' ', 1)
            cmd = parts[0]
            try:
                line = parts[1].strip()
            except IndexError:
                line = ''
            # 通过协议代码执行相应的方法
            method = getattr(self, 'do_' + cmd, None)
            try:
                method(session, line)
            except TypeError:
                self.unknown(session, cmd)
    
    
    #这里我们首先需要一个聊天服务器类,通过继承 asyncore 的 dispatcher 类来实现
    # 定义结束异常类
    class EndSession(Exception):
        pass
    
    
    class ChatServer(asyncore.dispatcher):
        """
        聊天服务器
        """
    
        def __init__(self, port):
            asyncore.dispatcher.__init__(self)
            # 创建socket
            self.create_socket()
            # 设置 socket 为可重用
            self.set_reuse_addr()
            # 监听端口
            self.bind(('127.0.0.1', port))
            self.listen(5)
            self.users = {}
            self.main_room = ChatRoom(self)
    
        def handle_accept(self):
            conn, addr = self.accept()
            ChatSession(self, conn)
    
    
    
    
    
    
    class ChatSession(asynchat.async_chat):
        """
        负责和客户端通信
        """
    
        def __init__(self, server, sock):
            asynchat.async_chat.__init__(self, sock)
            self.server = server
            self.set_terminator(b'
    ')
            self.data = []
            self.name = None
            self.enter(LoginRoom(server))
    
        def enter(self, room):
            # 从当前房间移除自身,然后添加到指定房间
            try:
                cur = self.room
            except AttributeError:
                pass
            else:
                cur.remove(self)
            self.room = room
            room.add(self)
    
        def collect_incoming_data(self, data):
            # 接收客户端的数据
            self.data.append(data.decode("utf-8"))
    
        def found_terminator(self):
            # 当客户端的一条数据结束时的处理
            line = ''.join(self.data)
            self.data = []
            try:
                self.room.handle(self, line.encode("utf-8"))
            # 退出聊天室的处理
            except EndSession:
                self.handle_close()
    
        def handle_close(self):
            # 当 session 关闭时,将进入 LogoutRoom
            asynchat.async_chat.handle_close(self)
            self.enter(LogoutRoom(self.server))
    
    
    class Room(CommandHandler):
        """
        包含多个用户的环境,负责基本的命令处理和广播
        """
    
        def __init__(self, server):
            self.server = server
            self.sessions = []
    
        def add(self, session):
            # 一个用户进入房间
            self.sessions.append(session)
    
        def remove(self, session):
            # 一个用户离开房间
            self.sessions.remove(session)
    
        def broadcast(self, line):
            # 向所有的用户发送指定消息
            # 使用 asynchat.asyn_chat.push 方法发送数据
            for session in self.sessions:
                session.push(line)
    
        def do_logout(self, session, line):
            # 退出房间
            raise EndSession
    
    
    class LoginRoom(Room):
        """
        处理登录用户
        """
    
        def add(self, session):
            # 用户连接成功的回应
            Room.add(self, session)
            # 使用 asynchat.asyn_chat.push 方法发送数据
            session.push(b'Connect Success')
    
        def do_login(self, session, line):
            # 用户登录逻辑
            name = line.strip()
            # 获取用户名称
            print(name)
            if not name:
                print(1)
                session.push(b'UserName Empty')
            # 检查是否有同名用户
            elif name in self.server.users:
                session.push(b'UserName Exist')
            # 用户名检查成功后,进入主聊天室
            else:
                session.name = name
                session.enter(self.server.main_room)
    
    
    class LogoutRoom(Room):
        """
        处理退出用户
        """
    
        def add(self, session):
            # 从服务器中移除
            try:
                del self.server.users[session.name]
            except KeyError:
                pass
    
    
    class ChatRoom(Room):
        """
        聊天用的房间
        """
    
        def add(self, session):
            # 广播新用户进入
            session.push(b'Login Success')
            self.broadcast((session.name + ' has entered the room.
    ').encode("utf-8"))
            self.server.users[session.name] = session
            Room.add(self, session)
    
        def remove(self, session):
            # 广播用户离开
            Room.remove(self, session)
            self.broadcast((session.name + ' has left the room.
    ').encode("utf-8"))
    
        def do_say(self, session, line):
            # 客户端发送消息
            self.broadcast((session.name + ': ' + line + '
    ').encode("utf-8"))
    
        def do_look(self, session, line):
            # 查看在线用户
            session.push(b'Online Users:
    ')
            for other in self.sessions:
                session.push((other.name + '
    ').encode("utf-8"))
    
    if __name__ == '__main__':
    
        s = ChatServer(PORT)
        try:
            print("chat server run at '127.0.0.1:{0}'".format(PORT))
            asyncore.loop()
        except KeyboardInterrupt:
            print("chat server exit")
    
    # 最后就可以运行程序进行聊天了,注意需要先启动服务器再启动客户端。这个项目中使用了 asyncore 的 dispatcher 来实现服务器,asynchat 的 asyn_chat 来维护用户的连接会话,用 wxPython 来实现图形界面,用 telnetlib 来连接服务器,在子线程中接收服务器发来的消息,由此一个简单的聊天室程序就完成了。
    View Code

    客户端

    import wx
    import telnetlib
    from time import sleep
    import _thread as thread
    
    class LoginFrame(wx.Frame):
        """
        登录窗口
        """
        def __init__(self, parent, id, title, size):
            # 初始化,添加控件并绑定事件
            wx.Frame.__init__(self, parent, id, title)
            self.SetSize(size)
            self.Center()
            self.serverAddressLabel = wx.StaticText(self, label="Server Address", pos=(10, 50), size=(120, 25))
            self.userNameLabel = wx.StaticText(self, label="UserName", pos=(40, 100), size=(120, 25))
            self.serverAddress = wx.TextCtrl(self, pos=(120, 47), size=(150, 25))
            self.userName = wx.TextCtrl(self, pos=(120, 97), size=(150, 25))
            self.loginButton = wx.Button(self, label='Login', pos=(80, 145), size=(130, 30))
            # 绑定登录方法
            self.loginButton.Bind(wx.EVT_BUTTON, self.login)
            self.Show()
    
        def login(self, event):
            # 登录处理
            try:
                serverAddress = self.serverAddress.GetLineText(0).split(':')
                print(serverAddress)
                con.open(serverAddress[0], port=int(serverAddress[1]), timeout=10)
                response = con.read_some()
                if response != b'Connect Success':
                    self.showDialog('Error', 'Connect Fail!', (200, 100))
                    return
                con.write(('login ' + str(self.userName.GetLineText(0)) + '
    ').encode("utf-8"))
                response = con.read_some()
                if response == b'UserName Empty':
                    self.showDialog('Error', 'UserName Empty!', (200, 100))
                elif response == b'UserName Exist':
                    self.showDialog('Error', 'UserName Exist!', (200, 100))
                else:
                    self.Close()
                    ChatFrame(None, 2, title='ShiYanLou Chat Client', size=(500, 400))
            except Exception:
                self.showDialog('Error', 'Connect Fail!', (95, 20))
    
        def showDialog(self, title, content, size):
            # 显示错误信息对话框
            print(1)
            dialog = wx.Dialog(self, title=title, size=size)
            dialog.Center()
            wx.StaticText(dialog, label=content)
            dialog.ShowModal()
    
    class ChatFrame(wx.Frame):
        """
        聊天窗口
        """
    
        def __init__(self, parent, id, title, size):
            # 初始化,添加控件并绑定事件
            wx.Frame.__init__(self, parent, id, title)
            self.SetSize(size)
            self.Center()
            self.chatFrame = wx.TextCtrl(self, pos=(5, 5), size=(490, 310), style=wx.TE_MULTILINE | wx.TE_READONLY)
            self.message = wx.TextCtrl(self, pos=(5, 320), size=(300, 25))
            self.sendButton = wx.Button(self, label="Send", pos=(310, 320), size=(58, 25))
            self.usersButton = wx.Button(self, label="Users", pos=(373, 320), size=(58, 25))
            self.closeButton = wx.Button(self, label="Close", pos=(436, 320), size=(58, 25))
            # 发送按钮绑定发送消息方法
            self.sendButton.Bind(wx.EVT_BUTTON, self.send)
            # Users按钮绑定获取在线用户数量方法
            self.usersButton.Bind(wx.EVT_BUTTON, self.lookUsers)
            # 关闭按钮绑定关闭方法
            self.closeButton.Bind(wx.EVT_BUTTON, self.close)
            thread.start_new_thread(self.receive, ())
            self.Show()
    
        def send(self, event):
            # 发送消息
            message = str(self.message.GetLineText(0)).strip()
            if message != '':
                con.write(('say ' + message + '
    ').encode("utf-8"))
                self.message.Clear()
    
        def lookUsers(self, event):
            # 查看当前在线用户
            con.write(b'look
    ')
    
        def close(self, event):
            # 关闭窗口
            con.write(b'logout
    ')
            con.close()
            self.Close()
    
        def receive(self):
            # 接受服务器的消息
            while True:
                sleep(0.6)
                result = con.read_very_eager()
                if result != '':
                    self.chatFrame.AppendText(result)
    
    if __name__ == '__main__':
        app = wx.App()
        con = telnetlib.Telnet()
        LoginFrame(None, -1, title="Login", size=(320, 250))
        app.MainLoop()
    View Code
  • 相关阅读:
    [Effective JavaScript 笔记] 第7条:视字符串为16位的代码单元序列
    [翻译]CSS模块-未来的编码方式
    [Effective JavaScript 笔记] 第6条:了解分号插入的局限
    [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符
    [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象
    [翻译]理解CSS模块方法
    [翻译]纠正PostCSS的4大认识误区
    [翻译]Gulp.js简介
    [Effective JavaScript笔记]第3条:当心隐式的强制转换
    [翻译]在gulp构建工具中使用PostCSS
  • 原文地址:https://www.cnblogs.com/-wenli/p/10264270.html
Copyright © 2011-2022 走看看