喜欢的朋友帮忙点个赞或者评论一下吧,万分感谢。^_^
服务和客户端的git链接: https://github.com/linchangshu0/WebSocketServerAndClient.git 最新功能请切换到dev分支。
2021-01-06更新: 进度条中增加了上传或下载速度。
2020-12-26更新: 文件列表增加了菜单,可以复制文件链接和删除选择的文件。
2020-12-23更新: 最新界面如下:
2020-11-04更新: 更新的驱动原因:每次打开窗口的时候所有记录均为空,导致想要获取文件列表比较难,需要到服务器上才能查看已经上传了哪些文件,然后手动拼接链接才能进行文件的下载,所以想要增加功能将已上传的文件保存到数据库中,然后提供接口查询文件记录,并在前端显示文件列表,用户可以自行选择下载哪些文件。
更新内容:
后台go语言:将上传的文件记录保存到数据库中,并提供接口供用户查询文件列表。前台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, ®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链接: https://github.com/linchangshu0/WebSocketServerAndClient.git 最新功能请切换到dev分支。
再次声明:该项目严禁用于商业用途。
欢迎加QQ:635864540进行技术交流和探讨。