使用Qt和go语言开发的一个聊天客户端及后台服务

4 篇文章 1 订阅
1 篇文章 0 订阅

使用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更新:
更新的驱动原因:每次打开窗口的时候所有记录均为空,导致想要获取文件列表比较难,需要到服务器上才能查看已经上传了哪些文件,然后手动拼接链接才能进行文件的下载,所以想要增加功能将已上传的文件保存到数据库中,然后提供接口查询文件记录,并在前端显示文件列表,用户可以自行选择下载哪些文件。

更新内容:

  1. 后台go语言:将上传的文件记录保存到数据库中,并提供接口供用户查询文件列表。
  2. 前台Qt应用程序:新增“已上传文件”控件可以获取已上传文件列表;点击文件列表中的“下载”即可将文件下载到指定目录。
    在这里插入图片描述
    在这里插入图片描述

2020-07-13更新:
后台可执行程序下载链接:https://download.csdn.net/download/codears/12598319
客户端可执行程序下载链接:https://download.csdn.net/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, &regUser)
	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和端口设置窗口:
设置服务器的IP和端口

登录成功之后的主窗口:
多个用户登录时右侧显示除自己以外的在线用户
双击右侧用户名,可私发消息到该用户。在主窗口中发送的消息所有在线用户都可接收到
主窗口不会显示私发的消息,具有较高的保密性
代码将以开源的方式贡献出来,如果转载请注明来源。开源代码仅供个人用途,严禁用于商业用途。请尊重开发者。

服务和客户端的git链接:
git@github.com:waynecn/WebSocketServerAndClient.git
最新功能请切换到dev分支。

再次声明:该项目严禁用于商业用途。

欢迎加QQ:635864540进行技术交流和探讨。

  • 9
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codears

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值