用go语言实现简单并发聊天室
1.可以支持多个客户端上线
2.服务器知道那个客户端上线
3.服务器可以将消息转发给所有的客户端
4.可以查看在线用户列表
5.可以实现用户重命名
并发聊天服务器原理分析
并发聊天服务器代码
package main
import (
"fmt"
"net"
"strings"
"time"
)
type Client struct {
C chan string //用户发送数据的管道
Name string //用户名
Addr string //网络地址
}
var onlineMap map[string]Client
var message = make(chan string)
func main(){
//监听
listener,err := net.Listen("tcp","127.0.0.1:8080")
if err != nil{
fmt.Println("net.Listen err:",err)
return
}
defer listener.Close()
//新开一个协程,用来转发消息,只要有消息,遍历map,给每个成员发送消息
go Manager()
//主协程,循环堵塞等待用户连接
for{
conn,err1 := listener.Accept()
if err1 != nil {
fmt.Println("listener Accept err:",err1)
continue
}
go HandleConn(conn) //处理用户连接
}
}
func MakeMsg(cli Client,msg string) (buf string) {
buf = "[" + cli.Addr + "]" + cli.Name + ":" + msg
return
}
func HandleConn(conn net.Conn){
defer conn.Close()
//获取客户端的网络地址
cliAddr := conn.RemoteAddr().String()
//创建一个结构体,默认用户名和网络地址一样
cli := Client{make(chan string),cliAddr,cliAddr}
//把结构体添加到map
onlineMap[cliAddr] = cli
//新开一个协程,专门给当前客户端发送信息
go WriteMsgToClient(cli,conn)
//广播某个在线
//message <- "[" + cli.Addr + "]" + cli.Name + ":login"
message <- MakeMsg(cli,"login")
//提示当前用户是谁
cli.C <- MakeMsg(cli,"I`m here")
isQuit := make(chan bool) //对方是否主动退出
hasDate := make(chan bool) //对方是否有数据发送
//新建一个协程,接受用户发送过来的数据
go func() {
buf := make([]byte,2048)
for{
n,err2 := conn.Read(buf)
if n == 0 { //对方断开或者出问题
isQuit <- true
fmt.Println("conn.Read err:",err2)
return
}
msg := string(buf[:n-1]) //通过windows nc测试,多一个换行
if len(msg) == 3 && msg == "who"{ //查看当前用户
//遍历map,给当前用户发送所有成员
conn.Write([]byte("user list:\n"))
for _,tmp := range onlineMap{
msg := tmp.Addr + ":" +tmp.Name + "\n"
conn.Write([]byte(msg))
}
}else if len(msg) >= 3 && msg[:6] == "rename"{ //更改用户名
name := strings.Split(msg,"|")[1]
cli.Name = name
onlineMap[cliAddr] = cli
conn.Write([]byte("rename ok\n"))
} else {
//转发此内容
message <- MakeMsg(cli,msg)
}
hasDate <- true //代表有数据
}
}()
for {
//通过select检测channel的流动
select {
case <- isQuit:
delete(onlineMap,cliAddr) //当前用户从map移除
message <- MakeMsg(cli,"login out\n") //广播谁下线了
return
case <- hasDate:
case <- time.After(60*time.Second): //60秒后超时
delete(onlineMap,cliAddr) //当前用户从map移除
message <- MakeMsg(cli,"time out leave out\n") //广播谁下线了
}
}
}
//新开一个协程,用来转发消息,只要有消息,遍历map,给每个成员发送消息
func Manager(){
//给map分配空间
onlineMap = make(map[string]Client)
for{
msg := <-message
for _,cli := range onlineMap{
cli.C <- msg
}
}
}
func WriteMsgToClient(cli Client,conn net.Conn){
for msg := range cli.C{ //给当前客户端发送信息
conn.Write([]byte(msg + "\n"))
}
}