套接字实现基于网络的简易聊天室
客户端还有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('\n-->',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 {} \n'.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'\n') 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.\n').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.\n').encode("utf-8")) def do_say(self, session, line): # 客户端发送消息 self.broadcast((session.name + ': ' + line + '\n').encode("utf-8")) def do_look(self, session, line): # 查看在线用户 session.push(b'Online Users:\n') for other in self.sessions: session.push((other.name + '\n').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)) + '\n').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 + '\n').encode("utf-8")) self.message.Clear() def lookUsers(self, event): # 查看当前在线用户 con.write(b'look\n') def close(self, event): # 关闭窗口 con.write(b'logout\n') 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()