使用Qt和go语言开发的一个聊天后台及客户端
喜欢的朋友帮忙点个赞或者评论一下吧,万分感谢。^_^
最新EasyChat客户端,代码地址:git@github.com:waynecn/EasyChat.git
新的界面如下:
服务和客户端的git链接:
https://github.com/linchangshu0/WebSocketServerAndClient.git
最新功能请切换到dev分支。
2021-07-09更新:
支持sqlite了,可以不用安装mysql就能快速开始使用了。
2021-01-06更新:
进度条中增加了上传或下载速度。
2020-12-26更新:
文件列表增加了菜单,可以复制文件链接和删除选择的文件。
2020-12-23更新:
最新界面如下:
2020-11-04更新:
更新的驱动原因:每次打开窗口的时候所有记录均为空,导致想要获取文件列表比较难,需要到服务器上才能查看已经上传了哪些文件,然后手动拼接链接才能进行文件的下载,所以想要增加功能将已上传的文件保存到数据库中,然后提供接口查询文件记录,并在前端显示文件列表,用户可以自行选择下载哪些文件。
更新内容:
- 后台go语言:将上传的文件记录保存到数据库中,并提供接口供用户查询文件列表。
- 前台Qt应用程序:新增“已上传文件”控件可以获取已上传文件列表;点击文件列表中的“下载”即可将文件下载到指定目录。
2020-07-13更新:
后台可执行程序下载链接:https://download.youkuaiyun.com/download/codears/12598319
客户端可执行程序下载链接:https://download.youkuaiyun.com/download/codears/12598333
其中后台需要配置数据库:在mysql中运行chat_user.sql中的建表语句,在后台可执行程序目录下创建config/config.json文件,并更改config.json中的内容。运行server之后,打开客户端,配置ip为你的电脑所在ip,端口为5133即可。注册时授权码填写为:10086
然后就能和朋友一起愉快的玩耍了。
2020-07-10更新:
最新dev分支服务和客户端增加了上传和下载文件的功能。在客户端中点击聊天信息中的链接即可触发下载保存功能。
----------------------分割线------------------------
闲来无事时候突然心血来潮就想着搞一个聊天工具,用于平时无法进行远程复制粘贴的时候,进行一些文本内容的传输。后续如果有时间会加入文件传输功能。
1. 后台
使用go语言开发一个websocket后台,这个网上有较多的教程,可以拿来简单的使用。
func main() {
//Read config
configPath := "./config/config.json"
g_sqlConfig = ReadConfig(configPath)
fmt.Println("sqlConfig:", g_sqlConfig)
var err error
g_Db, err = connectSql()
if err != nil {
log.Println("Connect sql failed.")
return
}
defer g_Db.Close()
// Create a simple file server
fs := http.FileServer(http.Dir("./public"))
http.Handle("/", fs)
//http request response
http.HandleFunc("/login", loginFunction)
http.HandleFunc("/register", registerFunction)
// Configure websocket route
http.HandleFunc("/ws", handleConnections)
// Start listening for incoming chat messages
go handleMessages()
// Start the server on localhost port 8000 and log any errors
log.Println("http server started on :5133")
err = http.ListenAndServe(":5133", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
但我需要处理登录、注册功能,所以新增了两个处理函数用于处理http请求。
func loginFunction(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
msg := "ReadAll body failed."
if !checkErr(err, msg, w) {
return
}
var userinfo LoginUser
err = json.Unmarshal(body, &userinfo)
msg = "json unmarshal failed."
if !checkErr(err, msg, w) {
return
}
//Check username and password from mysql
strSql := "select id, user_name, password from chat_user where mobile=?"
stmt, err := g_Db.Prepare(strSql)
msg = "Prepare sql failed."
if !checkErr(err, msg, w) {
return
}
rows, err := stmt.Query(userinfo.UserName)
msg = "Query sql failed."
if !checkErr(err, msg, w) {
return
}
defer rows.Close()
count := 0
var id int
var user_name string
var password string
for rows.Next() {
err := rows.Scan(&id, &user_name, &password)
msg = "Failed to get sql item."
if !checkErr(err, msg, w) {
return
}
if userinfo.Password != password {
response := HttpResponse{false, "Password is not correct.", -1, ""}
bts, err := json.Marshal(response)
if err != nil {
log.Println("json marshal failed.")
io.WriteString(w, "json marshal failed.")
return
}
log.Println(string(bts))
io.WriteString(w, string(bts))
return
}
count = count + 1
}
if count < 1 {
response := HttpResponse{false, "User does not exist.", -1, ""}
bts, err := json.Marshal(response)
if err != nil {
log.Println("json marshal failed.")
io.WriteString(w, "json marshal failed.")
return
}
log.Println(string(bts))
io.WriteString(w, string(bts))
return
}
response := HttpResponse{true, "success", id, user_name}
bts, err := json.Marshal(response)
if err != nil {
log.Println("json marshal failed.")
io.WriteString(w, "json marshal failed.")
return
}
io.WriteString(w, string(bts))
}
func registerFunction(w http.ResponseWriter, r *http.Request) {
token := r.Header["Token"][0]
log.Println("token:", token)
if token != "20200101" {
res := HttpResponse{false, "Token verify failed.", -1, ""}
_, err := json.Marshal(res)
if err != nil {
log.Println("json marshal failed.")
io.WriteString(w, "json marshal failed.")
return
}
}
body, err := ioutil.ReadAll(r.Body)
msg := "ReadAll body failed."
if !checkErr(err, msg, w) {
return
}
var regUser RegisterUser
err = json.Unmarshal(body, ®User)
msg = "json unmarshal body failed."
if !checkErr(err, msg, w) {
return
}
//check the new user exists or not
strSql := "select id from chat_user where mobile=?;"
stmt, err := g_Db.Prepare(strSql)
msg = "Prepare sql failed 1."
if !checkErr(err, msg, w) {
return
}
rows, err := stmt.Query(regUser.Mobile)
msg = "Query sql failed."
if !checkErr(err, msg, w) {
return
}
defer rows.Close()
var id int
for rows.Next() {
err := rows.Scan(&id)
msg = "Failed to get sql item."
if !checkErr(err, msg, w) {
return
}
if id > 0 {
response := HttpResponse{false, "User already exists.", -1, ""}
bts, err := json.Marshal(response)
if err != nil {
log.Println("json marshal failed.")
io.WriteString(w, "json marshal failed.")
return
}
log.Println(string(bts))
io.WriteString(w, string(bts))
return
}
}
strSql = "insert chat_user (user_name,mobile,password) values(?,?,?);"
stmt, err = g_Db.Prepare(strSql)
msg = "Prepare sql failed 2."
if !checkErr(err, msg, w) {
return
}
res, err := stmt.Exec(regUser.Username, regUser.Mobile, regUser.Password)
msg = "Insert into sql failed."
if !checkErr(err, msg, w) {
return
}
newId, err := res.LastInsertId()
if !checkErr(err, "Get LastInsertId failed.", w) {
return
}
log.Println("new Insert id:", newId)
response := HttpResponse{true, "success", id, regUser.Username}
bts, err := json.Marshal(response)
if err != nil {
log.Println("json marshal failed.")
io.WriteString(w, "json marshal failed.")
return
}
io.WriteString(w, string(bts))
}
另外还需要用到mysql,用于将注册的用户保存到数据库中,并用于登录验证。
另外还想要实时更新用户在线状态,每当有新用户登录时都要通知到所有的客户端。这个在websocket连接处理函数中一并进行了广播。
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
// Register our new client
clients[ws] = true
log.Println("ws:", ws.RemoteAddr(), " Network:", ws.RemoteAddr().Network(), " String:", ws.RemoteAddr().String())
//Whenever a new client was connected, send the online message to all clients
broadCastOnline()
for {
//var msg Message
// Read in a new message as JSON and map it to a Message object
messageType, p, err := ws.ReadMessage()
if err != nil {
log.Printf("ReadMessage error: %v", err)
addr := ws.RemoteAddr().String()
for index, item := range onlineusers {
if item.Addr == addr {
onlineusers = append(onlineusers[:index], onlineusers[index + 1:]...)
}
}
delete(clients, ws)
log.Println("Current online user count:", len(onlineusers))
broadCastOnline()
break
}
// Send the newly received message to the broadcast channel
var msg StringMessage
msg.MessageType = messageType
msg.Message = p
broadcast <- msg
//check if there is some online infos, then parse the online info and save them
var onlinestr string
onlinestr = string(p[:])
if strings.Index(onlinestr, "online") != -1 {
var onlineuser OnlineUser
err = json.Unmarshal([]byte(onlinestr), &onlineuser)
if err != nil {
log.Println("json unmarshal online info failed.")
continue
}
bFind := false
for _, item := range onlineusers {
if item.Online.Userid == onlineuser.Online.Userid {
bFind = true
break
}
}
if !bFind {
addr := ws.RemoteAddr().String()
onlineuser.Addr = addr
onlineusers = append(onlineusers, onlineuser)
}
}
log.Println("Current online user count:", len(onlineusers))
}
}
消息转发就比较简单了。
func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteMessage(msg.MessageType, msg.Message)
//log.Println(msg.Message)
if err != nil {
log.Printf("WriteMessage error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
2. 客户端
客户端使用我熟悉的Qt进行开发。
下面是代码结构图。
登录界面:
注册界面:
服务器IP和端口设置窗口:
登录成功之后的主窗口:
代码将以开源的方式贡献出来,如果转载请注明来源。开源代码仅供个人用途,严禁用于商业用途。请尊重开发者。
服务和客户端的git链接:
git@github.com:waynecn/WebSocketServerAndClient.git
最新功能请切换到dev分支。
再次声明:该项目严禁用于商业用途。
欢迎加QQ:635864540进行技术交流和探讨。