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

学了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 + "\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 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()
}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,我们需要了解两个概念:客户端和服务器端。客户端是指用户使用的设备,如电脑、手机等;服务器端是指提供服务的计算机,如网站、聊天室等。 在本例中,我们需要用到以下技术:HTML、CSS、JavaScript 和 Go 语言。其中,HTML、CSS 和 JavaScript 用于开发客户端,Go 语言用于开发服务器端。 以下是一个简单的聊天室实现步骤: 1. 创建 HTML 页面,包括聊天室界面和发送消息的表单。 ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Chat Room</title> <style> /* 样式表 */ </style> </head> <body> <div id="chatroom"> <!-- 聊天室界面 --> </div> <form id="message-form"> <input type="text" id="message-input"> <button type="submit">Send</button> </form> <script src="chatroom.js"></script> </body> </html> ``` 2. 创建 CSS 样式表。 ```css /* 样式表 */ ``` 3. 创建 JavaScript 文件 chatroom.js,实现客户端与服务器端通信和消息显示功能。 ```javascript // 创建 WebSocket 连接 const ws = new WebSocket("ws://localhost:8000/ws"); // 监听连接事件 ws.addEventListener("open", event => { console.log("Connected to server"); }); // 监听消息事件 ws.addEventListener("message", event => { const message = event.data; // 在聊天室界面中显示消息 }); // 监听表单提交事件 const form = document.getElementById("message-form"); form.addEventListener("submit", event => { event.preventDefault(); // 阻止表单提交 const input = document.getElementById("message-input"); const message = input.value; input.value = ""; // 清空输入框 // 发送消息到服务器 ws.send(message); }); ``` 4. 创建 Go 语言服务器端,实现 WebSocket 连接和消息转发功能。 ```go package main import ( "fmt" "net/http" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } func main() { http.HandleFunc("/ws", handleWebSocket) http.ListenAndServe(":8000", nil) } func handleWebSocket(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { fmt.Println(err) return } defer conn.Close() for { // 接收消息 _, message, err := conn.ReadMessage() if err != nil { fmt.Println(err) break } // 转发消息到所有客户端 for _, c := range clients { err = c.WriteMessage(websocket.TextMessage, message) if err != nil { fmt.Println(err) break } } } } ``` 以上就是一个简单的聊天室实现步骤。需要注意的是,本例中的服务器端只是一个简单的示例,实际应用中需要考虑更多的安全性和可靠性问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值