基于python的匿名聊天室

想法

刚有了自己的服务器总有点手痒痒,想写点什么东西用一用。
感觉聊天室挺有意思,也比较常用,方便找一些参考。

构造

因为刚入门python,想练练手,于是就打算用python了

打算用socket和threading来做收发消息的主题部分,然后图形界面听说PyQt不错,差不多基本的部分就用这些模块了~

做法就是常见的socket连接部分:服务器监听,客户端连接,然后实现通信,

我的消息传递用了特殊的格式(协议):

1
2
3
4
5
6
#客户端向服务端 
<text><dio>"the world"
#客户dio向大厅发送了 the world 这句话 <command><jack>"change name to jostar"
#客户jack向服务端发出命令:把名字改为jostar
#服务器向客户端 <text>dio:the world
#服务端向某个客户端发送消息并打印

再使用checkmes函数解析这些明文命令

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def checkmes(mes):
pos_1 = mes.find('<')
pos_2 = mes.find('>', pos_1)
pos_3 = mes.find('<', pos_2)
pos_4 = mes.find('>', pos_3)
status = mes[pos_1 + 1:pos_2]
person = mes[pos_3 + 1:pos_4]
message = mes[pos_4 + 2:-1]
if status == 'command':
servercommand(person,message)
elif status == 'text':
serversendtext(person, message)
elif status == 'jpg':
try:
os.remove('png.txt')
except:
pass
file = open('jpg.txt', 'wb')
file.write(message)
file.close()
serversendimg(status,person, message)
elif status == 'png':
try:
os.remove('jpg.txt')
except:
pass
file = open('png.txt', 'wb')
file.write(message)
file.close()
serversendimg(status,person,message)

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def checkmes(ui,mes):
pos_1 = mes.find('<')
pos_2 = mes.find('>', pos_1)
status = mes[pos_1 + 1:pos_2]
message = mes[pos_2 + 1:]
if status == 'command':
clientcommand(ui,message)
elif status == 'text':
tm=time.localtime()
ui.listWidget.addItem("%s/%s %s:%s" % (tm.tm_mon, tm.tm_mday, str(tm.tm_hour).rjust(2,'0'), str(tm.tm_min).rjust(2,'0')))
ui.listWidget.addItem(message)
ui.listWidget.setCurrentRow(ui.listWidget.count()-1)
elif status == 'namelist':
setnamelist(ui, message)
elif status == 'jpg':
message=message[:-1]
image_data = base64.b64decode(message)
f = open('result.jpg', 'wb')
f.write(image_data)
f.close()
if os.path.exists('rusult.png'):
os.remove('rusult.png')
pixmap = QPixmap("result.jpg")
ui.label.setPixmap(pixmap)
elif status == 'png':
message = message[:-1]
image_data = base64.b64decode(message)
f = open('result.png', 'wb')
f.write(image_data)
f.close()
if os.path.exists('rusult.jpg'):
os.remove('rusult.jpg')
pixmap = QPixmap("result.png")
ui.label.setPixmap(pixmap)

这样服务器可以把收到的东西直接打印在屏幕上,方便调试和监视。

socket连接

这是整个聊天室最核心的部分,

思路是用户端连接服务器固定的地址和端口,服务器端检测到连接就创建一个用户列表,然后每个用户向服务器发送消息时,服务器再按用户列表分发消息。

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
host = '175.24.65.175'
port = 20001
# 连接客户端
client.connect((host, port))
name='guest'
client.send(('<command><%s>"join the chatroom"' % name).encode('utf-8'))
ui.listWidget.addItem('已连接到服务器')
# 建立接受信息,线程对象
t1 = threading.Thread(target=indatas, name='input', args=(ui,))
# 启动多线程
t1.start()
app.exec_()
exit_value = 0
client.send(('<command><%s>"close"' % name).encode('utf-8'))

如代码所示,在一连接成功后就发送<command><%s>"join the chatroom
使服务端能够知道这是一个新的连接,以及该用户的名称。

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def accept():
while True:
client, addr = server.accept()
clients.append(client)

firstcall = client.recv(1024)
print(firstcall)
checkmes(firstcall)
#firstcall是用户将会发送加入用户列表的指令,如<command><jojo>"join the chatroom"
printnowtime()
print('服务器被'+str(addr)+'连接,当前连接:'+str(guests))

t3 = threading.Thread(target=accept, name='accept')
t3.start()

checkmes函数中解析命令将用户添加到用户列表里。

图形化界面

pyqt的图形化界面还不错,基本上就是在qt designer里图形化界面拖一拖(有点像高中vb的感觉),然后可以导出个python脚本。
qt designer的入门攻略
让你的客户端脚本import一下,然后再在官方文档里面找找对应的类的调用方式。
类似这种

1
2
3
4
5
6
7
8
9
import chatroom                #chatroom.py就是qt designer导出的py了
app = QApplication(sys.argv)
MainWindow = QMainWindow()
ui = chatroom.Ui_MainWindow() #Ui_MainWindow是chatroom.py中的是一个窗口类
ui.setupUi(MainWindow) #Ui初始化
MainWindow.show() #窗口开始显示
ui.pushButton.clicked.connect(partial(click_send, ui))
#为ui这个窗口类中pushButtom的clicked方法绑定上函数
#因为只能传递一个参数,所以使用partial函数同时传递两个参数

稍微设置一下大概就是这个样子

图片的传输

光有文字太无聊了。
本来计划是想有像社交聊天软件的聊天图片,可以随意镶嵌在文字的聊天记录之间。
但是考虑到文件的存储,以及服务器的带宽并不能顶得住大堆的图片。所以就改成了只能分享一张图片,也正好能符合匿名聊天室的初衷。
由于除了文件的二进制还需要一些字符来告诉服务器这是张图片,以及图片已经传输完了。直接传二进制文件可能会遇到一些字符编码导致的传输上的错误。
于是base64就派上了用场。
分享图片后就能用<jojo><jpg>"OHFf03jIJVEW03H一堆base64/ejpfwej98=="来传输,用引号定位开始和结束。
为了减少服务器的cpu使用,base64后的图片就直接以文本形式存在服务器,所有用户进入就能接收到这张图片。
客户端接收到这张图片会直接保存在当前目录(方便保存)
一旦有新的用户分享图片,前一张的所有记录都会被删除,包括本地和服务端(除非提前另存为),这也是出于匿名聊天室的考虑。

打包

由于python的通用性,用pyinstaller之类的工具就能直接打包成exe文件,也想过导出为app文件(mac上的可执行文件),但是压缩之后传输过去,再在mac上打开时,其中_exec这个文件的可执行权限会被自动修改,于是就打不开了。
虽然可以直接命令行chmod更改权限,但是打包的初衷就是为了不会这些计算机命令的人也能使用(以及懒得装环境的)。
后来发现了可以打包成dmg文件,是mac上的压缩镜像文件,再用来安装app,这样就不会遇到那些权限的问题了。(想了想怪不得之前在mac上下载qq、迅雷啥的都是下载了一个dmg文件)

部署

启动脚本

1
2
3
#!/bin/sh
kill -9 $(lsof -i:20001|tail -1|awk '"$1"!=""{print $2}')
nohup python -u host.py > run.log 2>&1 &

总结

整体来说学到了挺多的,也知道了一些协议和编码为什么要这样规定。
仔细一想,给我这个小程序加个自动截图上传的功能是不是就是低配版的 雷课堂 了?
具体源码已上传到github
https://github.com/Jacobianoffame/pychatroom

后记

再看了一些项目之后回头来看,发现有许多部分有一些错误和漏洞,比如说命令之类的也是明文传输,再用字符串去匹配,很容易被利用做攻击;错误处理也做的不是很好,要么直接忽略或者就没报错退出了。不过也有一些可取的地方,比如后来我发现正常网页的图片居然真的是用base64来传输的;还有要匹配的文本是否写一个json来匹配会更好……

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信