0%

Go+Python实现简易聊天室

所谓的成功,就是用自己的方式度过人生。

一、环境

  1. go version:go version go1.23.5 windows/amd64
  2. python –version:Python 3.5.3
  3. pip –version:pip 20.3.4
  4. systeminfo:Microsoft Windows 11 专业版

二、代码

  1. Server.go
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
package main

import (
"fmt"
"net"
"time"
)

type UDPAddr struct {
IP []byte
Port int
}

var UDPServerLog = `                                                                 
_ _ _____ _____ _____
| | | | __ \| __ \ / ____|
| | | | | | | |__) | | (___ ___ _ ____ _____ _ __
| | | | | | | ___/ \___ \ / _ \ '__\ \ / / _ \ '__|
| |__| | |__| | | ____) | __/ | \ V / __/ |
\____/|_____/|_| |_____/ \___|_| \_/ \___|_|
`

var Ip_port_Map = make(map[string]UDPAddr)
var heartbeat = ""

func main() {
fmt.Println(UDPServerLog)
// 1. 设置接收数据IP端口
listen, err := net.ListenUDP("udp", &net.UDPAddr{
IP: net.IPv4(0, 0, 0, 0),
Port: 8977,
})
fmt.Println("[Server Start] IP: ", listen.LocalAddr())
if err != nil {
fmt.Println("listen failed, err:", err)
return
}
defer listen.Close()

// 1.1 心跳检测
go func() {
for {
timers_heartbeat := time.NewTimer(time.Minute)
<-timers_heartbeat.C
fmt.Println("发送心跳包:", Ip_port_Map)
if len(Ip_port_Map) != 0 {
for k, v := range Ip_port_Map {
data := k + ":" + "AreYouOk"
var addra *net.UDPAddr
addra = &net.UDPAddr{IP: v.IP, Port: v.Port}
_, err = listen.WriteToUDP([]byte(data), addra)
if err != nil {
fmt.Println("Write to udp failed, err: ", err)
}
// 休眠等待接收包
time.Sleep(500000000)
if heartbeat != string(data[:6]) {
delete(Ip_port_Map, string(data[:6]))
fmt.Println(heartbeat, "用户不在线")
}
}
}
timers_heartbeat.Reset(1 * time.Second)
}
}()

// 主业务
for {
var data [128]byte
// 2. 接收数据
n, addr, err := listen.ReadFromUDP(data[:])
if err != nil {
fmt.Println("read udp failed, err:", err)
continue
}

// 3. 新上线用户加入Map及移除下线用户
go func() {
if string(data[7:13]) == "OnLine" {
Ip_port_Map[string(data[:6])] = UDPAddr{IP: addr.IP, Port: addr.Port}
} else if string(data[7:14]) == "OffLine" {
delete(Ip_port_Map, string(data[:6]))
fmt.Println("用户下线:", string(data[:6]))
} else if string(data[7:11]) == "ImOK" {
heartbeat = string(data[:6])
}
}()

// 4. 收到数据发送OK
go func() {
data_OK := "OK"
_, err = listen.WriteToUDP([]byte(data_OK), addr)
if err != nil {
fmt.Println("Write to udp failed, err: ", err)
}
}()

// 5. 群发数据
go func() {
fmt.Println()
fmt.Println("群发ing.....")
for _, v := range Ip_port_Map {
var addra *net.UDPAddr
addra = &net.UDPAddr{IP: v.IP, Port: v.Port}
_, err = listen.WriteToUDP([]byte(data[:n]), addra)
if err != nil {
fmt.Println("Write to udp failed, err: ", err)
}
}
fmt.Println("群发结束.....")
}()

fmt.Println("当前在线用户:", Ip_port_Map)
fmt.Printf("数据:%v 地址:%v 字节长度:%v\n", string(data[:n]), addr, n)
}
}
  1. Client.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
import sys, socket, random, _thread
from PyQt5.Qt import *
from PyQt5.QtWidgets import (QWidget, QPushButton, QLineEdit, QTextEdit,QLabel, QApplication)
from PyQt5.QtCore import QTimer


BUFSIZE = 128
client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 8977)
# 1. 随机生成用户名
name = ''.join(random.sample('abcdefghijklmnopqrstuvwxyz!@#$%&*123456789', 6))
print("用户名: " + name)
data = name + ":" + "OnLine"
string_data = ""
try:
client.sendto(data.encode('utf-8'), ip_port)
data, server_addr = client.recvfrom(BUFSIZE)
if data.decode('utf-8') == "OK":
print('服务器在线!', data.decode('utf-8'), server_addr)
except Exception:
print('服务器离线请稍后尝试!')
quit()


class Example(QWidget):
def __init__(self):
global string_data
super().__init__()
self.initUI()

def initUI(self):
# 输入框
self.qle = QLineEdit(self)
self.qle.move(170, 260)

# 用户名显示
self.qla = QLabel(self)
self.qla.move(60, 10)
self.qla.setText("User name: " + name)

# 聊天数据显示
self.lbl = QTextEdit(self)
self.lbl.setReadOnly(True)
self.lbl.setMinimumSize(485, 200)
self.lbl.move(60, 40)

# 发送消息按键
redb = QPushButton('发送', self)
redb.setCheckable(True)
redb.move(200, 300)

# 下线退出
reclo = QPushButton('退出', self)
reclo.setCheckable(True)
reclo.move(380, 300)

redb.clicked[bool].connect(self.onChanged)
reclo.clicked[bool].connect(self.OffLine)

self.setMaximumSize(600, 370)
self.setMinimumSize(600, 370)
self.setWindowTitle('UDP Client')
self.setWindowFlags(Qt.WindowMinimizeButtonHint)
self.setWindowIcon(QIcon('C:/Users/KAILIN/AppData/Local/Microsoft/Edge Beta/User Data/Default/Web Applications/_crx_ihmafllikibpmigkcoadcmckbfhibefp/Edge Feedback.ico'))
self.show()

# 初始化定时器
self.timers = QTimer(self)
self.timers.timeout.connect(self.Data_look)
self.startTimer()

def startTimer(self):
self.timers.start(1000) # 1000 单位是毫秒, 即1秒

def Data_look(self):
# 消息显示
self.lbl.setText(string_data)
self.lbl.adjustSize()
# 滑动条显示最底下
self.lbl.verticalScrollBar().setValue(self.lbl.verticalScrollBar().maximum())
self.lbl.moveCursor(QTextCursor.End)

def onChanged(self):
if self.qle.text() == "":
return
# 消息拼接
data = name + ":" + self.qle.text()
try:
# 发送数据
client.sendto(data.encode('utf-8'), ip_port)
except Exception:
print('服务器离线请稍后尝试!')
self.qle.clear()

def OffLine(self):
super(Example, self).close()
data = name + ":" + "OffLine"
try:
client.sendto(data.encode('utf-8'), ip_port)
except Exception:
print("下线失败!")
self.OffLine()

def Server_data(threadName):
global string_data
print("接收服务器数据")
while True:
# 6. 接收服务器数据
data, server_addr = client.recvfrom(BUFSIZE)
print(data.decode('utf-8')[7:])
if data.decode('utf-8') == "OK":
print('服务器接收到数据!', data.decode('utf-8'), server_addr)
elif data.decode('utf-8')[7:] == "ImOK":
continue
elif data.decode('utf-8')[7:] == "AreYouOk":
try:
heartbeat_data = name+":"+"ImOK"
client.sendto(heartbeat_data.encode('utf-8'), ip_port)
except Exception:
print("心跳包发送失败!")
continue
elif data.decode('utf-8')[6:7] == ":":
print('接收到群数据!', data.decode('utf-8'), server_addr)
string_data += data.decode('utf-8') + "\n"
else:
string_data += "消息发送失败! " + "\n"
print('消息发送失败!')


if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
_thread.start_new_thread(Server_data, ("Server_data_Thread",))
sys.exit(app.exec_())

client.close()
  1. 运行Server:go run Server.go

    • go env -w CGO_ENABLED=0 GOOS=windows GOARCH=amd64
    • go build Server.go,会生成Server.exe
    • 双击运行Server.exe
  2. 运行Client.py

    • pip install PyQt5
    • python Client.py

三、问题

  1. Python执行报错

    • ImportError: No module named ‘PyQt5’
  2. Windows打包Linux/Mac可执行文件

    • go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64
      • 或者:
        • SET CGO_ENABLED=0
        • SET GOOS=linux
        • SET GOARCH=amd64
    • go env -w CGO_ENABLED=0 GOOS=darwin3 GOARCH=amd64
      • 报错:go: unsupported GOOS/GOARCH pair darwin3/amd64
        • 修改:go env -w CGO_ENABLED=0 GOOS=darwin GOARCH=amd64,正常
      • 或者:
        • SET CGO_ENABLED=0
        • SET GOOS=darwin
        • SET GOARCH=amd64

四、参考

  1. 参考一
  2. 参考二
  3. 参考三
  4. 参考四