iis7.5网站配置,巴中 网站建设,鞍山信息港征婚,互联网整合营销推广欢迎来到Golang的世界#xff01;在当今快节奏的软件开发领域#xff0c;选择一种高效、简洁的编程语言至关重要。而在这方面#xff0c;Golang#xff08;又称Go#xff09;无疑是一个备受瞩目的选择。在本文中#xff0c;带领您探索Golang的世界#xff0c;一步步地了… 欢迎来到Golang的世界在当今快节奏的软件开发领域选择一种高效、简洁的编程语言至关重要。而在这方面Golang又称Go无疑是一个备受瞩目的选择。在本文中带领您探索Golang的世界一步步地了解这门语言的基础知识和实用技巧。 在这篇文章中我们将用Go语言实现一个简易网络聊天应用重点探讨Socket编程、map结构用于管理用户、goroutines与channels实现并发通信、select语句处理超时与主动退出以及timer定时器的应用。这些概念将帮助我们构建高效且实用的聊天系统。让我们开始吧
目录
socket-server建立
创建msg广播通道
查询用户与重命名
用户主动退出聊天
用户超时退出聊天 socket-server建立
socket-server的作用是实现网络通信的基础允许不同设备如客户端和服务器通过网络交换数据下面我们模拟TCP服务器能够接收多个客户端的连接请求并在每个连接上启动一个新的goroutine进行数据处理。每当有数据从客户端发送到服务器时服务器会读取并打印这些数据
package mainimport (fmtnet
)func main() {// 01 创建服务器listener, err : net.Listen(tcp, :8080)if err ! nil {fmt.Println(net.listen err:, err)return} else {fmt.Println(服务器启动成功...)}for {fmt.Println(主go程监听中...)// 02 监听服务器connect, err : listener.Accept()if err ! nil {fmt.Println(listener.accept err:, err)return}fmt.Println(建立连接成功...)// 03 启动处理业务的go程go handler(connect)}}func handler(conn net.Conn) {for {fmt.Println(启动处理业务)// TODO// 读取客户端发送的数据buf : make([]byte, 1024)cnt, err : conn.Read(buf)if err ! nil {fmt.Println(conn.read err:, err)return} else {fmt.Println(服务器接收客户端发送过来的数据为:, string(buf[:cnt-1]), cnt:, cnt)}}}
这种设计使得服务器具有并发处理能力可以同时处理多个客户端的请求这里我们借助nc工具来模拟请你不了解工具的可以参考我之前的文章地址 具体如下所示 创建msg广播通道
要知道我们程度当中是有很多用户的当一个用户发送消息能让所有的用户看到的话是需要有一个进行全局广播的管道message如下所示全局广播的message获取到“hello”然后遍历所有的用户并向用户msg管道发送hello在go程中每一个用户连接一个需要再启动一个go程读取message数据之后发送给客户端 接下来我们开始创建User结构用于管理每次创建用户的结构
// User 定义用户结构体
type User struct {id stringname stringmsg chan string
}// 创建全局的map结构用于保存所有的用户
var allUsers make(map[string]User)
然后我们再每次创建go程的时候以连接的key作为唯一添加到用户的map结构当中 接下来我们定义全局的管道用于接收任何人发送过来的消息
// 定义一个message全局通道用于接收任何人发送过来的消息
var message make(chan string, 10)
接下来再每次创建新用户上线的时候写入message 接下来创建一个全局唯一的广播通道用于通知用户消息然后在main函数中调用一次下面的go程即可
// 向所有的用户广播消息启动全局唯一go程
func broadcast() {fmt.Println(启动广播go程...)defer fmt.Println(broadcast程序结束...) // 程序结束关闭广播go程for {fmt.Println(广播go程监听中...)// 01 从message通道中读取消息info : -message// 02 遍历map结构向每个用户发送消息for _, user : range allUsers {// 03 向每个用户发送消息user.msg - info}}
} 接下来每个用户应该还有一个用来监听自己msg管道的go程负责将数据返回给客户端
// 每个用户监听自己的msg通道负责将数据返回给客户端
func writeBackToClient(user *User, conn net.Conn) {fmt.Println(启动用户, user.name, 的writeBackToClient go程...)for data : range user.msg {fmt.Printf(user: %s 写回给客户端的数据为: %s\n, user.name, data)_, _ conn.Write([]byte(data))}
} 查询用户与重命名
查询用户当用户输入查询命令who则将当前所有登录的用户展示出来id与name返回给当前用户
// 01 查询当前所有的用户 who
if len(buf[:cnt-1]) 3 string(buf[:cnt-1]) who {var userInfos []string// 遍历map结构获取所有的用户信息for _, user : range allUsers {userInfo : fmt.Sprintf(userid:%s, username: %s, user.id, user.name)userInfos append(userInfos, userInfo)}// 最终写到管道中message - strings.Join(userInfos, \n)
} 重命名这里我们可以设置一个规则rename | Duke使用竖线进行分割获取竖线后面的部分作为名字通过设置 newUser.name Duke然后通知客户端更新名字成功为了避免想输入命令作为消息这里我们对命令做一个处理
// 01 查询当前所有的用户 who
if len(buf[:cnt-1]) 4 string(buf[:cnt-1]) \\who {var userInfos []string// 遍历map结构获取所有的用户信息for _, user : range allUsers {userInfo : fmt.Sprintf(userid:%s, username: %s, user.id, user.name)userInfos append(userInfos, userInfo)}// 最终写到管道中newUser.msg - strings.Join(userInfos, \n)
} else if len(buf[:cnt-1]) 9 string(buf[:7]) \\rename {// 更新名字newUser.name strings.Split(string(buf[:cnt-1]), |)[1]allUsers[newUser.id] newUser // 更新map结构中的用户信息// 通知客户端更新成功newUser.msg - fmt.Sprintf(改名成功, 新的名字为: %s, newUser.name)
} else {message - string(buf[:cnt-1])
} 用户主动退出聊天
接下来我们通过使用ctrlc的方式进行退出程序用户退出还需要做一下清理工作需要从map当中删除用户信息还需要将对应的conn连接进行close具体如下所示
// 启动一个go程负责监听退出信号通知所有go程退出
func watch(user *User, conn net.Conn, isQuit chan bool) {fmt.Println(启动用户, user.name, 的watch go程...)defer fmt.Println(watch程序结束...) // 程序结束关闭监听go程for {select {case -isQuit: // 收到退出信号通知所有go程退出delete(allUsers, user.id)fmt.Println(删除当前用户, user.name)message - fmt.Sprintf([%s][%s]下线了, user.id, user.name)_ conn.Close()}}
}
在handler中启动go watch并传入对应信息 然后在read之后通过读取cnt判断用户是否退出向isQuit中写入信息 最终实现的效果如下所示 用户超时退出聊天
这里我们可以设置使用定时器来进行超时管理如果60s内没有发送任何消息的情况下就直接将这个连接关闭掉
// 启动一个go程负责监听退出信号通知所有go程退出
func watch(user *User, conn net.Conn, isQuit chan bool, resTimer chan bool) {fmt.Println(启动用户, user.name, 的watch go程...)defer fmt.Println(watch程序结束...) // 程序结束关闭监听go程for {select {case -isQuit: // 收到退出信号通知所有go程退出delete(allUsers, user.id)fmt.Println(删除当前用户, user.name)message - fmt.Sprintf([%s][%s]下线了\n, user.id, user.name)_ conn.Close()returncase -time.After(10 * time.Second):fmt.Println(删除当前用户, user.name)delete(allUsers, user.id)message - fmt.Sprintf([%s]用户超时下线了\n, user.name)_ conn.Close()returncase -resTimer:fmt.Printf(连接%s 重置计数器\n, user.name)}}
}
这里我们定义一个重置的管道只要用户不断输入就不会超时如果用户没有输入超过10s就会触发超时退出的操作
// 创建一个用于重置计算器的管道用于告知watch函数当前用户正在输入
var resTimer make(chan bool)
// 启动go程负责监听用户退出
go watch(newUser, conn, isQuit, resTimer) 完整代码如下所示
package mainimport (fmtnetstringstime
)// User 定义用户结构体
type User struct {id stringname stringmsg chan string
}// 创建全局的map结构用于保存所有的用户
var allUsers make(map[string]User)// 定义一个message全局通道用于接收任何人发送过来的消息
var message make(chan string, 10)func main() {// 01 创建服务器listener, err : net.Listen(tcp, :8080)if err ! nil {fmt.Println(net.listen err:, err)return} else {fmt.Println(服务器启动成功...)// 启动全局唯一go程用于广播消息go broadcast()}for {fmt.Println(主go程监听中...)// 02 监听服务器connect, err : listener.Accept()if err ! nil {fmt.Println(listener.accept err:, err)return}fmt.Println(建立连接成功...)// 03 启动处理业务的go程go handler(connect)}}func handler(conn net.Conn) {fmt.Println(启动处理业务)// 客户端与服务器建立连接的时候会有ip与port可以当成user的idclientAddr : conn.RemoteAddr().String()fmt.Println(客户端地址为:, clientAddr)// 创建UsernewUser : User{id: clientAddr, // id,不会被修改作为mao中的keyname: clientAddr, // 可以修改会提供rename命令修改建立连接时初始值与id相同msg: make(chan string, 10), // 消息通道注意分配空间}// 添加user到map结构中allUsers[newUser.id] newUser// 定义一个退出信号用于通知所有go程退出var isQuit make(chan bool)// 创建一个用于重置计算器的管道用于告知watch函数当前用户正在输入var resTimer make(chan bool)// 启动go程负责监听用户退出go watch(newUser, conn, isQuit, resTimer)// 启动用户自己的writeBackToClient go程go writeBackToClient(newUser, conn)// 向message写入消息用于通知所有人有用户上线message - fmt.Sprintf([%s][%s]上线了, newUser.id, newUser.name)for {buf : make([]byte, 1024)// 读取客户端发送的数据cnt, err : conn.Read(buf)if cnt 0 {fmt.Println(客户端主动关闭ctrlc准备退出)// 在这里不进行真正的退出动作只是通知所有go程退出isQuit - true}if err ! nil {fmt.Println(conn.read err:, err, cnt, cnt)return} else {fmt.Println(服务器接收客户端发送过来的数据为:, string(buf[:cnt-1]), cnt:, cnt)// -------业务逻辑处理开始-------// 01 查询当前所有的用户 whoif len(buf[:cnt-1]) 4 string(buf[:cnt-1]) \\who {var userInfos []string// 遍历map结构获取所有的用户信息for _, user : range allUsers {userInfo : fmt.Sprintf(userid:%s, username: %s, user.id, user.name)userInfos append(userInfos, userInfo)}// 最终写到管道中newUser.msg - strings.Join(userInfos, \n)} else if len(buf[:cnt-1]) 9 string(buf[:7]) \\rename {// 更新名字newUser.name strings.Split(string(buf[:cnt-1]), |)[1]allUsers[newUser.id] newUser // 更新map结构中的用户信息// 通知客户端更新成功newUser.msg - fmt.Sprintf(改名成功, 新的名字为: %s, newUser.name)} else {message - string(buf[:cnt-1])}resTimer - true // 发送一个信号告知watch函数当前用户正在输入// -------业务逻辑处理结束-------}}
}// 向所有的用户广播消息启动全局唯一go程
func broadcast() {fmt.Println(启动广播go程...)defer fmt.Println(broadcast程序结束...) // 程序结束关闭广播go程for {fmt.Println(广播go程监听中...)// 01 从message通道中读取消息info : -messagefmt.Println(广播消息为:, info)// 02 遍历map结构向每个用户发送消息for _, user : range allUsers {// 03 向每个用户发送消息user.msg - info}}
}// 每个用户监听自己的msg通道负责将数据返回给客户端
func writeBackToClient(user *User, conn net.Conn) {fmt.Println(启动用户, user.name, 的writeBackToClient go程...)for data : range user.msg {fmt.Printf(user: %s 写回给客户端的数据为: %s\n, user.name, data)_, _ conn.Write([]byte(data))}
}// 启动一个go程负责监听退出信号通知所有go程退出
func watch(user *User, conn net.Conn, isQuit chan bool, resTimer chan bool) {fmt.Println(启动用户, user.name, 的watch go程...)defer fmt.Println(watch程序结束...) // 程序结束关闭监听go程for {select {case -isQuit: // 收到退出信号通知所有go程退出delete(allUsers, user.id)fmt.Println(删除当前用户, user.name)message - fmt.Sprintf([%s][%s]下线了\n, user.id, user.name)_ conn.Close()returncase -time.After(10 * time.Second):fmt.Println(删除当前用户, user.name)delete(allUsers, user.id)message - fmt.Sprintf([%s]用户超时下线了\n, user.name)_ conn.Close()returncase -resTimer:fmt.Printf(连接%s 重置计数器\n, user.name)}}
}