python socket + tkinter实现网络聊天室

个人博客文章链接:http://www.huqj.top/article?id=169

最近突然想用socket做个聊天室程序,之前用java写过一个文件传输的程序,这次就用python做一下,顺便也学习一下python的界面设计。界面库选择了python自带的tkinter。

    总的来说聊天室功能比较简单,只是一个练习用的demo,但是其中一些关于tk和socket的东西值得记录一下。最终的功能包括:注册、登录、显示聊天室在线成员、聊天。先来几张运行截图:

python socket + tkinter实现网络聊天室_第1张图片

python socket + tkinter实现网络聊天室_第2张图片      python socket + tkinter实现网络聊天室_第3张图片

python socket + tkinter实现网络聊天室_第4张图片

因为代码比较少,所以服务端和客户端的代码写在一起的,整个代码结构如下:

python socket + tkinter实现网络聊天室_第5张图片

其中只有Server.py是服务器端的代码,服务器端没有界面。其它文件的作用分别如下:

  •  Client.py  负责和Server的socket通信,主要是收发数据

  • LoginPanel.py  登陆界面

  • Main.py  客户端的入口,负责调度各个界面之间的切换和调用client发送接收数据

  • MainPanel.py  聊天室主界面

  • MD5  md5算法

  • RegisterPanel.py  注册界面

  • data  目录,存放用户帐号密码数据

  • image  目录,存放图标

完整的代码和打包的exe文件可以从 https://download.csdn.net/download/qq_32216775/10903517 下载。

启动方式为:

1)先启动Server.exe开始监听端口(12323),或者直接用python运行Server.py

2)再启动一个或多个Main.exe打开客户端的界面,或者直接用python运行Main.py

这里再代码中写的是直接连接本地(127.0.0.1)的服务器,如果需要测试客户端连接远程服务器,只需要将Client.py中第10行的ip地址换成服务器地址即可,换端口也是同理。

python socket + tkinter实现网络聊天室_第6张图片


一些值得记录的技术细节:

①tkinter界面上显示图片的方法:

使用tkinter模块的PhotoImage类,和Lbael组件即可,代码参考下面

1

2

3

4

5

6

7

form_frame = Frame(self.login_frame, bg="#333333")

user_img = PhotoImage(file="image\\user.png", master=self.login_frame)

key_img = PhotoImage(file="image\\key.png", master=self.login_frame)

user_img_label = Label(form_frame, image=user_img, width=30, height=30, bg="#333333")

key_img_label = Label(form_frame, image=key_img, width=30, height=30, bg="#333333")

user_img_label.grid(row=0, column=0, padx=5)

key_img_label.grid(row=1, column=0, padx=5)

②tkinter的Entry组件实现密码框效果:

这个还是可以使用Entry组件,只需要加上属性show="*",即可将输入显示为"*"

1

2

Entry(form_frame, textvariable=self.key, show="*", bg="#e3e3e3", width=30) \

    .grid(row=1, column=2, ipady=1)

③Text+Scrollbar组件实现聊天记录的效果:

这种效果主要考虑两个方面,一个是用不同的颜色显示区别其他人的消息和自己的消息,这个可以使用Text组件的tag_configure方法设置不同的标签,第二个是实现消息刷新时自动将滚动条滚动到底部,这个主要是通过Text的see函数实现,主要代码见下面

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

......

 

# 显示消息的文本框不可编辑,当需要修改内容时再修改版为可以编辑模式 NORMAL

self.message_text.config(state=DISABLED)

self.message_text.tag_configure('greencolor', foreground='green')

self.message_text.tag_configure('bluecolor', foreground='blue')

 

......

 

# 接受到消息,在文本框中显示,自己的消息用绿色,别人的消息用蓝色

def recv_message(self, user, content):

    self.message_text.config(state=NORMAL)

    title = user + " " + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + "\n"

    if user == self.username:

        self.message_text.insert(END, title, 'greencolor')

    else:

        self.message_text.insert(END, title, 'bluecolor')

    self.message_text.insert(END, content + "\n")

    self.message_text.config(state=DISABLED)

    # 滚动到最底部

    self.message_text.see(END)

④python md5加密的方法:

1

2

3

4

5

6

7

import hashlib

 

# md5加密方法

def gen_md5(_str):

    hl = hashlib.md5()

    hl.update(_str.encode(encoding='utf-8'))

    return hl.hexdigest()

⑤python中int型数字和4字节数组之间的转换方法:

因为在socket传输的过程中,有些变长的数据,例如消息,所以这里我设计的传输协议在传输变长字符串的时候会先发送一个4字节的数据表示接下来会传多少数据,因此需要有一个方法来转换int数值和字节数组,这里使用的是int.from_bytes和int.to_bytes方法,如下

1

2

3

4

5

# 将数字number转换成4字节数组,byteorder表示字节数组中高位在前还是低位在前

int(number).to_bytes(4, byteorder='big')

 

# 从4个字节的数组中转成数字,_conn.recv(4)返回的就是字节数组

int.from_bytes(_conn.recv(4), byteorder='big')

⑥python程序打包成可执行文件:

使用pyinstaller库,可以方便的打包python项目成exe文件,其原理就是将python解释器以及程序都打包起来,所以效率上可能会比直接运行python文件低。使用方法:1)下载安装pyinstaller库。2)使用pyinstaller打包程序。

1

2

3

pip install pyinstaller

 

pyinstaller -F Server.py

其中-F选项是指打包成一个exe文件,如果不加这个选项会打包成一个附加有很多dll文件的文件夹。然后会发现当前目录下多出了build文件夹和dist目录,dist目录下就有我们需要的可执行文件。


最后贴上客户端的控制流程代码Main.py

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

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

from LoginPanel import LoginPanel

from MainPanel import MainPanel

from RegisterPanel import RegisterPanel

from Client import ChatClient

import MD5

from tkinter import messagebox

from threading import Thread

import time

 

 

def send_message():

    print("send message:")

    content = main_frame.get_send_text()

    if content == "" or content == "\n":

        print("空消息,拒绝发送")

        return

    print(content)

    # 清空输入框

    main_frame.clear_send_text()

    client.send_message(content)

 

 

def close_sk():

    print("尝试断开socket连接")

    client.sk.close()

 

 

def close_main_window():

    close_sk()

    main_frame.main_frame.destroy()

 

 

def close_login_window():

    close_sk()

    login_frame.login_frame.destroy()

 

 

# 关闭注册界面并打开登陆界面

def close_reg_window():

    reg_frame.close()

    global login_frame

    login_frame = LoginPanel(login, register, close_login_window)

    login_frame.show()

 

 

# 关闭登陆界面前往主界面

def goto_main_frame(user):

    login_frame.close()

    global main_frame

    main_frame = MainPanel(user, send_message, close_main_window)

    # 新开一个线程专门负责接收并处理数据

    Thread(target=recv_data).start()

    main_frame.show()

 

 

def login():

    print("点击登录按钮")

    user, key = login_frame.get_input()

    # 密码传md5

    key = MD5.gen_md5(key)

    if user == "" or key == "":

        messagebox.showwarning(title="提示", message="用户名或者密码为空")

        return

    print("user: " + user + ", key: " + key)

    if client.check_user(user, key):

        # 验证成功

        goto_main_frame(user)

    else:

        # 验证失败

        messagebox.showerror(title="错误", message="用户名或者密码错误")

 

 

# 登陆界面前往注册界面

def register():

    print("点击注册按钮")

    login_frame.close()

    global reg_frame

    reg_frame = RegisterPanel(close_reg_window, register_submit, close_reg_window)

    reg_frame.show()

 

 

# 提交注册表单

def register_submit():

    print("开始注册")

    user, key, confirm = reg_frame.get_input()

    if user == "" or key == "" or confirm == "":

        messagebox.showwarning("错误""请完成注册表单")

        return

    if not key == confirm:

        messagebox.showwarning("错误""两次密码输入不一致")

        return

    # 发送注册请求

    result = client.register_user(user, MD5.gen_md5(key))

    if result == "0":

        # 注册成功,跳往登陆界面

        messagebox.showinfo("成功""注册成功")

        close_reg_window()

    elif result == "1":

        # 用户名重复

        messagebox.showerror("错误""该用户名已被注册")

    elif result == "2":

        # 未知错误

        messagebox.showerror("错误""发生未知错误")

 

 

# 处理消息接收的线程方法

def recv_data():

    # 暂停几秒,等主界面渲染完毕

    time.sleep(1)

    while True:

        try:

            # 首先获取数据类型

            _type = client.recv_all_string()

            print("recv type: " + _type)

            if _type == "#!onlinelist#!":

                print("获取在线列表数据")

                online_list = list()

                for in range(client.recv_number()):

                    online_list.append(client.recv_all_string())

                main_frame.refresh_friends(online_list)

                print(online_list)

            elif _type == "#!message#!":

                print("获取新消息")

                user = client.recv_all_string()

                print("user: " + user)

                content = client.recv_all_string()

                print("message: " + content)

                main_frame.recv_message(user, content)

        except Exception as e:

            print("接受服务器消息出错,消息接受子线程结束。" + str(e))

            break

 

 

def start():

    global client

    client = ChatClient()

    global login_frame

    login_frame = LoginPanel(login, register, close_login_window)

    login_frame.show()

 

 

if __name__ == "__main__":

    start()

 

你可能感兴趣的:(unity3d)