加入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多人即时聊天,没有用数据库的,没有用框架的的主要内容,如果未能解决你的问题,请参考以下文章

FPS 来了,Steam 3 款多人射击游戏推荐

《CS:GO》Steam国区加入免费版下载 直接安装就能玩

玩家可以加入多个房间吗?双关语

深入浅出Go语言的库源码文件

shell安全防范———慎将当前目录.加入PATH~~~之~隔壁老王来敲门

《CS:GO》“沙漠2”重制版亮相 惊艳又熟悉的味道