利用go并发与网络通信编写聊天室服务端
图片引用第三方
上代码:
package main
import (
"fmt"
"net"
"strings"
)
//注意结构体定义是type,不是var
type Client struct {
C chan string
Name string
Addr string
}
//用户在线列表 key表示ip和端口号,value表示Client结构体
var onlineClient map[string]Client
//全局channel,用于通信
var message = make(chan string)
//用于监听用户自带channel中是否有信息
func WriteMsgToClient(client Client, conn net.Conn) {
//由于client中的channel是无缓冲的,当channel中无数据时,channel会阻塞,相当于死循环一直在监听
for msg := range client.C {
//客户端接收该信息写到聊天面板中
conn.Write([]byte(msg + "\n"))
}
}
//用于对获取到的数据加以处理
func makeMsg(client Client, msg string) (buf string) {
buf = "[" + client.Addr + "] " + client.Name + ":" + msg
return
}
//处理客户端请求
func HandleClient(conn net.Conn) {
defer conn.Close()
//获取用户的IP地址和端口号转换成string类型
addr := conn.RemoteAddr().String()
fmt.Println(addr + "用户登录成功")
//创建一个用户,初始姓名都是用户的IP地址和端口号
client := Client{
C: make(chan string),
Name: addr,
Addr: addr,
}
//将新增的用户加入到在线用户列表中
onlineClient[addr] = client
//创建一个go程给自己发送短信,显示在聊天面板中
go WriteMsgToClient(client, conn)
//将用户登录信息加载到全局channel中
message <- makeMsg(client, " login...")
//创建匿名go程来持续监听并获取用户发送的信息
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
//读取用户发送的信息
if n == 0 {
//如果n为0,表示用户没有输入,则退出在线
fmt.Println("检测到用户退出登录")
return
}
errFunc("Conn Read:", err)
//减一是因为读取到的字符串中最后会有一个”\n“,切记,Windows系统为”\r\n“
msg := string(buf[:n-1])
if msg == "who" && len(msg) == 3 {
conn.Write([]byte("online user list:"))
for _,i := range onlineClient {
str := "["+i.Addr+"]"+i.Name+"\n"
conn.Write([]byte(str))
}
}else if len(msg)>8 && msg[:7] == "rename|" {
str := strings.Split(msg, "|")[1]
client.Name = str
s := string([]byte(client.Addr + "改名成功: " + str))
message <- s
}else if len(msg) == 4 && msg == "exit" {
delete(onlineClient,client.Addr)
message <- makeMsg(client,"logout")
}else{
message <- makeMsg(client, msg)
}
}
}()
//保持线程不会结束
for {
;
//select 用于监听通道的数据流通,可以用于超时强踢
}
}
//创建管理者线程,用于将写入到全局channel中的信息广播给每一个用户私人channel中
func manager() {
//初始化用户在线列表
onlineClient = make(map[string]Client)
//循环监听全局channel中是否有数据产生
for {
msg := <-message
//将产生的数据传输给每一个用户
for _, client := range onlineClient {
client.C <- msg
}
}
}
//用来处理err错误,简化代码量
func errFunc(str string, err error) {
if err != nil {
fmt.Println(str, err)
return
}
}
//主函数,用来模拟服务器
func main() {
//创建监听套接字(主go程)
//参数一:指定协议,参数二:指定IP地址以及端口号
listen, err := net.Listen("tcp", "127.0.0.1:8001")
errFunc("监听套接字连接失败", err)
defer listen.Close()
fmt.Println("服务器打开成功,等待客户端连接请求!")
//启动管理者进程
go manager()
for {
//循环监听客户端请求
conn, err := listen.Accept()
errFunc("客户端请求连接失败", err)
//defer conn.Close() 此处细节:defer不放在for循环中,而是放到go程中,以免发生不必要的错误
//启动go程,处理客户端请求
go HandleClient(conn)
}
}
可通过安装Nacap验证,打开终端,输入指定的IP和port
打开多个终端,模拟多用户在线
即可同步看到消息。
该代码包括的功能点:
- 用户上线消息广播
- 输入who查看用户在线列表
- 输入rename| 更改用户名
- 输入exit退出客户端
更多功能只需增加相应代码即可