Golang快速入门 | 第五天 | 即时通讯系统服务端
-
阶段一
基础server构建
-
初始化相关
mkdir IM_System cd IM_System go mod init my_server touch server.go main.go
-
创建Sever对象
server.go
type Server struct { Ip string Port int }
-
处理server创建接口的函数
server.go
func NewServer(ip string, port int) *Server { server := &Server{ Ip: ip, Port: port, } return server }
-
启动服务器接口的函数&处理请求的函数
server.go
// 处理请求 func (s *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功!!!") } func (s *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } fmt.Println("Server 运行成功~~~") defer listener.Close() for { //accept conn, err := listener.Accept() if err != nil { fmt.Println("listener accept err:", err) continue } //do handler go s.Handler(conn) } }
-
编写main函数&编辑运行
main.go
func main() { server := NewServer("127.0.0.1", 9999) server.Start() }
go build -o server
运行截图
-
全部代码
server.go
package main import ( "fmt" "net" ) type Server struct { Ip string Port int } // 创建 server 的接口 func NewServer(ip string, port int) *Server { server := &Server{ Ip: ip, Port: port, } return server } // 处理请求 func (s *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功!!!") } // 启动服务器接口 func (s *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } fmt.Println("Server 运行成功~~~") defer listener.Close() for { //accept conn, err := listener.Accept() if err != nil { fmt.Println("listener accept err:", err) continue } //do handler go s.Handler(conn) } }
main.go
package main func main() { server := NewServer("127.0.0.1", 9999) server.Start() }
-
-
阶段二
用户上线功能
-
原理图
-
重构
Server
对象和创建方法server.go
type Server struct { Ip string Port int OnlineMap map[string]*User mapLock sync.RWMutex Message chan string } func NewServer(ip string, port int) *Server { server := &Server{ Ip: ip, Port: port, OnlineMap: make(map[string]*User), Message: make(chan string), } return server }
-
广播消息函数
server.go
func (s *Server) BroadCast(user *User, msg string) { sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg s.Message <- sendMsg }
-
监听到广播消息去处理函数
server.go
func (s *Server) ListenMessager() { for { msg := <-s.Message s.mapLock.Lock() for _, cli := range s.OnlineMap { cli.C <- msg } s.mapLock.Unlock() } }
-
User
对象和新建方法user.go
type User struct { Name string Addr string C chan string conn net.Conn } func NerUser(conn net.Conn) *User { userAddr := conn.RemoteAddr().String() user := &User{ Name: userAddr, Addr: userAddr, C: make(chan string), conn: conn, } //启动当前user channel的goroutine go user.ListenMessage() return user }
-
将user监听到消息发送给客户端
user.go
func (u *User) ListenMessage() { for { msg := <-u.C u.conn.Write([]byte(msg + "\n")) } }
-
重写
server
的Start
、Handler
方法server.go
func (s *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功!!!") user := NerUser(conn) //将当前用户插入map中 s.mapLock.Lock() s.OnlineMap[user.Name] = user s.mapLock.Unlock() //广播当前用户上线消息 s.BroadCast(user, "已上线") } func (s *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } fmt.Println("Server 运行成功~~~") defer listener.Close() go s.ListenMessager() for { //accept conn, err := listener.Accept() if err != nil { fmt.Println("listener accept err:", err) continue } //do handler go s.Handler(conn) } }
-
测试运行
-
全部代码
server.go
package main import ( "fmt" "net" "sync" ) type Server struct { Ip string Port int OnlineMap map[string]*User mapLock sync.RWMutex Message chan string } // 创建 server 的接口 func NewServer(ip string, port int) *Server { server := &Server{ Ip: ip, Port: port, OnlineMap: make(map[string]*User), Message: make(chan string), } return server } // 当监听到message广播消息,就将消息发送给全部的在线User func (s *Server) ListenMessager() { for { msg := <-s.Message s.mapLock.Lock() for _, cli := range s.OnlineMap { cli.C <- msg } s.mapLock.Unlock() } } // 广播消息的方法 func (s *Server) BroadCast(user *User, msg string) { sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg s.Message <- sendMsg } // 处理请求 func (s *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功!!!") user := NerUser(conn) //将当前用户插入map中 s.mapLock.Lock() s.OnlineMap[user.Name] = user s.mapLock.Unlock() //广播当前用户上线消息 s.BroadCast(user, "已上线") } // 启动服务器接口 func (s *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } fmt.Println("Server 运行成功~~~") defer listener.Close() go s.ListenMessager() for { //accept conn, err := listener.Accept() if err != nil { fmt.Println("listener accept err:", err) continue } //do handler go s.Handler(conn) } }
user.go
package main import "net" type User struct { Name string Addr string C chan string conn net.Conn } // 创建用户的接口 func NerUser(conn net.Conn) *User { userAddr := conn.RemoteAddr().String() user := &User{ Name: userAddr, Addr: userAddr, C: make(chan string), conn: conn, } //启动当前user channel的goroutine go user.ListenMessage() return user } // 监听消息 func (u *User) ListenMessage() { for { msg := <-u.C u.conn.Write([]byte(msg + "\n")) } }
main.go
package main func main() { server := NewServer("127.0.0.1", 9999) server.Start() }
-
-
阶段三
用户消息广播机制
-
修改
Handler
方法server.go
func (s *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功!!!") user := NerUser(conn) //将当前用户插入map中 s.mapLock.Lock() s.OnlineMap[user.Name] = user s.mapLock.Unlock() //广播当前用户上线消息 s.BroadCast(user, "已上线") //处理客户端发送的消息,由于是堵塞状态,新开一个goroutine go func() { buf := make([]byte, 4096) for { n, err := conn.Read(buf) if n == 0 { s.BroadCast(user, "下线") return } if err != nil && err != io.EOF { fmt.Println("Conn Error:", err) return } msg := string(buf[:n-1]) s.BroadCast(user, msg) } }() }
-
测试运行
-
-
阶段四
用户业务封装,将server处理业务封装成用户对象的方法
添加
Online
、Offline
、DoMessage
-
新增
Online
、Offline
、DoMessage
方法user.go
func (u *User) Online() { //将当前用户插入map中 u.server.mapLock.Lock() u.server.OnlineMap[u.Name] = u u.server.mapLock.Unlock() //广播当前用户上线消息 u.server.BroadCast(u, "已上线") } func (u *User) Offline() { //将当前用户插入map中 u.server.mapLock.Lock() delete(u.server.OnlineMap, u.Name) u.server.mapLock.Unlock() //广播当前用户上线消息 u.server.BroadCast(u, "下线") } func (u *User) DoMessage(msg string) { u.server.BroadCast(u, msg) }
-
修改
Handler
server.go
func (s *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功!!!") user := NerUser(conn, s) ////将当前用户插入map中 //s.mapLock.Lock() //s.OnlineMap[user.Name] = user //s.mapLock.Unlock() ////广播当前用户上线消息 //s.BroadCast(user, "已上线") user.Online() //处理客户端发送的消息,由于是堵塞状态,新开一个goroutine go func() { buf := make([]byte, 4096) for { n, err := conn.Read(buf) if n == 0 { user.Offline() //s.BroadCast(user, "下线") return } if err != nil && err != io.EOF { fmt.Println("Conn Error:", err) return } msg := string(buf[:n-1]) //s.BroadCast(user, msg) user.DoMessage(msg) } }() }
-
测试运行
-
-
阶段五
在线用户查询
输入
who
查询当前在线用户-
实现代码
user.go
func (u *User) DoMessage(msg string) { if msg == "who" { u.server.mapLock.Lock() for _, user := range u.server.OnlineMap { onlineMsg := "[" + user.Name + "] is online\n" u.sendMsg(onlineMsg) } u.server.mapLock.Unlock() } else { u.server.BroadCast(u, msg) } } func (u *User) sendMsg(msg string) { u.conn.Write([]byte(msg)) }
-
测试运行
-
-
阶段六
用户名修改
输入
rename|newName
格式修改用户名-
实现代码
user.go
func (u *User) DoMessage(msg string) { if msg == "who" { u.server.mapLock.Lock() for _, user := range u.server.OnlineMap { onlineMsg := "[" + user.Name + "] is online\n" u.sendMsg(onlineMsg) } u.server.mapLock.Unlock() } else if len(msg) > 7 && msg[:7] == "rename|" { newName := strings.Split(msg, "|")[1] _, isExist := u.server.OnlineMap[newName] if isExist { u.sendMsg("用户名已存在,修改失败") } else { u.server.mapLock.Lock() oldName := u.Name u.server.OnlineMap[newName] = u delete(u.server.OnlineMap, oldName) u.Name = newName u.sendMsg("用户名修改成功,[" + oldName + "]->[" + newName + "]") u.server.mapLock.Unlock() } } else { u.server.BroadCast(u, msg) } }
-
测试运行
-
-
阶段七
超时强踢
-
实现代码
server.go
func (s *Server) Handler(conn net.Conn) { fmt.Println("链接建立成功!!!") user := NerUser(conn, s) ////将当前用户插入map中 //s.mapLock.Lock() //s.OnlineMap[user.Name] = user //s.mapLock.Unlock() ////广播当前用户上线消息 //s.BroadCast(user, "已上线") user.Online() isLive := make(chan bool) //处理客户端发送的消息,由于是堵塞状态,新开一个goroutine go func() { buf := make([]byte, 4096) for { n, err := conn.Read(buf) if n == 0 { user.Offline() //s.BroadCast(user, "下线") return } if err != nil && err != io.EOF { fmt.Println("Conn Error:", err) return } msg := string(buf[:n-1]) //s.BroadCast(user, msg) user.DoMessage(msg) isLive <- true } }() for { select { case <-isLive: case <-time.After(time.Second * 10): user.sendMsg("你已超时,将被踢出下线") //关闭channel close(user.C) //关闭链接 conn.Close() delete(s.OnlineMap, user.Name) return } } }
-
测试运行
-
-
阶段八
私聊功能
格式:
to|username|msg
-
实现代码
func (u *User) DoMessage(msg string) { if msg == "who" { u.server.mapLock.Lock() for _, user := range u.server.OnlineMap { onlineMsg := "[" + user.Name + "] is online\n" u.sendMsg(onlineMsg) } u.server.mapLock.Unlock() } else if len(msg) > 7 && msg[:7] == "rename|" { newName := strings.Split(msg, "|")[1] _, isExist := u.server.OnlineMap[newName] if isExist { u.sendMsg("用户名已存在,修改失败") } else { u.server.mapLock.Lock() oldName := u.Name u.server.OnlineMap[newName] = u delete(u.server.OnlineMap, oldName) u.Name = newName u.sendMsg("用户名修改成功,[" + oldName + "]->[" + newName + "]") u.server.mapLock.Unlock() } } else if len(msg) > 3 && msg[:3] == "to|" { targetUserName := strings.Split(msg, "|")[1] if targetUserName == "" { u.sendMsg("发送私聊消息格式错误,正确格式:to|张三|你好呀") return } targetUser, isExist := u.server.OnlineMap[targetUserName] if !isExist { u.sendMsg("目标对象不存在,请仔细检查") return } content := strings.Split(msg, "|")[2] if content == "" { u.sendMsg("消息内容格式错误") return } targetUser.sendMsg("来自:[" + u.Name + "]的消息,内容是:" + content) } else { u.server.BroadCast(u, msg) } }
-
测试运行
-
-
代码
-
说明
使用
nc
工具进行测试nc是一个网络工具,全称为"Netcat"。它是一个功能强大的命令行工具,可用于在计算机网络上进行数据传输和网络连接的创建。nc可以在不同的操作系统上使用,包括Linux、Windows和macOS。
nc的主要功能包括:
- 端口扫描:通过指定目标IP地址和端口范围,nc可以扫描主机上开放的网络端口。
- 网络监听:nc可以作为一个网络服务器,监听指定的端口并接受传入的连接请求。
- 文件传输:通过nc,可以在网络上传输文件。你可以将文件从一个计算机发送到另一个计算机,或者将输出导向到文件。
- 网络调试和测试:nc可以用于测试网络服务和应用程序的连接和响应。你可以使用nc发送自定义的网络请求并查看服务器的响应。
- 网络代理:使用nc可以创建简单的网络代理,将数据从一个端口转发到另一个端口,从而实现端口转发和代理功能。
请注意,由于nc是一个强大的工具,也被一些恶意用户用于攻击或非法活动。因此,在使用nc时要遵守法律法规,并确保只在合法授权的网络环境中使用它。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。