GO语言网络编程demo-聊天室

GO语言网络编程demo-聊天室

代码展示:

package main
​
import (
    "fmt"
    "net"
    "strings"
    "time"
)
​
type Client struct {
    C    chan string
    Name string
    Addr string
}
​
var onlinemap map[string]Client
​
var meassage = make(chan string)
​
func main() {
    listenr, err := net.Listen("tcp", "127.0.0.1:8080")
    if err != nil {
       fmt.Println("err:=", err)
       return
    }
    defer listenr.Close()
    go Manager()
    for true {
       conn, err := listenr.Accept()
       if err != nil {
          fmt.Println("err:=", err)
          return
       }
       defer conn.Close()
       //处理用户连接
       go Handleconn(conn)
    }
}
​
// 处理用户连接
func Handleconn(conn net.Conn) {
    defer conn.Close()
    cliaddr := conn.RemoteAddr().String()
    cli := Client{make(chan string), cliaddr, cliaddr}
    onlinemap[cliaddr] = cli
    go WriteMagToClient(cli, conn)
    meassage <- MakeMsg(cli, "login")
    isQuit := make(chan bool)  //对方是否主动退出
    hasDate := make(chan bool) //对方是否有数据发送
​
    //新建一个协程,接收用户发来的数据
    go func() {
       buf := make([]byte, 2048)
       for {
​
          n, err := conn.Read(buf)
          if n == 0 {
             isQuit <- true
             fmt.Println("conn.read err=", err)
             return
          }
          msg := string(buf[:n-1])
          if len(msg) == 3 && msg == "who" {
             conn.Write([]byte("user list:\n"))
             for _, tmp := range onlinemap {
                msg = tmp.Addr + ":" + tmp.Name + "\n"
                conn.Write([]byte(msg))
​
             }
          } else if len(msg) >= 8 && msg[:6] == "rename" {
             name := strings.Split(msg, "|")[1]
             cli.Name = name
             onlinemap[cliaddr] = cli
             conn.Write([]byte("rename ok \n"))
          } else {
             meassage <- MakeMsg(cli, msg)
          }
          hasDate <- true
       }
    }()
​
    for {
       select {
       case <-isQuit:
          delete(onlinemap, cliaddr)
          meassage <- MakeMsg(cli, "login out")
          return
       case <-hasDate:
       case <-time.After(60 * time.Second):
          delete(onlinemap, cliaddr)
          meassage <- MakeMsg(cli, "time out")
          return
       }
​
    }
​
}
func MakeMsg(cli Client, login string) (buf string) {
    buf = "[" + cli.Addr + "]" + cli.Name + "  " + login
    return buf
​
}
​
func WriteMagToClient(cli Client, conn net.Conn) {
    for msg := range cli.C {
       conn.Write([]byte(msg + "\n"))
    }
​
}
func Manager() {
    onlinemap = make(map[string]Client)
    for {
       msg := <-meassage
       for _, cli := range onlinemap {
          cli.C <- msg
       }
​
    }
}

完成本demo需要的技术栈:

go语言基础(到多线程,通道即可)

网络编程

思路讲解部分:

本聊天室功能旨在实现多用户的聊天功能,需要下列功能:

1.一个用户发送消息所有用户都能接收

2.更改名字

3.输入:who,展示所有在线用户

4.长时间无操作自动退出

首先需要创建一个监听,使用TCP协议,使用本地地址,然后创建conn对象,用来向用户输出和读取信息。

对于功能实现:

1.一个用户发送消息所有用户都能接收

通过通道功能,不进入信息则阻塞,进入信息则向全体广播信息。

2.更改名字

对用户输入进行判断,符合设定的更改命名格式则允许更改结构体中的name,修改成功则给出提示。

3.输入:who,展示所有在线用户

遍历一遍结构体中的name,并输入给用户。

4.长时间无操作自动退出

使用time 包的函数,当时间到的时候若用户仍然无操作,则删除该用户信息,并返回

代码讲解部分:

我将仔细讲解每段代码的作用,作为网络编程和GO的复习

“不要使用共享内存来通信,而是要通过通信来共享内存”

本demo使用的所有包

第一个Client(用户端)结构体,他有三个参数,作用如上。

对于onlineMap,他是一个集合类型,索引就是用户的ID,索引对应的值就是Client类型

对于message,他是一个管道,用于通信

这些全局变量在后面的代码中用到的时候会进行详细说明

现在展示main()函数的所有内容:

现在逐层进行讲解

这段代码创建了一个监听器listener,通过调用net包中的Listen()函数,该函数有两个参数,

network,指的是网络协议类型,这里使用TCP协议进行通信。

address,指的是监听地址,这里的地址杀死本地ip地址的8080端口。

错误处理就不说了。

这里的Close()是关闭监听器,defer语句是在函数返回前被调用。

Managger()开启了一个协程,后续会展示代码

这段代码调用了一个listener.Accept()方法,这个方法会等待并接受一个传入的连接请求,如果成功接受到连接,就会

返回一个conn连接对象,这个对象的作用如下:

该对象可以与客户端实现双向的数据交互,可以使用Read()方法读取客户端发送的数据,并使用Write()方法向客户端发送响应数据。

conn的类型是 net.Conn类型。

总结:用于客户端与服务端进行交互等操作。

HanleConn开启了 一个协程,传入conn,该函数用于处理用户链接。

现讲解Manager()的代码:

首先你创建了一个之前说过的map,然后创建了一个msg,用来接收meassage通道的信息

使用cli来遍历map中的内容,并将msg传给cli.C

这两个使用通道的代码都会阻塞,所以这段代码没有接受信息之前是阻塞的,当有信息进入meassage通道的时候,才能将信息传到

map中的通道中。

现在这个代码是单功能HandleConn()函数,看懂这个之后我们会重新看完全版的HandleConn()

对于cliAddr := conn.RemoteAddr().String(),

conn.RemoteAddr() 返回一个 net.Addr 类型的对象,net.Addr 类型是一个接口类型,可以表示各种类型的网络地址。

他的String()方法,可以返回地址的字符串表示形式。

Network()方法,可以返回地址的网络类型,例如 “tcp”,“IP”

所以cliAddr是个字符串类型的网址地址。

然后创建一个cli结构体,把结构体放到map中。

之后创建一个协程,发送信息给客户端。

这个是把信息写给客户端,使用msg遍历cli.c通道

使用conn.Write()接收信息,并写入conn

现在展示完全版的HandleConn:

上面四行已经说过了,从第六行开始:

第六行的函数稍后讲解

第七八行创建了两个bool 类型的通道,用于之后的代码。

MakeMsg(),两个参数,第一个参数为Client结构体,,第二个参数为字符串

这个函数的作用是返回一个字符串,符合我所写的结构,即:

[地址]姓名login

login为变量,主要看输入的是什么。

启动了一个协程,用一个匿名函数来包装内部代码

函数中首先创建了一个缓冲区 buf,用于接收读取的数据。

在循环中,通过 conn.Read(buf) 从连接 conn 中读取数据,并返回读取的字节数 n 和可能的错误 err。如果读取的字节数为零,表示连接已关闭,此时触发 isQuit 通道的发送操作,并输出错误信息。然后函数返回。

如果读取到有效的数据,将其转换为字符串形式,存储在变量 msg 中。

接下来进行条件判断。如果 msg 的长度为 3 且等于 "who",则表示客户端请求用户列表。服务器将向客户端连接 conn 发送当前在线用户的列表。

如果 msg 的长度大于等于 8 且前六个字符为 "rename",则表示客户端请求修改自身的名称。服务器将从 msg 中提取新名称,并更新 cli.Name 以及 onlinemap 中对应客户端的信息。随后向客户端连接 conn 发送 "rename ok" 的响应。

如果以上条件均不满足,表示客户端发送的是聊天消息。使用 MakeMsg 函数生成包含客户端和消息的完整消息,并将其通过 meassage 通道发送出去。

这里就实现了修改姓名的功能与发送在线用户的功能。

在这段代码中,在处理客户端连接的循环中,使用了 select 语句来同时等待多个通道的操作。

select 语句用于监视通道的状态,当有多个通道都准备好进行操作时,select 会随机选择其中一个通道执行对应的操作。

在这个循环中,select 语句包含了三个 case 分支:

  1. <-isQuit:等待从 isQuit 通道接收数据。如果接收到数据,表示客户端主动退出。在这种情况下,会从 onlinemap 中删除对应的客户端信息,然后通过 meassage 通道向其他客户端发送退出消息,并返回函数。

    这里是用户的主动退出。

  2. <-hasDate:等待从 hasDate 通道接收数据。如果接收到数据,表示有新的数据可处理。这里的作用是当消息处理完后,继续等待下一个消息的到来。

  3. <-time.After(60 * time.Second):等待一个 60 秒的定时器。如果 60 秒内没有其他操作发生,表示客户端超时断开连接。在这种情况下,会从 onlinemap 中删除对应的客户端信息,然后通过 meassage 通道向其他客户端发送超时退出消息,并返回函数。

这里实现了超时自动退出的功能。

通过使用 select 语句和不同的通道操作,可以实现对不同事件的处理,包括客户端主动退出、接收到新的数据、以及客户端超时等情况的处理。

代码讲解完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值