Go-聊天室

用go写一个聊天室

参考https://www.bilibili.com/video/BV1ub411p7cz?p=28
服务端:服务端作为转发服务器,接收客户端的数据,根据客户端信息中的目的ip进行转发,因此每个客户端接入的时候,我们需要保存其地址及对应的net.Conn,因此用一个map来保存。主线程的作用在于接收客户端,接收到一个客户端就开启一个协程来处理该客户端发送的数据,另外我们设置一个string通道,用来保存数据,协程接收客户端发送的数据保存入string通道,string通道的作用就是一个信息队列,我们另外开启一个消费者协程来处理这些数据,这个消费者协程的作用在于读取通道中的数据,然后根据数据中的目的ip转发数据过去。
客户端:客户端必须具备发送和接收功能,因此我们通过主线程接入服务器后,主线程的功能用来接收服务器转发过来的数据,打印出来,另外我们开启一个协程用来读取标准输入的数据,发送给服务端
附上服务器和客户端的代码

package main

import (
	"fmt"
	"log" //书写日志
	"net"
	"os"
	"strings"
)

//全局变量
const(
	 LOGDIR = "./logTest.log"
)
//用来保存接入服务器的客户端级消息队列
var connsMap = make(map[string]net.Conn,1000)
var messagesChan = make(chan string,1000)
var quitChan = make(chan bool)
var logFile os.File
var logger *log.Logger
//日志

func CheckError(err error){
	if err!=nil{
		fmt.Println("Error:%s",err.Error())
		os.Exit(1)
	}
}
func ReceiveFromClient(conn net.Conn){
	//创建字节数组接收数据
	//协程结束除了关闭连接,还要清除map
	//获取客户端地址
	clientAddr := fmt.Sprintf("%s",conn.RemoteAddr())
	defer func(conn net.Conn){
		addr := fmt.Sprintf("%s",conn.RemoteAddr())
		delete(connsMap,addr)
		conn.Close()
		fmt.Println(addr + "下线了!")
		logger.Println(addr + "下线了!")
	}(conn)
	buf := make([]byte,1024)
	for{
		//读取数据
		numOfBytes,err := conn.Read(buf)
		//把接收到的数据写入消息队列中
		if err != nil{
			break
			//如果读取不到数据则会断开,断开应该把map中的conn去掉
		}
		if numOfBytes > 0 {
			//把数据和地址合并一下
			message := clientAddr + "#" +string(buf[:numOfBytes])
			messagesChan<-string(message)
		}
	}

}
func messageChanProcess(){
	//消息队列处理协程,专门用来转发线程
	for{
		select{
			case data :=<-messagesChan:
				//如果消息队列有数据,就处理数据,
				 messageProcess(data)
			case  <-quitChan:
				//如果这个退出通道接收的数据则退出
				break;
		}
	}
}
func messageProcess(data string){
	//用来处理数据
	//根据#号拆分字符串,拆分出ip地址跟信息两部分
	str := strings.Split(data,"#")
	//除去一个个人地址,目标地址,len大于2说明还有需要传送的数据
	if len(str) > 2{
		//取出ip地址,之后的数据再次按照#连接,只断开第一个#
		clientAddr := str[0]
		toAddr := str[1]
		message := strings.Join(str[2:],"#")
		clientAddr = strings.Trim(clientAddr," ")
		toAddr = strings.Trim(toAddr," ")
		//判断map中是否存在该conn
		if conn,ok := connsMap[toAddr];ok{
			 //有的话就写给它
			message = "from "+clientAddr+" :"+message
			_,err := conn.Write([]byte(message))
			if err != nil{
				fmt.Println("send fail:"+err.Error());
				logger.Fatal(message + "send fail:" + err.Error())
			}
		}
	}else{
		//说明len<=2,那就是指令
		switch strings.ToUpper(str[1]) {
			case "LIST":
				//返回当前在线的客户端ip
				ReturnOnlineClient(str[0])
			default:
				//返回提示:无效的指令,指定ip#信息
				ReturnNoEcho(str[0])
		}
	}
}
func ReturnOnlineClient(addr string){
	//返回当前在线的客户端ip
	message := "current online client :\n"
	for clientAddr,_ := range(connsMap){
		message += clientAddr + "\n"
	}
	conn := connsMap[addr]
	_,err := conn.Write([]byte(message))
	if err != nil{
		fmt.Println("send failure:"+err.Error())
		logger.Fatal(message + "send fail:" + err.Error())
	}
}
func ReturnNoEcho(addr string){
	message := "指令无效,请输入:IP地址#message"
	conn := connsMap[addr]
	_,err := conn.Write([]byte(message))
	if err!= nil{
		fmt.Println("send failure " + err.Error())
		logger.Fatal(message + "send fail:" + err.Error())
	}
}
func main(){
	//初始化log文件
	logFile,err := os.OpenFile(LOGDIR,os.O_RDWR|os.O_CREATE,0666)
	if err!=nil{
		fmt.Println("log file create failutre!")
		os.Exit(1);
	}
	defer logFile.Close()
	logger = log.New(logFile,"\r\n",log.Ldate|log.Ltime|log.Llongfile)
	//监听
	listen_socket,err := net.Listen("tcp","127.0.0.1:8000")
	CheckError(err)
	defer listen_socket.Close()
	fmt.Println("server is waiting ... ")
	logger.Println("server is begining now")
	//服务器作为中转服务器,需要读取客户端的ip地址,保存起来
	//开启一个协程用来接收客户端的数据。我们将数据写入通道
	//开启另一个协程用来处理通道的数据,按客户端指定的ip地址发送给其他客户端
	//开启线程来处理消息队列
	go messageChanProcess()
	for{
		//接收连接
		conn,err := listen_socket.Accept()
		CheckError(err)
		//接收到客户端就写入ConnsMap中
		addr := fmt.Sprintf("%s",conn.RemoteAddr())
		connsMap[addr] = conn
		//打印当前接入的ip号
		fmt.Printf("%s online!\n",addr)
		logger.Printf("%s online!",addr)
		//开启线程接收该连接发送的数据
		go ReceiveFromClient(conn)
	}


}

客户端

package main
import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
)
func CheckError(err error){
	if err!=nil{
		panic(err)
	}
}
func WriteInfo(conn net.Conn){
	//把标准输入包装成输入流
	reader := bufio.NewReader(os.Stdin)
	//用reader读取标准输入
	for {
		data, _, _ := reader.ReadLine()
		input := string(data)
		//如果是退出字符串则退出
		if strings.ToUpper(input) == "EXIT"{
			conn.Close()
			break;
		}
		_,err  := conn.Write([]byte(input))
		if err != nil{
			conn.Close();
			fmt.Println("client connection error:"+err.Error())
			break;
		}
	}
}
func main(){
	conn,err := net.Dial("tcp","127.0.0.1:8000")
	//CheckError(err)
	if err!=nil{
		fmt.Println("连接服务器失败,检查服务器是否未开启")
		os.Exit(1)
	}
	defer conn.Close()
	//我们开启一个协程来负责发送数据
	go WriteInfo(conn)
	//主线程来负责接收数据
	for{
		buf := make([]byte,1024)
		_,err = conn.Read(buf)
		//CheckError(err)
		if err!=nil{
			//说明已退出
			fmt.Println("您已退出,欢迎再次使用!")
			//正常退出码是0,不正常退出未非0
			os.Exit(0)
		}
		fmt.Println("client receiver data "+string(buf))
	}
	fmt.Println("Close client")

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值