300行代碼實(shí)現(xiàn)go語(yǔ)言即時(shí)通訊聊天室
學(xué)了2年Java,因?yàn)楣ぷ髟蛐枰D(zhuǎn)Golang,3天時(shí)間學(xué)習(xí)了下go的基本語(yǔ)法,做這樣一個(gè)聊天室小項(xiàng)目來(lái)鞏固串聯(lián)一下語(yǔ)法。
實(shí)現(xiàn)的功能:公聊,私聊,修改用戶名
只用到了四個(gè)類(lèi):
- main.go:用來(lái)啟動(dòng)服務(wù)器
- server.go:服務(wù)器相關(guān)代碼
- client.go:客戶端相關(guān)代碼,用戶可以直接操作的可視化界面
- user.go:用戶類(lèi),用來(lái)封裝用戶的業(yè)務(wù)邏輯
架構(gòu)圖

完整代碼
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) {
//業(yè)務(wù)邏輯
//fmt.Println("鏈接建立成功")
user := NewUser(conn, s)
user.Online()
//監(jiān)聽(tīng)用戶是否活躍
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:
//當(dāng)前用戶活躍,不做任何時(shí),激活select,重置定時(shí)器
case <-time.After(time.Second * 300):
//超時(shí),將user強(qiáng)制關(guān)閉
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(">>>>請(qǐng)輸入合法數(shù)字<<<<")
return false
}
}
//修改用戶名
func (c *Client) UpdateName() bool {
fmt.Println(">>>>請(qǐng)輸入用戶名")
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(">>>>請(qǐng)輸入聊天內(nèi)容,輸入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(">>>>請(qǐng)輸入聊天內(nèi)容,輸入exit退出")
fmt.Scanln(&chatMsg)
}
}
//私聊
func (c *Client) PrivateChat() {
var remoteUser string
var chatMsg string
c.SelectUsers()
fmt.Println(">>>>請(qǐng)輸入聊天對(duì)象的用戶名,輸入exit退出")
fmt.Scanln(&remoteUser)
for remoteUser != "exit" {
fmt.Println(">>>>請(qǐng)輸入聊天內(nèi)容,輸入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(">>>>請(qǐng)輸入聊天內(nèi)容,輸入exit退出")
fmt.Scanln(&chatMsg)
}
c.SelectUsers()
remoteUser = ""
fmt.Println(">>>>請(qǐng)輸入聊天對(duì)象的用戶名,輸入exit退出")
fmt.Scanln(&remoteUser)
}
}
//查詢?cè)诰€用戶
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", "設(shè)置服務(wù)器IP地址(默認(rèn)為127.0.0.1)")
flag.IntVar(&serverPort, "port", 8888, "設(shè)置服務(wù)器端口(默認(rèn)為8888)")
}
func main() {
flag.Parse()
client := NewClient(serverIp, serverPort)
if client == nil {
fmt.Println(">>>>鏈接服務(wù)器失敗")
return
}
go client.DealResponse()
fmt.Println(">>>>鏈接服務(wù)器成功")
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, "下線")
}
//給當(dāng)前user的客戶端發(fā)送消息
func (u *User) SendMsg(msg string) {
u.conn.Write([]byte(msg))
}
//處理消息
func (u *User) DoMessage(msg string) {
if msg == "who" {
//查詢當(dāng)前在線用戶
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]
//判斷名字是否已經(jīng)存在
_, 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|你好
//獲取對(duì)方用戶名
remoteName := strings.Split(msg, "|")[1]
if remoteName == "" {
u.SendMsg("用戶名格式不對(duì)\n")
return
}
//獲取對(duì)方user
remoteUser, ok := u.server.OnlineMap[remoteName]
if !ok {
u.SendMsg("用戶不存在\n")
return
}
//獲取消息
msg := strings.Split(msg, "|")[2]
if msg == "" {
u.SendMsg("無(wú)消息內(nèi)容,重新發(fā)送\n")
}
//發(fā)送消息
remoteUser.SendMsg(u.Name + "對(duì)您說(shuō):" + 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()
}
到此這篇關(guān)于300行代碼實(shí)現(xiàn)go語(yǔ)言即時(shí)通訊聊天室的文章就介紹到這了,更多相關(guān)go語(yǔ)言即時(shí)通訊聊天室內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang連接mysql數(shù)據(jù)庫(kù)操作使用示例
這篇文章主要為大家介紹了golang連接mysql數(shù)據(jù)庫(kù)操作使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
基于go interface{}==nil 的幾種坑及原理分析
這篇文章主要介紹了基于go interface{}==nil 的幾種坑及原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
Go1.21新增內(nèi)置函數(shù)(built-in?functions)詳解
Go?1.21新增的內(nèi)置函數(shù)分別是?min、max?和?clear,這篇文章主要帶大家一起了解一下這幾個(gè)函數(shù)的用途和使用示例,感興趣的小伙伴可以學(xué)習(xí)一下2023-08-08
Golang?Gin框架獲取請(qǐng)求參數(shù)的幾種常見(jiàn)方式
在我們平常添加路由處理函數(shù)之后,就可以在路由處理函數(shù)中編寫(xiě)業(yè)務(wù)處理代碼了,但在此之前我們往往需要獲取請(qǐng)求參數(shù),本文就詳細(xì)的講解下gin獲取請(qǐng)求參數(shù)常見(jiàn)的幾種方式,需要的朋友可以參考下2024-02-02
深入解析Go語(yǔ)言編程中slice切片結(jié)構(gòu)
這篇文章主要介紹了Go語(yǔ)言編程中slice切片結(jié)構(gòu),其中Append方法的用法介紹較為詳細(xì),需要的朋友可以參考下2015-10-10
基于golang的簡(jiǎn)單分布式延時(shí)隊(duì)列服務(wù)的實(shí)現(xiàn)
Go語(yǔ)音開(kāi)發(fā)中常見(jiàn)Error類(lèi)型處理示例詳解
Go語(yǔ)言實(shí)現(xiàn)websocket推送程序

