之前在学习廖雪峰老师的python3教程,看到socket的那一章时,有一个想法突然冒出来,是不是可以通过socket做一个简单的聊天室软件,在构思了一段时间后,废话不多说,开始实施:
按照自定的报文格式构造和解析消息在客户端完成,服务端只负责接受消息和分发消息。
了解过web的同学应该知道http协议,http协议是人们规定的一个超文本传输协议,一般是用作web的,而在协议中又分别有请求报文与响应报文,我们今天的聊天室也需要一个报文,但不需要http协议的报文那么复杂,我们自己制定一个简单的报文。
方法有n(normal) , e(enter) , q(quit)
enter方法:e-$-timestamp-$-name
,不需要提供内容,需要时间戳和用户名。
quit方法:q-$-name
,不需要提供时间戳和内容,只需提供用户名。
chatroom_client.py
import tkinter as tk
import tkinter.messagebox
import socket
import datetime
import os
import threading
import time
def analyze_and_build(raw):
rawlist=raw.decode('utf-8').split('-$-')
method=rawlist[0]
if method=='n':
time=datetime.datetime.fromtimestamp(float(rawlist[1])).strftime('%Y-%m-%d %H:%M:%S')
name=rawlist[2]
content=rawlist[3]
return '%s\n%s : %s\n\n' % (time,name,content)
elif method=='e':
time=datetime.datetime.fromtimestamp(float(rawlist[1])).strftime('%Y-%m-%d %H:%M:%S')
name=rawlist[2]
return '%s\n%s加入了聊天\n\n' % (time,name)
elif method=='q':
name=rawlist[1]
return '%s退出了聊天\n\n' % name
def mainview():
name=nameEntry.get()
host=hostEntry.get()
port=portEntry.get()
if name=='' or host=='' or port=='':
tkinter.messagebox.showinfo(title='login failed',message='Value cannot be empty!')
else:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,int(port)))
loginWindow.destroy()
mainWindow=tk.Tk()
mainWindow.title('Chatroom(%s)' % name)
mainWindow.geometry('440x390')
showText=tk.Text(mainWindow,height=20,width=60)
emptyLabel=tk.Label(mainWindow,height=1)
writeText=tk.Text(mainWindow,height=4,width=60)
showText.tag_config('red',foreground='red')
showText.pack()
emptyLabel.pack()
writeText.pack()
s.send(('e-$-%s-$-%s' % (datetime.datetime.now().timestamp(),name)).encode('utf-8'))
def fresh():
while True:
data=s.recv(1024)
rawlist=data.decode('utf-8').split('-$-')
if (rawlist[0]=='n' or rawlist[0]=='e') and rawlist[2]==name:
showText.insert('end',analyze_and_build(data),'red')
else:
showText.insert('end',analyze_and_build(data))
showText.see('end')
t1=threading.Thread(target=fresh)
t1.start()
def send_message():
data=writeText.get('0.0','end')
if data=='\n':
pass
else:
s.send(('n-$-%s-$-%s-$-%s' % (datetime.datetime.now().timestamp(),name,data)).encode('utf-8'))
writeText.delete('0.0','end')
sendButton=tk.Button(mainWindow,text='send',width=5,height=1,command=send_message)
sendButton.pack()
def quit():
s.send(('q-$-%s' % name).encode('utf-8'))
time.sleep(0.5)
s.send('_quitchatroom'.encode('utf-8'))
mainWindow.destroy()
os._exit(0)
mainWindow.protocol('WM_DELETE_WINDOW',quit)
mainWindow.mainloop()
loginWindow=tk.Tk()
loginWindow.title('login')
loginWindow.geometry('200x250')
loginLabel=tk.Label(loginWindow,
text='Login',
font=('Arial',15),
width=10,height=2)
nameLabel=tk.Label(loginWindow,
text='name:',
font=('Arial',10),
width=6,height=1)
hostLabel=tk.Label(loginWindow,
text='host:',
font=('Arial',10),
width=6,height=1)
portLabel=tk.Label(loginWindow,
text='port:',
font=('Arial',10),
width=6,height=1)
nameEntry=tk.Entry(loginWindow)
hostEntry=tk.Entry(loginWindow)
portEntry=tk.Entry(loginWindow)
loginButton=tk.Button(loginWindow,
text='login',
width=10,
height=1,
command=mainview)
loginLabel.pack()
nameLabel.pack()
nameEntry.pack()
hostLabel.pack()
hostEntry.pack()
portLabel.pack()
portEntry.pack()
loginButton.pack()
loginWindow.mainloop()
chatroom_server.py
import socket,threading,os
host=os.environ.get('HOST') or '127.0.0.1'
port=os.environ.get('PORT') or 9999
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((host,int(port)))
s.listen(5)
print('Waiting for connection...')
socks={}
def tcplink(sock,addr):
global socks
print('Accept new connection from %s:%s...' % addr)
while True:
data=sock.recv(1024)
if data.decode('utf-8')=='_quitchatroom':
socks.pop(sock)
break
for eachsock in socks:
eachsock.send(data)
sock.close()
print('Connection from %s:%s closed.' % addr)
while True:
sock,addr=s.accept()
socks[sock]=addr
t=threading.Thread(target=tcplink,args=(sock,addr))
t.start()
1.打开聊天室服务器
2.打开聊天室客户端
3.填写信息
4.成功登陆,出现聊天室窗口
5.输入不同人名,相同地址与端口,再打开一个客户端。
6.聊天与退出
至此,这个聊天室就基本完成了,使用时间戳的原因是因为时间戳不包含时区,然后再在本地转换,但是这个聊天室还是有很多缺点的,比如很多地方没有验证,可能会出现错误、窗口没有美化等等,如果有人有兴趣完善的话,那真的要给你点一个大大的赞,如果有需要,还可以用pyinstaller打包成exe文件,那样使用就会很方便。
如果你有什么意见和建议,欢迎在评论区留言。