300行代码实现go语言即时通讯聊天室

2022-05-13 13:49:36

学了2年Java,因为工作原因需要转Golang,3天时间学习了下go的基本语法,做这样一个聊天室小项目来巩固串联一下语法。

实现的功能:公聊,私聊,修改用户名

只用到了四个类:

    main.go:用来启动服务器server.go:服务器相关代码client.go:客户端相关代码,用户可以直接操作的可视化界面user.go:用户类,用来封装用户的业务逻辑

    架构图

    完整代码

    server.go

    package main
    
    import (
    	"fmt"
    	"io"
    	"net"
    	"sync"
    	"time"
    )
    
    type Server struct {
    	Ip   string
    	Port int
    	//在线用户列表
    	OnlineMap map[string]*User
    	mapLock   sync.RWMutex
    	//消息广播的Channel
    	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
    }
    
    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.Message
    
    		s.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 + ":" + msg
    	s.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 main
    
    import (
    	"flag"
    	"fmt"
    	"io"
    	"net"
    	"os"
    )
    
    type Client struct {
    	ServerIp   string
    	ServerPort int
    	Name       string
    	conn       net.Conn
    	flag       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 = conn
    	return client
    }
    
    func (c *Client) menu() bool {
    	var flag int
    
    	fmt.Println("1.公聊模式")
    	fmt.Println("2.私聊模式")
    	fmt.Println("3.修改用户名")
    	fmt.Println("0.退出")
    
    	fmt.Scanln(&flag)
    	if flag >= 0 && flag <= 3 {
    		c.flag = flag
    		return 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 string
    	fmt.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 string
    	var chatMsg string
    
    	c.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 + "nn"
    				_, 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 := "whon"
    	_, 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 int
    
    func 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 main
    
    import (
    	"net"
    	"strings"
    )
    
    type User struct {
    	Name   string
    	Addr   string
    	C      chan string
    	conn   net.Conn
    	server *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] = u
    	u.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|xxx
    		newName := 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] = u
    			u.server.mapLock.Unlock()
    
    			u.Name = newName
    			u.SendMsg("用户名成功修改为:" + newName + "n")
    		}
    
    	} else if len(msg) > 4 && msg[:3] == "to|" {
    		//私聊  to|zhangsan|你好
    
    		//获取对方用户名
    		remoteName := strings.Split(msg, "|")[1]
    		if remoteName == "" {
    			u.SendMsg("用户名格式不对n")
    			return
    		}
    		//获取对方user
    		remoteUser, 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.C
    		u.conn.Write([]byte(msg + "n"))
    	}
    }
    
    

    main.go

    package main
    
    func main() {
    	server := NewServer("127.0.0.1", 8888)
    	server.Start()
    }
    
    

    到此这篇关于300行代码实现go语言即时通讯聊天室的文章就介绍到这了,更多相关go语言即时通讯聊天室内容请搜索易采站长站以前的文章或继续浏览下面的相关文章希望大家以后多多支持易采站长站!