加入go行列的一个敲门砖吗----小玩意cs多人即时聊天,没有用数据库的,没有用框架的
Posted 有关于S27的故事
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了加入go行列的一个敲门砖吗----小玩意cs多人即时聊天,没有用数据库的,没有用框架的相关的知识,希望对你有一定的参考价值。
这个项目实现的内容有如下:
1.公聊
2.私聊
3.修改用户名
4.超时强踢
这个项目的缺点:
1.用的tcp协议,但没有考虑到网络传输中丢包的问题
2.没有用json格式传输数据
3.没有用数据库,只是实时的传输信息,甚至没有登录校验
这个项目的分层结构
1.客户端 client.go
2.服务端 server.go main.go user.go
源代码如下
main.go
package main
func main()
server := NewServer("0.0.0.0", 8888)
server.Start()
server.go
package main
import (
"fmt"
"io"
"net"
"runtime"
"sync"
"time"
)
type Server struct
Ip string
Port int
//user online list
OnlineMap map[string]*User
MapLock sync.RWMutex
//the channel for the message broadcast
MessageChan chan string
func NewServer(ip string, port int) *Server
server := &Server
Ip: ip,
Port: port,
OnlineMap: make(map[string]*User),
MessageChan: make(chan string),
return server
func (this *Server) Handler(conn net.Conn)
user := NewUser(conn, this)
//user online,join user to OnlineMap
// this.MapLock.Lock()
// this.OnlineMap[user.Name] = user
// this.MapLock.Unlock()
//Broading the current user online message
// this.BroadCast(user, "I am online")
user.Online()
//the channel for whether the user is active
isLive := make(chan bool)
//accept the messages sent from the client
go func()
buf := make([]byte, 4096)
for
n, err := conn.Read(buf)
if n == 0
user.Outline()
return
if err != nil && err != io.EOF
fmt.Println("Coon Read err:", err)
return
//get rid of '\\n'
msg := string(buf[:n-1])
user.DoMessage(msg)
//user send any msg represent that is active
isLive <- true
()
//Blocking current Hander()
for
select
case <-isLive:
// this user is active, and do anything for reset timer
case <-time.After(time.Second * 300):
//time out, this user inactive
user.SendMessage("You were forced back!")
//destroy the resource for user
close(user.stringChan)
//close the link
conn.Close()
//drop out the goruntine
runtime.Goexit()
func (this *Server) BroadCast(user *User, msg string)
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
this.MessageChan <- sendMsg
//the goroutine of channel,listen for the broadcast message
func (this *Server) ListenMessager()
for
msg := <-this.MessageChan
//send the msg for all onlined user
this.MapLock.Lock()
for _, cli := range this.OnlineMap
cli.stringChan <- msg
this.MapLock.Unlock()
//the function that starts the server
func (this *Server) Start()
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))
if err != nil
fmt.Println("net.Listen err:", err)
return
//close listen socket , run at the end of the function
defer listener.Close()
//turn on one goroutine for listening Message
go this.ListenMessager()
for
conn, err := listener.Accept()
if err != nil
fmt.Println("listnner accept err:", err)
continue
//do handler
go this.Handler(conn)
user.go
package main
import (
"fmt"
"net"
"strings"
)
type User struct
Name string
Addr string
stringChan chan string
conn net.Conn
//for users to associate with the service
server *Server
//Create a user
func NewUser(conn net.Conn, server *Server) *User
userAddr := conn.RemoteAddr().String()
user := &User
Name: userAddr,
Addr: userAddr,
stringChan: make(chan string),
conn: conn,
server: server,
//Start the listening user channel message goroutine
go user.ListenMessage()
return user
func (this *User) ListenMessage()
for
msg := <-this.stringChan
this.conn.Write([]byte(msg + "\\n"))
//user online service
func (this *User) Online()
//join in user map
this.server.MapLock.Lock()
this.server.OnlineMap[this.Name] = this
this.server.MapLock.Unlock()
this.server.BroadCast(this, "online")
//user outline service
func (this *User) Outline()
//delete from map
this.server.MapLock.Lock()
delete(this.server.OnlineMap, this.Name)
this.server.MapLock.Unlock()
this.server.BroadCast(this, "outline")
//
func (this *User) SendMessage(msg string)
this.conn.Write([]byte(msg))
//user broadcast service
func (this *User) DoMessage(msg string)
//look online user list
if msg == "who"
this.server.MapLock.Lock()
for _, user := range this.server.OnlineMap
onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "online...\\n"
this.SendMessage(onlineMsg)
this.server.MapLock.Unlock()
//change user name
else if len(msg) > 7 && msg[:7] == "rename|"
newName := strings.Split(msg, "|")[1]
//judge whether the name exsits
_, ok := this.server.OnlineMap[newName]
if ok
this.SendMessage("this name was used\\n")
else
this.server.MapLock.Lock()
delete(this.server.OnlineMap, this.Name)
this.server.OnlineMap[newName] = this
this.server.MapLock.Unlock()
this.Name = newName
this.SendMessage("you have changed the user name:" + this.Name + "\\n")
else if len(msg) > 4 && msg[:3] == "to|"
//msg format to|name|sayword
//1.get the user name that username who recevied the message
receverName := strings.Split(msg, "|")[1]
if receverName == ""
this.SendMessage("msg format \\"to|name|sayword\\"\\n")
return
//get the user object by recevied name
receverUser, ok := this.server.OnlineMap[receverName]
if !ok
this.SendMessage(fmt.Sprintf("%v the user does not exist\\n", receverUser))
return
//get message content and send to recever
content := strings.Split(msg, "|")[2]
if content == ""
this.SendMessage("No message, please send again")
return
receverUser.SendMessage("[" + this.Name + "]" + "send:" + content)
else
this.server.BroadCast(this, msg)
client.go
package main
import (
"flag"
"fmt"
"io"
"net"
"os"
)
type Client struct
ServerIP string
ServerPort int
Name string
Coon net.Conn
flag int
func NewClient(serverIp string, serverPort int) *Client
client := &Client
ServerIP: serverIp,
ServerPort: serverPort,
flag: 999,
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", serverIp, serverPort))
if err != nil
fmt.Println("net.Dial err:", err)
return nil
client.Coon = conn
return client
func (this *Client) menu() bool
var flag int
fmt.Println("1.public chat mode")
fmt.Println("2.private chat mode")
fmt.Println("3.Change user name")
fmt.Println("0.quit")
fmt.Scanln(&flag)
if flag >= 0 && flag <= 3
this.flag = flag
return true
else
fmt.Println(">>>>>Please enter number within the legal range<<<<<<<<<<<")
return false
func (this *Client) Run()
for this.flag != 0
for this.menu() != true
switch this.flag
case 1:
this.PublicChat()
break
case 2:
this.PrivateChat()
break
case 3:
this.UpdateName()
break
func (this *Client) SelectUser()
sendMsg := "who\\n"
_, err := this.Coon.Write([]byte(sendMsg))
if err != nil
fmt.Println("conn Write err:", err)
return
func (this *Client) PrivateChat()
var remoteName string
var chatMsg string
//who all user first
this.SelectUser()
fmt.Println(">>>>>Please input username,and qiut when input exit : ")
fmt.Scanln(&remoteName)
for remoteName != "exit"
fmt.Println(">>>>please input message,and qiut when input exit :")
fmt.Scanln(&chatMsg)
for chatMsg != "exit"
if len(chatMsg) != 0
sendMsg := "to|" + remoteName + "|" + chatMsg + "\\n\\n"
_, err := this.Coon.Write([]byte(sendMsg))
if err != nil
fmt.Println("Coon Write err:", err)
break
chatMsg = ""
fmt.Println(">>>>please input message,and qiut when input exit :")
fmt.Scanln(&chatMsg)
this.SelectUser()
remoteName = ""
fmt.Println(">>>>>Please input username,and qiut when input exit : ")
fmt.Scanln(&remoteName)
func (this *Client) PublicChat()
var chatMsg string
fmt.Println(">>>>>>please enter the chat connet,qiut when enter exit")
fmt.Scanln(&chatMsg)
for chatMsg != "exit"
//send msg to server
if len(chatMsg) != 0
sendMsg := chatMsg + "\\n"
_, err := this.Coon.Write([]byte(sendMsg))
if err != nil
fmt.Println("coon Write err:", err)
break
chatMsg = ""
fmt.Println(">>>>>>please enter the chat connet,qiut when enter exit")
fmt.Scanln(&chatMsg)
func (this *Client) UpdateName() bool
fmt.Println(">>>please input user name:")
fmt.Scanln(&this.Name)
sendMsg := "rename|" + this.Name + "\\n"
_, err := this.Coon.Write([]byte(sendMsg))
if err != nil
fmt.Println("conn.Write err:", err)
return false
return true
//deal server send msg, and show to stdout
func (this *Client) DealResponse()
// for
// buf := make([]byte ,1024)
// this.Coon.Read(buf)
// fmt.Println(buf)
//
io.Copy(os.Stdout, this.Coon)
var serverIp string
var serverPort int
//run before the main func
func init()
//./client -ip 127.0.0.1 -port 8888
flag.StringVar(&serverIp, "ip", "159.75.91.76", "set server ip(default is 159.75.91.76)")
flag.IntVar(&serverPort, "port", 8888, "set up server port(default is 8888)")
func main()
flag.Parse()
client := NewClient(serverIp, serverPort)
if client == nil
fmt.Println(">>>>>>>>>>>>>>>>>>>>>Link server failed.....")
return
//setup goruntine to deal with service msg
go client.DealResponse()
fmt.Println(">>>>>>>>>>>>>>>>>>>>>Link server success............")
//Start the client business
//select
client.Run()
关于编译
关于运行
客户端
./client -ip 106.55.36.146 -port 8888
ip 后面的地址就是你服务器运行的地址,端口我server写死了8888
服务端
运行后就一直在这里阻塞等待客户端来连接了,你可以多加点提示
后续
我这个是学习了丹冰的8小时入门go写的cs即时聊天,我想学习完redis再写一个可以离线聊天的,需要登录的、以及考虑到丢包问题的一个版本,再学习丹冰的zinx框架再搞一个版本
刘丹冰Aceld的主页 https://space.bilibili.com/373073810/
以上是关于加入go行列的一个敲门砖吗----小玩意cs多人即时聊天,没有用数据库的,没有用框架的的主要内容,如果未能解决你的问题,请参考以下文章