前面我们实现了大部分聊天室的功能,但有个核心的功能没做好,那就是每个客户端无法显示当前在线的客户有哪些。现在就增加这个功能。
在客户端和服务端之间的网路协议增加一条:
当客户端和网络端用*隔开的时候,就表示客户端要向服务端发送命令,现在暂定命令是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]))
}
}
(全文完)