您当前的位置: 首页 > 业界动态 > > 内容页

GO实现Redis:GO实现TCP服务器(1)

来源:博客园 2023-03-23 19:48:26


(相关资料图)

本文实现一个Echo TCP Server

interface/tcp/Handler.go

type Handler interface {   Handle(ctx context.Context, conn net.Conn)   Close() error}
Handler:业务逻辑的处理接口Handle(ctx context.Context, conn net.Conn) 处理连接

tcp/server.go

type Config struct {    Address string}func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {    closeChan := make(chan struct{})    listen, err := net.Listen("tcp", cfg.Address)    if err != nil {       return err   }    logger.Info("start listen")    ListenAndServe(listen, handler, closeChan)    return nil}func ListenAndServe(listener net.Listener,                    handler tcp.Handler,                    closeChan <-chan struct{}) {    ctx := context.Background()    var waitDone sync.WaitGroup    for true {        conn, err := listener.Accept()        if err != nil {            break        }        logger.Info("accept link")        waitDone.Add(1)        go func() {            defer func() {                waitDone.Done()            }()            handler.Handler(ctx, conn)        }()    }    waitDone.Wait()}
Config:启动tcp服务器的配置Address:监听地址ListenAndServe:ctx是上下文,可以传递一些参数。死循环中接收到新连接时,让一个协程去处理连接如果listener.Accept()出错了就会break跳出来,这时候需要等待已经服务的客户端退出。使用WaitGroup等待客服端退出
func ListenAndServe(listener net.Listener,                    handler tcp.Handler,                    closeChan <-chan struct{}) {    go func() {       <-closeChan       logger.Info("shutting down...")       _ = listener.Close()       _ = handler.Close()   }()    defer func() {       _ = listener.Close()       _ = handler.Close()   }()    ......}

listener和handler在退出的时候需要关掉。如果用户直接kill掉了程序,我们也需要关掉listener和handler,这时候要使用closeChan,一旦接收到关闭信号,就执行关闭逻辑

func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {    closeChan := make(chan struct{})    sigCh := make(chan os.Signal)    signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)    go func() {       sig := <-sigCh       switch sig {          case syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:          closeChan <- struct{}{}      }   }()    listen, err := net.Listen("tcp", cfg.Address)    if err != nil {       return err   }    logger.Info("start listen")    ListenAndServe(listen, handler, closeChan)    return nil}

当系统对程序发送信号时,sigCh会接收到信号

tcp/echo.go

type EchoHandler struct {   activeConn sync.Map   closing    atomic.Boolean}

EchoHandler:

activeConn:记录连接closing:是否正在关闭,有并发竞争,使用atomic.Boolean
type EchoClient struct {   Conn    net.Conn   Waiting wait.Wait}func (c *EchoClient) Close() error {c.Waiting.WaitWithTimeout(10 * time.Second)_ = c.Conn.Close()return nil}

EchoClient:一个客户端就是一个连接。Close方法关闭客户端连接,超时时间设置为10s

func MakeHandler() *EchoHandler {return &EchoHandler{}}func (h *EchoHandler) Handle(ctx context.Context, conn net.Conn) {   // 连接正在关闭,不接收新连接   if h.closing.Get() {      _ = conn.Close()   }   client := &EchoClient{      Conn: conn,   }   h.activeConn.Store(client, struct{}{})   reader := bufio.NewReader(conn)   for {      msg, err := reader.ReadString("\n")      if err != nil {         if err == io.EOF {            logger.Info("connection close")            h.activeConn.Delete(client)         } else {            logger.Warn(err)         }         return      }      // 正在处理业务,不要关掉      client.Waiting.Add(1)      // 将数据原封不动写回去,测试      b := []byte(msg)      _, _ = conn.Write(b)      client.Waiting.Done()   }}func (h *EchoHandler) Close() error {   logger.Info("handler shutting down...")   h.closing.Set(true)   h.activeConn.Range(func(key interface{}, val interface{}) bool {      client := key.(*EchoClient)      _ = client.Close()      return true   })   return nil}
MakeEchoHandler:创建EchoHandlerHandle:处理客户端的连接。1.连接正在关闭时,不接收新连接2.存储新连接,value用空结构体3.使用缓存区接收用户发来的数据,使用\n作为结束的标志Close:将所有客户端连接关掉

main.go

const configFile string = "redis.conf"var defaultProperties = &config.ServerProperties{   Bind: "0.0.0.0",   Port: 6379,}func fileExists(filename string) bool {   info, err := os.Stat(filename)   return err == nil && !info.IsDir()}func main() {   logger.Setup(&logger.Settings{      Path:       "logs",      Name:       "godis",      Ext:        "log",      TimeFormat: "2022-02-02",   })   if fileExists(configFile) {      config.SetupConfig(configFile)   } else {      config.Properties = defaultProperties   }   err := tcp.ListenAndServeWithSignal(      &tcp.Config{         Address: fmt.Sprintf("%s:%d",            config.Properties.Bind,            config.Properties.Port),      },      EchoHandler.MakeHandler())   if err != nil {      logger.Error(err)   }}
测试
上一篇 下一篇
x
推荐阅读 更多
GO实现Redis:GO实现TCP服务器(1)

本文实现一个EchoTCPServerinterface tcp Handler gotypeHandlerinterface{Handle(ctxcontext Context,

2023-03-23
关于清明的资料|环球报道

1、清明节,又称踏青节、行清节、三月节、祭祖节等,节期在仲春与暮春之交。清明节源自上古时代的祖先信仰与春祭礼俗,兼具自然与人文两大内涵

2023-03-23
只有看看的份儿 2023 HONDA Hness CB350/CB350RS印度发布

HONDACB350RS与HONDAH & 039;nessCB350在动力及配备上大致相同,2023年式也并无重大更动。CB350RS采两版本发售,分别为DLX以及DLXPRODUALTONE,起售

2023-03-23
英文成语400句与典故_关于英文成语400句与典故的简介 快讯

1、《英文成语400句与典故》是2009年11月1日中国纺织出版社出版的图书,作者是弘恢。本书主要讲述了英文成语40

2023-03-23
外交部:美国硅谷银行事件已引发全球金融市场动荡 希望美方增强透明度

证券时报网讯,据央视新闻,3月23日,外交部发言人汪文斌主持例行记者会。有记者提问:近期,美国硅谷银行倒闭,成为自2008年金融危机以来美国

2023-03-23
即时焦点:永嘉县乌牛交通枢纽互通及连接线工程 关于建设范围坟墓迁移的公告

因永嘉县乌牛交通枢纽互通及连接线工程建设需要,凡坐落在本工程建设范围内的坟墓需要迁移。现将有关事项公告如下:一、迁移范围:乌牛街道吴岙

2023-03-23
当前热讯:郑州市召开落实食品安全“两个责任”制止餐饮浪费暨“守查保”专项行动推进会

3月21日下午,郑州市政府食品安全办、市场监管局召开落实食品安全工作专项行动推进会,对全市落实食品安全“两个责任”、制止餐饮浪费和“守查

2023-03-23
全球播报:东田微:截止至2023年3月20日,公司股东人数为11365人

东田微(301183)03月23日在投资者关系平台上答复了投资者关心的问题。

2023-03-23
全球快播:联通自由组合套餐可以随时变更吗

不可以。如果需要更改是可以更改的,但是不是随时变更,一个月只能变更一次。更改方法如下:1、登陆联通网上营业厅,点击页面左上角的“登陆”

2023-03-23
菲利华:3月22日融资买入667.41万元,融资融券余额1.21亿元

3月22日,菲利华(300395)融资买入667 41万元,融资偿还632 69万元,融资净买入34 72万元,融资余额1 02亿元,近20个交易日中有11个交易日出

2023-03-23