github.com/rolandhe/saber@v0.0.4/nfour/duplex/srv.go (about) 1 // net framework basing tcp, tcp is 4th layer of osi net model 2 // Copyright 2023 The saber Authors. All rights reserved. 3 4 // Package duplex 多路复用模式的4层tcp通信,同时支持服务端和客户端。多路复用可以使用一条tcp连接并发响应多个请求,极少的使用资源。类似于http2. 5 // 利用多路复用模式可以利用极少资源实现高性能的网络通信功能。 6 package duplex 7 8 import ( 9 "github.com/rolandhe/saber/nfour" 10 "github.com/rolandhe/saber/utils/bytutil" 11 "net" 12 "strconv" 13 "time" 14 ) 15 16 const seqIdHeaderLength = 8 17 18 // Startup 启动一个多路复用的服务端,在多路复用模式下,每个连接由两个goroutine服务,一个负责读取请求,另一个负责写出响应,但一个读取goroutine可以持续的从连接中读取请求, 19 // 而没有必要等待上一个请求完成,多个请求可以并发的被执行,最终这些结果被负责写的goroutine写出。 20 // conf.concurrent 指定了最大并发数 21 func Startup(port int, conf *nfour.SrvConf) { 22 ln, err := net.Listen("tcp", ":"+strconv.Itoa(port)) 23 if err != nil { 24 // handle error 25 nfour.NFourLogger.InfoLn(err) 26 return 27 } 28 nfour.NFourLogger.Info("listen tcp port %d,and next to accept\n", port) 29 for { 30 conn, err := ln.Accept() 31 if err != nil { 32 // handle error 33 nfour.NFourLogger.InfoLn(err) 34 return 35 } 36 handleConnection(conn, conf.GetConcurrent().TotalTokens(), conf) 37 } 38 } 39 40 func handleConnection(conn net.Conn, limitPerConn uint, conf *nfour.SrvConf) { 41 writeCh := make(chan *result, limitPerConn) 42 closeCh := make(chan struct{}) 43 go readConn(conn, writeCh, closeCh, conf) 44 go writeConn(conn, writeCh, closeCh, conf) 45 } 46 47 func readConn(conn net.Conn, writeCh chan *result, closeCh chan struct{}, conf *nfour.SrvConf) { 48 nfour.NFourLogger.DebugLn("start to read header info...") 49 fullHeaderLength := seqIdHeaderLength + nfour.PayLoadLenBufLength 50 header := make([]byte, fullHeaderLength) 51 for { 52 conn.SetReadDeadline(time.Now().Add(conf.IdleTimeout)) 53 err := nfour.InternalReadPayload(conn, header, fullHeaderLength, true) 54 if err != nil { 55 nfour.NFourLogger.InfoLn("read header error") 56 close(closeCh) 57 close(writeCh) 58 break 59 } 60 l, _ := bytutil.ToInt32(header[:nfour.PayLoadLenBufLength]) 61 bodyBuff := make([]byte, l, l) 62 conn.SetReadDeadline(time.Now().Add(conf.ReadTimeout)) 63 err = nfour.InternalReadPayload(conn, bodyBuff, int(l), false) 64 if err != nil { 65 close(closeCh) 66 close(writeCh) 67 nfour.NFourLogger.Info("read payload error,need %d bytes\n", l) 68 break 69 } 70 seqId, _ := bytutil.ToUint64(header[nfour.PayLoadLenBufLength:]) 71 if !conf.GetConcurrent().AcquireTimeout(conf.SemaWaitTime) { 72 writeCh <- &result{true, seqId, conf.ErrHandle(nfour.ExceedConcurrentError)} 73 continue 74 } 75 go doBiz(bodyBuff, writeCh, conf, seqId) 76 } 77 } 78 79 func doBiz(bodyBuff []byte, writeCh chan *result, conf *nfour.SrvConf, seqId uint64) { 80 task := &nfour.Task{PayLoad: bodyBuff} 81 resBody, err := conf.Working(task) 82 83 if err != nil { 84 resBody = conf.ErrHandle(err) 85 } 86 writeCh <- &result{false, seqId, resBody} 87 } 88 89 // readConn 感知是否需要关闭连接后,通过closeCh来通知,writeConn得到消息后最终关闭连接 90 // writeConn识别到网络失败,关闭连接后,等待readConn感知到后再通知writeConn是否资源 91 func writeConn(conn net.Conn, writeCh chan *result, closeCh chan struct{}, conf *nfour.SrvConf) { 92 writeCloseConn := false 93 for { 94 if isClose(closeCh) { 95 if !writeCloseConn { 96 conn.Close() 97 } 98 return 99 } 100 select { 101 case res := <-writeCh: 102 if !writeCore(res.ret, res.seqId, conn, conf.WriteTimeout) { 103 writeCloseConn = true 104 continue 105 } 106 // quickFailed=true代表没有执行也操作,直接返回超出并发错误,因此不需要释放信号量 107 if !res.quickFailed { 108 conf.GetConcurrent().Release() 109 } 110 case <-closeCh: 111 if !writeCloseConn { 112 conn.Close() 113 } 114 return 115 } 116 } 117 } 118 119 func writeCore(res []byte, seqId uint64, conn net.Conn, timeout time.Duration) bool { 120 conn.SetWriteDeadline(time.Now().Add(timeout)) 121 122 fullHeaderLength := nfour.PayLoadLenBufLength + seqIdHeaderLength 123 124 plen := len(res) 125 allSize := plen + fullHeaderLength 126 payload := make([]byte, allSize) 127 copy(payload, bytutil.Int32ToBytes(int32(plen))) 128 copy(payload[nfour.PayLoadLenBufLength:], bytutil.Uint64ToBytes(seqId)) 129 copy(payload[fullHeaderLength:], res) 130 131 l := 0 132 for { 133 n, err := conn.Write(payload) 134 if err != nil { 135 conn.Close() 136 nfour.NFourLogger.InfoLn(err, "write core failed") 137 return false 138 } 139 l += n 140 if l == allSize { 141 break 142 } 143 payload = payload[l:] 144 } 145 146 nfour.NFourLogger.Debug("write data:%d, expect:%d\n", l, allSize) 147 return true 148 } 149 150 func isClose(closeCh chan struct{}) bool { 151 select { 152 case <-closeCh: 153 return true 154 default: 155 return false 156 } 157 } 158 159 type result struct { 160 quickFailed bool 161 seqId uint64 162 ret []byte 163 }