go语言聊天室实现(六)创建HTTP连接,并升级为长连接

Python013

go语言聊天室实现(六)创建HTTP连接,并升级为长连接,第1张

我们在mian函数中,首先初始化配置文件,然后新建http连接。

这个连接创建之后,监听服务器的9999端口。如果url的路径后缀为 "/ws",就转发到ws/ws.go中的IndexHandler方法中。

这个方法中首先我们创建一个websocket的Upgrader实例,然后我们使用Upgrader的upgrade方法来升级一下我们的连接为长连接。

升级完成之后会返回一个*websocket.Conn的连接,我们之后所有的关于连接的操作,都是基于该conn的。

在该连接完成之后,我们将连接存放到一个名为Client的map中,以便之后管理更为方便。

之后,我们启动一个goroutine来读取连接中发送的信息内容,再根据内容进行相应的操作。

我们可以看到 gorilla/websocket中的examples中有一个聊天室的demo。

我们进入该项目可以看到里面有这样的一些内容

按照官方的运行方式来运行这个项目

在浏览器中打开8080端口,可以看到该项目可以被成功运行了。

就是这样一个简单的demo。

然后我们去看一下它的具体实现。

在这个项目中首先定义了一个hub的结构体:

这个结构体中,clients代表所有已经注册的用户,broadcast管道会存储客户端发送来的信息。 register是一个*Client类型的管道,用于存储新注册的用户,unregister管道反之。

我们打开main.go,main函数的源码为:

在这里首先会新开一个goroutine,去跑hub的run方法,run方法中一个死循环,不停地去轮询hub中的内容

如果取到了新用户,就加入到clients中,如果取到了信息,就循环所有的client,将信息写到client.send中。

我们看到在请求路径为根的时候,它会请求一个函数,而这个函数就是将home.html发送到客户端。

而在请求路径为“/ws”的时候,他会执行一个serveWS的函数。

每当一个新的用户进来之后,首先将连接升级为长连接,然后将当前的client写到register中,由hub.run函数去做处理。然后开启两个goroutine,一个去读client中发送来的数据,一个将数据写入到所有的client中,去发送给用户。

这就是整个聊天室的实现原理。

为每个client fd开两个goroutine,一个recv,一个send。同时还有加2个channel,一个用于recv routine向逻辑主线程传送收到的数据,一个用于逻辑主线程向send goroutine传送待发送的数据,是这样的么?

实际上需要 3 个 goroutine,一个 read,一个 send,还有一个 handle。

read goroutine 读,然后写入 recevice chan。

write goroutine 把 send chan 的东西写。

handle goroutine 是 conn 的主要处理逻辑,负责把 recevice chan 的东西读出来 call 业务逻辑。

业务逻辑中要写数据就直接写入 send chan。

这样就可以保证,业务逻辑的读写都是在 handle goroutine 上处理,而避免 race 产生。

如果需要定时任务(比如心跳),就在 handle goroutine 上加上一个 timer.C;

如果需要 goroutine 下发任务,在 handle goroutine 增加一个 task chan,hanlde 收到 task 后处理业务;

如果需要输出结果,那就增加 result chan,业务逻辑把数据输出即可。

----------------------------

还有,如果我开2个goroutine的话,client断开连接了,假设recv goroutine先发生err并且close(fd),那在send goroutine中该如何处理呢?有可能不应该这样处理,那应该怎么处理呢?

如果 net.Conn Close() 了,不论 Read() 阻塞还是 Write() 阻塞都会立即收到 err 返回。

一般来说,Write() 是不可能主动知道连接断开的,除非是 SetDeadline() 猜测对方断掉了,指定时间内没有写成功就认为是断开。Read() 是可以主动收到对方发来的断开(TCP FIN),但也没办法知道异常的断开(当然也可以设置超时)。

无论是谁,是确实收到 FIN 还是 Deadline 猜测断开,只要 Close() 大家就知道连接断开了。

handle goroutine 还有一个用处就是:你的程序主动结束的时候,能正确的 close conn,让对方知道你是真的断开了,而不用去猜。