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

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

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

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

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

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

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