加入go行列的一个敲门砖吗----小玩意cs多人即时聊天,没有用数据库的,没有用框架的
Posted 老虎中的小白Gentle
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多人即时聊天,没有用数据库的,没有用框架的的主要内容,如果未能解决你的问题,请参考以下文章