前面我们实现了大部分聊天室的功能,但有个核心的功能没做好,那就是每个客户端无法显示当前在线的客户有哪些。现在就增加这个功能。

在客户端和服务端之间的网路协议增加一条:

当客户端和网络端用*隔开的时候,就表示客户端要向服务端发送命令,现在暂定命令是members,然后服务端就向客户端发送当前所有在线客户的列表。

在增加一个实用的功能,那就是服务端把日志写到日志文件里面去。

当然,前面的例子只实现了客户端之间通信,没有实现聊天室广播通信的功能,在这里实现了。

sever代码

package main

import (
	"errors"
	"log"
	"net"
	"os"

	//"github.com/deckarep/golang-set"
	"strings"
)
type MsgWapper struct {
	ClientMsg string
	From string
}

func ParseClientMsg(msgPacket string) (string, string, error){
	listOfStr := strings.Split(msgPacket, "#")
	if len(listOfStr) < 2 {
		return "","",errors.New("message format error")
	}
	addr := listOfStr[0]
	msg := listOfStr[1]
	return addr,msg,nil
}

func ParseClientCommand(msgPacket string) (string, string, error){
	listOfStr := strings.Split(msgPacket, "*")
	if len(listOfStr) < 2 {
		return "","",errors.New("command format error")
	}
	command := listOfStr[0]
	data := listOfStr[1]
	return command,data,nil
}

func HandleMsg() {
	for {
		msgWapper := <- msgQueue
		addr, msg, err := ParseClientMsg(msgWapper.ClientMsg)
		if err != nil {
			command, data, err := ParseClientCommand(msgWapper.ClientMsg)
			if err != nil {
				logger.Fatalln(err)
				continue
			}
			if strings.ToUpper(command) == "MEMBERS" {
				 conn, ok := clients[msgWapper.From]
				 if !ok {
				 	logger.Fatalln(data +" is offline")
				 	continue
				 }
				 _, err = conn.Write([]byte(PrintAllClient()))
				 if err != nil {
				 	logger.Fatal(err.Error())
				 	continue
				 }
			}
			continue
		}
		if addr == "" {
			BroadCast(msgWapper.From+" say: "+msg)
		} else {
			clientConn, ok := clients[addr]
			if !ok {
				logger.Fatalln("client is offline")//最好是写回给客户端
				_, err = clientConn.Write([]byte(addr + " is offline"))
				if err != nil {
					logger.Fatalln(err.Error())
					continue
				}
				continue
			}
			_, err = clientConn.Write([]byte(msg))
			if err != nil {
				logger.Fatalln(err.Error())
				continue
			}
		}
	}
}

func handleOneClient(client net.Conn) {
	defer client.Close()
	buf := make([]byte, 1024)
	for {
		nbytes, err := client.Read(buf)

		if err != nil {
			return
		}
		msgQueue <- MsgWapper{ClientMsg:string(buf[:nbytes]), From:client.RemoteAddr().String()}
	}
	logger.Println("client close.")
}

var clients map[string]net.Conn
var msgQueue chan MsgWapper
var logger *log.Logger

func PrintAllClient() string {
	var clientsList string
	for i := range clients {
		clientsList += i+"\n"
	}
	return clientsList
}

func BroadCast(msg string) {
	for _, conn := range clients {
		_, err := conn.Write([]byte(msg))
		if err != nil {
			logger.Fatalln(err.Error())
		}
	}
}

func main() {
	logfile, err := os.OpenFile("chatroom.log", os.O_CREATE|os.O_WRONLY, 0)
	if err != nil {
		panic(err)
		return
	}
	logger = log.New(logfile, "\r\n", log.Ldate|log.Ltime|log.Llongfile)

	listener,err := net.Listen("tcp4", "127.0.0.1:54321")
	if err != nil {
		return
	}
	clients = make(map[string]net.Conn, 100)
	msgQueue = make(chan MsgWapper, 200)
	go HandleMsg()
	for {
		oneClient, err := listener.Accept()
		if err != nil {
			continue
		}
		oneClient.Write([]byte(PrintAllClient()))
		clients[oneClient.RemoteAddr().String()] = oneClient
		BroadCast(oneClient.RemoteAddr().String() + "enter chat room")
		go handleOneClient(oneClient)
	}
}

client代码:

package main

import (
	"bufio"
	"log"
	"net"
	"os"
	"strings"
)

func handleInput(conn net.Conn) {
	defer conn.Close()
	for {
		//todo:在客户端输入的时候有个前缀提示,如(本机IP:端口 >>)
		r := bufio.NewReader(os.Stdin)
		input, _, err := r.ReadLine()
		if err != nil {
			log.Fatalln(err.Error())
			break
		}

		if strings.ToUpper(string(input)) == string("QUIT") {
			break
		}

		_, err = conn.Write([]byte(input))
		if err != nil {
			log.Fatalln(err.Error())
			break
		}
	}
}
func main() {
	conn, err := net.Dial("tcp4", "127.0.0.1:54321")
	if err != nil {
		return
	}
	defer conn.Close()

	//处理往服务器发送数据的功能
	go handleInput(conn)

	buf := make([]byte, 1024)
	for {
		nbytes, err := conn.Read(buf)
		if err != nil {
			return
		}
		log.Println("\n"+string(buf[:nbytes]))
	}
}

(全文完)