一、前置知识
在开始之前,我们需要掌握一些基本知识:
1.golang语言基础:变量、函数、结构体、接口等基本概念。
2.网络编程基础:tcp/udp、http等协议的基本知识。
3.goroutine:golang语言的协程,能够大大提高并发编程的效率。
4.channel:golang语言提供的一种通信机制,可用于不同协程之间的数据传输和同步。
5.select:golang语言提供的一种多路复用机制,可监听多个channel的状态,提高程序的效率。
二、框架架构
根据workerman框架的实现方式,我们可以将其划分为三个部分:
1.接收连接并生成客户端。
2.用于处理客户端请求的业务进程。
3.监听客户端连接状态,并进行回收。
在golang语言中,我们可以分别使用goroutine实现以上三个部分。
1.接收连接并生成客户端
我们可以使用golang语言自带的net包创建一个tcp服务器,同时开启一个goroutine用于监听客户端连接状态。
import ( fmt net)func main() { listener, err := net.listen(tcp, 127.0.0.1:8080) if err != nil { fmt.println(failed to listen:, err) return } go func() { for { conn, err := listener.accept() if err != nil { fmt.println(failed to accept:, err) continue } // 生成客户端 } }() // 等待进程退出 select {}}
在接收到客户端连接之后,我们需要封装出一个client对象,用于处理该连接的所有请求和响应。
type client struct { conn net.conn respch chan []byte}func newclient(conn net.conn) *client { return &client { conn: conn, respch: make(chan []byte, 10), }}
2.用于处理客户端请求的业务进程
客户端的请求和响应直接是通过channel进行传递的。当收到一个新连接时,我们需要将其封装成一个client对象,并开启一个goroutine用于处理该连接。该goroutine将通过channel监听客户端发送的所有请求,并做出相应的响应。
我们将业务进程封装成一个handler接口。
type handler interface { onconnect(*client) error onmessage(*client, []byte) error onclose(*client) error}
客户端的请求和响应都是通过client对象的respch属性进行传递的。因此,在handler接口中,我们需要定义一个respch属性,用于接收来自客户端的响应。
type handler interface { onconnect(*client) error onmessage(*client, []byte) error onclose(*client) error respch() chan []byte}
我们可以创建一个echohandler来实现handler接口。
type echohandler struct { clients []*client respchan chan []byte}func newechohandler() *echohandler { return &echohandler{ clients: make([]*client, 0), respchan: make(chan []byte, 10), }}func (h *echohandler) onconnect(c *client) error { h.clients = append(h.clients, c) return nil}func (h *echohandler) onmessage(c *client, data []byte) error { // 将客户端发送的数据广播给所有其他客户端,并将其存入respchan中 for _, client := range h.clients { if client == c { continue } client.respch <- data } return nil}func (h *echohandler) onclose(c *client) error { for index, client := range h.clients { if client == c { h.clients = append(h.clients[:index], h.clients[index+1:]...) } } return nil}func (h *echohandler) respch() chan []byte { return h.respchan}
当 clients 数组中存储了每个连接客户端的client对象之后,我们就可以通过respch属性来接收到每个客户端发送过来的数据,实现将客户端发送的信息广播给其他客户端的功能。
3.监听客户端连接状态,并进行回收
对于旧版本的workerman框架来说,workerman会在一定时间内对空闲连接进行回收。而新版本的workerman则通过tcp保活来实现该功能。
在实现golang版本的workerman时,我们也可以通过tcp保活来解决空闲连接问题。我们可以在每个客户端的goroutine中,监听其socket的状态,若某个客户端在10秒空闲时间后未发送数据,则将其视为非法连接,并关闭其socket。
func (c *client) process() { defer func() { c.conn.close() c.handler.onclose(c) }() // 设置 socket keepalive tcpconn, ok := c.conn.(*net.tcpconn) if ok { tcpconn.setkeepalive(true) tcpconn.setkeepaliveperiod(10 * time.second) } // 进入读协程,接收客户端发送的所有数据 go func() { for { buf := make([]byte, 1024) n, err := c.conn.read(buf) if err != nil { if err != io.eof { fmt.println("failed to read:", err) } break } // 将客户端发送的消息交给handler处理 c.handler.onmessage(c, buf[:n]) } }() // 进入写协程,将respchan中的所有响应发送给当前客户端 go func() { for resp := range c.handler.respch() { _, err := c.conn.write(resp) if err != nil { fmt.println("failed to write:", err) break } } }() // onconnect err := c.handler.onconnect(c) if err != nil { fmt.println("failed to on connect:", err) return } // 在worker进程退出时进行清理 select {}}
三、实现worker进程
在完成以上三个步骤之后,我们需要创建一个worker进程来管理所有的客户端连接。worker进程中需要加载一个或多个handler来处理所有客户端发送过来的数据请求。
type worker struct { listener net.listener handlers map[string]handler}func newworker(addr string) (*worker, error) { listener, err := net.listen("tcp", addr) if err != nil { fmt.println("failed to listen:", err) return nil, err } return &worker{ listener: listener, handlers: make(map[string]handler), }, nil}func (w *worker) register(name string, handler handler) { w.handlers[name] = handler}func (w *worker) start() { go func() { for { conn, err := w.listener.accept() if err != nil { fmt.println("failed to accept:", err) continue } // 封装连接客户端为client对象,用于后续的处理 client := newclient(conn) client.handler = w.handlers["echo"] // 开启客户端goroutine来处理该连接 go client.process() } }() // 等待进程退出 select {}}
在worker进程中,我们需要定义一个handlers属性,用于存储不同handler的实例,并在start()函数中监听客户端连接并开启新的goroutine去处理客户端的请求。
四、测试
我们可以通过以下代码来创建一个worker进程,并在其中注册一个echohandler来处理所有客户端的请求。
func main() { server, _ := newworker("127.0.0.1:8080") handler := newechohandler() server.register("echo", handler) server.start()}
我们可以使用telnet工具来模拟多个客户端向服务器发送消息,并查看其接收情况。
我们使用以下命令,连接到服务器:
telnet 127.0.0.1 8080
我们可以在telnet中输入以下文本:
hello workerman!
我们可以同时开启多个telnet窗口,以模拟多客户端并行请求。
在服务器上,我们可以看到输出:
$ go run worker.go 服务器已启动...failed to read: read tcp 127.0.0.1:8080->127.0.0.1:56182: use of closed network connection
这是因为我们在关闭客户端连接时,会取消其侦听操作,导致读取错误。
在telnet输入完成之后,我们可以看到,每个telnet窗口都会收到服务器返回的文本。
五、总结
本文中,我们介绍了如何使用golang语言实现一个高并发的网络编程框架,类似于php语言中的workerman。在实现过程中,我们使用了golang语言中的协程、通信机制以及多路复用机制,通过封装client对象和handler接口,成功地实现了类似于workerman的高并发网络编程框架。
实际上,在日常编程中,我们建议直接使用golang语言提供的net/http包来实现高并发的网络编程,相比workerman框架更加简洁,性能更优。我们只需要开启一个http服务器,再在其中使用goroutine并发处理每个请求,即可轻松实现高并发网络编程。
以上就是go语言怎么实现一个高并发的网络编程框架的详细内容。
