套接字实现基于网络的简易聊天室
客户端还有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 来连接服务器,在子线程中接收服务器发来的消息,由此一个简单的聊天室程序就完成了。
客户端
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()