先在main方法中创建一个sorcket服务端,这个服务端就像一个酒店
func main() {
listen, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("listen err", err)
return
}
defer listen.Close()
}
之后就要开始监听下一个客户端的呼叫,也就是等待下一位客人
Accept方法会等待下一个呼叫,并返回一个该呼叫的Conn接口。这时便拿到了客人的信息
func main() {
// 创建服务端
listen, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("listen err", err)
return
}
defer listen.Close()
// 等待客户端呼叫
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept err", err)
return
}
}
}
创建客户端结构体, 每个服务端是一个用户
type Client struct{
c chan string // 消息接受管道
Name string // 昵称
Addr string // ip与端口
}
收到呼叫拿到conn后,就需要为这个conn来创建一个协程
也就是得到客人的信息后,专门派一个服务员只为该客户服务
func main() {
// 创建服务端
listen, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("listen err", err)
return
}
defer listen.Close()
go Manager() // 这个暂时忽略掉,下面有
// 等待客户端呼叫
for {
conn, err := listen.Accept()
if err != nil {
fmt.Println("accept err", err)
return
}
go HandleConnect(conn) // 这里加了一句代码,启动一个go协程来为conn服务
}
}
服务员要干的第一件是就是带客户到房间,也就是为这个conn创建一个Client,并将其放进在线用户Map,netAddr是网络地址,以网络地址为键,client为值
全局变量 在线用户map
var onlineUsersMap map[string]Client
到这里就出现问题了,那在线用户map在哪里初始化使用啊?
我们创建一个专门来管理在线用户与消息群发的管理方法,这里暂时先初始化在线用户
func Manager(){
// 初始化一下在线用户map
onlineUsersMap = make(map[string]Client)
}
初始化一定要在赋值前,因此Manager协程一定要在HandleConnect前启动,现在翻到上面的main方法中,看一下位置
func HandleConnect(conn net.Conn){
defer conn.Close()
netAddr := conn.RemoteAddr().String()
client := Client{make(chan string), netAddr, netAddr}
onlineUsersMap[netAddr] = client
for{} // 先整个空循环放这里,目的是别让这个go程结束断开conn连接
}
房间开好了,现在需要开始服务!
聊天室的服务是什么?当然是聊天啊!聊天室是什么,当然是群聊啊,每个用户的消息,所有用户都能看的到
说先我们的先保证用户能看的到其他人说话,我们在定义Client时,同时定义了一个消息接收通道,用户收到的消息会先发到这个消息接受通道里,然后从通道取出然后打印出来
func writeMsgToClient(client Client, conn net.Conn) {
for msg := range client.c {
conn.Write([]byte(msg))
}
}
在HandleConnect()
中为服务的用户开一个go协程
go writeMsgToClient(client, conn)
“听”有了,该“说”了
定义一个全局变量 消息通道 该通道内的消息会被发到聊天室内
var messageChan make(chan string)
我们为服务的用户开一个go协程来发送消息,就直接写在HandleConnect()
中了
我们的消息都先发送到全局messageChan通道中了
go func(){
buf := make([]byte, 4096)
for{
n, err := conn.Read(buf) // 从链接中读取消息,读到buf中,返回长度n
if n == 0{ // 如果消息长度为0,则用户已退出
fmt.Printf("用户[%s]:%s已退出\n", client.Addr, client.Name)
return
}
if err != nil {
fmt.Println("read err", err)
return
}
// string(buf[:n-1])中的n-1,主要是为了不读取末尾的换行符
msg := "[" + client.Addr + "]" + client.Name + ": " + string(buf[:n-1]) + "\n"
messageChan <- msg
}
}()
通过将 全局消息通道 中的消息发送到 每个用户的消息接收通道中 来实现消息群发
哪里有全部用户呢?当然是已经初始化onlineUsersMap
的Manager()
中了!
func Manager(){
// 初始化一下在线用户map
onlineUsersMap = make(map[string]Client)
// 消息群发
for{
// 如果全局消息通道中没有消息会阻塞,直到有消息
msg := <-messageChan
for _, client := range onlineUsersMap{
client.c <- msg
}
}
}
进入聊天室前时先说一句,我来了,要不然什么提示都没有,感觉跟还没进聊天室一样
完整的HandleConnect()
代码:
func HandleConnect(conn net.Conn){
defer conn.Close()
netAddr := conn.RemoteAddr().String()
onlineUsersMap[netAddr] = client
go writeMsgToClient(client, conn) // "听"消息
loginMsg := "[" + client.Addr + "]" + client.Name + " login!\n"
messageChan <- loginMsg
// 发消息
go func(){
buf := make([]byte, 4096)
for{
n, err := conn.Read(buf) // 从链接中读取消息,读到buf中,返回长度n
if n == 0{ // 如果消息长度为0,则用户已退出
fmt.Printf("用户[%s]:%s已退出\n", client.Addr, client.Name)
return
}
if err != nil {
fmt.Println("read err", err)
return
}
// string(buf[:n-1])中的n-1,主要是为了不读取末尾的换行符
msg := "[" + client.Addr + "]" + client.Name + ": " + string(buf[:n-1]) + "\n"
messageChan <- msg
}
}()
for{} // 先整个空循环放这里,目的是别让这个go程结束断开conn连接
}
至此,简单的聊天功能实现了!
那,要如何验证呢?
这里我们需要用到一个工具:“netcat”
下载地址:https://eternallybored.org/misc/netcat/
下载后解压出来,注意,这一步杀毒会报黑客工具,然后自动删掉解压的东西,建议先关闭杀毒软件,然后再解压,最后打开杀毒软件,此时大概率解压出来的东西已经又被杀毒软件删了,先将解压目录添加到杀毒白名单(信任区)中,再到隔离区将前两个文件恢复即可
最后将目录添加到系统的环境变量path中即可
打开两个cmd窗口
两个cmd窗口都输入
nc 127.0.0.1 8000
再按回车
之后便可以互相发消息测试了!
明天继续更新 改名,查询在线用户,私聊功能