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  }