1. Demo背景
功能展示:
-
Server:
-
Client:
网络聊天室Demo简介:
- Demo简介:
- Golang实现UDP-Server
- Python实现Client
- Server实现功能:用户上下线,消息的转发,心跳检测
- CLient实现功能:基本聊天,用户下线
技术选型简介:
-
技术选型:
Golang使用包:
基本网络协议包 | net |
---|---|
定时器包(用于心跳检测) | time |
Python使用包:
多线程 | _thread |
---|---|
GUI | PyQT5 |
定时任务 | QTimer |
- 运行环境:
Windows10
Golang版本14.4
Python版本 3.7
2. 源码:
- Server:
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)
}
}
- Client:
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()