之前在学习廖雪峰老师的python3教程,看到socket的那一章时,有一个想法突然冒出来,是不是可以通过socket做一个简单的聊天室软件,在构思了一段时间后,废话不多说,开始实施:

一、思维导图


按照自定的报文格式构造和解析消息在客户端完成,服务端只负责接受消息和分发消息。

二、所需库与模块

  • socket
  • threading
  • tkinter
  • datetime
  • time
  • os

三、报文

了解过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文件,那样使用就会很方便。

如果你有什么意见和建议,欢迎在评论区留言。

评论

  • 最新随笔

  • 中秋经典BGM:滴滴滴
  • 猫确实喜欢在各种犄角旮旯里睡觉
  • 尝试让DALLE生成一些连续的精灵图,让gpt帮忙生成一些提示词,如果能稳定输出的话就很强大了。
    让gpt帮忙生成的DALLE提示词
    "Generate a pixel art sprite sheet of a character walking in four directions (north, south, east, west) in a retro video game style."
    "Create a series of pixel art frames showing a character performing different actions like walking, running, jumping, and attacking in a classic 2D game aesthetic."
  • 路过别人山庄的门口,被一条大黑狗边叫边追过来,幸好骑电动车,不然还不一定跑得过,哈哈哈哈哈哈哈哈哈哈。
  • 最近两周也没咋出去玩,主要也是觉得没啥好玩的(笑哭)。看完布莱恩阿瑟的《复杂经济学》后,里面那个酒吧问题勾起我的兴趣,最近空了就花了些时间实现个python版本,顺便搞了篇博文,很享受这种新知识能和已有知识碰撞的感觉。(配张前段时间拍的图片,梧桐山门口前面那条路,挺漂亮的)
  • 盐田港夜景
  • 为啥这猫总喜欢喝杯子里的水
  • 确实开始冷了,在树林里至少要比人类聚集区低个几度,进出入口就能很明显感觉到。看看深圳水库的风景,貌似后面的视野更开阔。