学了2年Java,因为工作原因需要转Golang,3天时间学习了下go的基本语法,做这样一个聊天室小项目来巩固串联一下语法。
实现的功能:公聊,私聊,修改用户名
只用到了四个类:
- main.go:用来启动服务器
- server.go:服务器相关代码
- client.go:客户端相关代码,用户可以直接操作的可视化界面
- user.go:用户类,用来封装用户的业务逻辑
架构图
完整代码
server.go
package mainimport ("fmt""io""net""sync""time"
)type Server struct {Ip stringPort int//在线用户列表OnlineMap map[string]*UsermapLock sync.RWMutex//消息广播的ChannelMessage 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
}func (s *Server) Handler(conn net.Conn) {//业务逻辑//fmt.Println("链接建立成功")user := NewUser(conn, s)user.Online()//监听用户是否活跃isLive := make(chan bool)go func() {buf := make([]byte, 4096)for {n, error := conn.Read(buf)if n == 0 {user.Offline()return}if error != nil && error != io.EOF {fmt.Println("read error")}msg := string(buf[:n-1])user.DoMessage(msg)//表示用户活跃isLive <- true}}()for {select {case <-isLive://当前用户活跃,不做任何时,激活select,重置定时器case <-time.After(time.Second * 300)://超时,将user强制关闭user.SendMsg("你被踢了")close(user.C)conn.Close()return}}
}func (s *Server) ListenMessager() {for {msg := <-s.Messages.mapLock.Lock()for _, user := range s.OnlineMap {user.C <- msg}s.mapLock.Unlock()}
}func (s *Server) BroadCast(user *User, msg string) {sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msgs.Message <- sendMsg
}func (s *Server) Start() {listener, error := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))if error != nil {fmt.Println("listener error...")return}defer listener.Close()go s.ListenMessager()for {conn, error := listener.Accept()if error != nil {fmt.Println("accept error...")continue}go s.Handler(conn)}}
client.go
package mainimport ("flag""fmt""io""net""os"
)type Client struct {ServerIp stringServerPort intName stringconn net.Connflag int
}func NewClient(serverIp string, serverPort int) *Client {client := &Client{ServerIp: serverIp,ServerPort: serverPort,flag: 9999,}conn, error := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))if error != nil {fmt.Println("net dial error...")return nil}client.conn = connreturn client
}func (c *Client) menu() bool {var flag intfmt.Println("1.公聊模式")fmt.Println("2.私聊模式")fmt.Println("3.修改用户名")fmt.Println("0.退出")fmt.Scanln(&flag)if flag >= 0 && flag <= 3 {c.flag = flagreturn true} else {fmt.Println(">>>>请输入合法数字<<<<")return false}
}//修改用户名
func (c *Client) UpdateName() bool {fmt.Println(">>>>请输入用户名")fmt.Scanln(&c.Name)sendMsg := "rename|" + c.Name + "\n"_, error := c.conn.Write([]byte(sendMsg))if error != nil {fmt.Println("conn.write error...")return false}return true
}//公聊
func (c *Client) PublicChat() {var chatMsg stringfmt.Println(">>>>请输入聊天内容,输入exit退出")fmt.Scanln(&chatMsg)for chatMsg != "exit" {if len(chatMsg) != 0 {msg := chatMsg + "\n"_, error := c.conn.Write([]byte(msg))if error != nil {fmt.Println("conn.Write error....")break}}chatMsg = ""fmt.Println(">>>>请输入聊天内容,输入exit退出")fmt.Scanln(&chatMsg)}
}//私聊
func (c *Client) PrivateChat() {var remoteUser stringvar chatMsg stringc.SelectUsers()fmt.Println(">>>>请输入聊天对象的用户名,输入exit退出")fmt.Scanln(&remoteUser)for remoteUser != "exit" {fmt.Println(">>>>请输入聊天内容,输入exit退出")fmt.Scanln(&chatMsg)for chatMsg != "exit" {if len(chatMsg) != 0 {msg := "to|" + remoteUser + "|" + chatMsg + "\n\n"_, error := c.conn.Write([]byte(msg))if error != nil {fmt.Println("conn.Write error....")break}}chatMsg = ""fmt.Println(">>>>请输入聊天内容,输入exit退出")fmt.Scanln(&chatMsg)}c.SelectUsers()remoteUser = ""fmt.Println(">>>>请输入聊天对象的用户名,输入exit退出")fmt.Scanln(&remoteUser)}}//查询在线用户
func (c *Client) SelectUsers() {sendMsg := "who\n"_, error := c.conn.Write([]byte(sendMsg))if error != nil {fmt.Println("conn.Write error....")return}
}//处理server返回的消息
func (c *Client) DealResponse() {io.Copy(os.Stdout, c.conn)
}
func (c *Client) Run() {for c.flag != 0 {for c.menu() != true {}switch c.flag {case 1://公聊c.PublicChat()case 2://私聊c.PrivateChat()case 3://修改用户名c.UpdateName()}}
}var serverIp string
var serverPort intfunc init() {flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认为127.0.0.1)")flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认为8888)")
}
func main() {flag.Parse()client := NewClient(serverIp, serverPort)if client == nil {fmt.Println(">>>>链接服务器失败")return}go client.DealResponse()fmt.Println(">>>>链接服务器成功")client.Run()
}
user.go
package mainimport ("net""strings"
)type User struct {Name stringAddr stringC chan stringconn net.Connserver *Server
}func NewUser(conn net.Conn, server *Server) *User {userAddr := conn.RemoteAddr().String()user := &User{Name: userAddr,Addr: userAddr,C: make(chan string),conn: conn,server: server,}go user.ListenMessage()return user
}//用户上线
func (u *User) Online() {u.server.mapLock.Lock()u.server.OnlineMap[u.Name] = uu.server.mapLock.Unlock()u.server.BroadCast(u, "上线")
}//用户下线
func (u *User) Offline() {u.server.mapLock.Lock()delete(u.server.OnlineMap, u.Name)u.server.mapLock.Unlock()u.server.BroadCast(u, "下线")
}//给当前user的客户端发送消息
func (u *User) SendMsg(msg string) {u.conn.Write([]byte(msg))
}//处理消息
func (u *User) DoMessage(msg string) {if msg == "who" {//查询当前在线用户u.server.mapLock.Lock()for _, user := range u.server.OnlineMap {onlineMsg := "[" + user.Addr + "]" + user.Name + ":在线...\n"u.SendMsg(onlineMsg)}u.server.mapLock.Unlock()} else if len(msg) > 7 && msg[:7] == "rename|" {//修改用户名 rename|xxxnewName := strings.Split(msg, "|")[1]//判断名字是否已经存在_, ok := u.server.OnlineMap[newName]if ok {u.SendMsg("用户名已存在\n")} else {u.server.mapLock.Lock()delete(u.server.OnlineMap, u.Name)u.server.OnlineMap[newName] = uu.server.mapLock.Unlock()u.Name = newNameu.SendMsg("用户名成功修改为:" + newName + "\n")}} else if len(msg) > 4 && msg[:3] == "to|" {//私聊 to|zhangsan|你好//获取对方用户名remoteName := strings.Split(msg, "|")[1]if remoteName == "" {u.SendMsg("用户名格式不对\n")return}//获取对方userremoteUser, ok := u.server.OnlineMap[remoteName]if !ok {u.SendMsg("用户不存在\n")return}//获取消息msg := strings.Split(msg, "|")[2]if msg == "" {u.SendMsg("无消息内容,重新发送\n")}//发送消息remoteUser.SendMsg(u.Name + "对您说:" + msg)} else {u.server.BroadCast(u, msg)}
}func (u *User) ListenMessage() {for {msg := <-u.Cu.conn.Write([]byte(msg + "\n"))}
}
main.go
package mainfunc main() {server := NewServer("127.0.0.1", 8888)server.Start()
}