本篇是上一篇的 看点2
内容。本该放上篇,但是由于上篇内容已经不少,所以单独拆开。另外还是主动说明下:我使用quic-go版本是github.com/quic-go/quic-go v0.47.0
,
虽然我在上篇截图过程中就尽量带上了quic-go版本信息,但在此还是再次说明。
本篇分析代码如下:
go
for {
conn, err := listener.Accept(context.Background())
// ... use conn...
}
看下面官方源码和注释:Accept返回新的连接,本方法应该在循环中调用。
都给出使用说明了:
go
// Accept returns new connections. It should be called in a loop.
func (l *Listener) Accept(ctx context.Context) (Connection, error) {
return l.baseServer.Accept(ctx)
}
// Accept returns connections that already completed the handshake.
// It is only valid if acceptEarlyConns is false.
// 返回已完成握手的连接
// 只有当 acceptEarlyConns标识(上篇有说明)为false的时候,返回的连接才是有效的
func (s *baseServer) Accept(ctx context.Context) (Connection, error) {
return s.accept(ctx)
}
func (s *baseServer) accept(ctx context.Context) (quicConn, error) {
select {
case <-ctx.Done():
return nil, ctx.Err()
case conn := <-s.connQueue: // 从这个chan中读取连接
return conn, nil
case <-s.errorChan:
return nil, s.closeErr
}
}
在上篇我们知道connQueue是一个长度为32的quicConn chan,
那么问题来了,谁往baseServer的connQueue扔连接呢?
server.go第555行:
go
func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error
方法内的723行:
这个方法内容很多,回头我们分析,先看看是谁调用了它?
server.go第377行:
go
func (s *baseServer) handlePacketImpl(p receivedPacket) bool /* is the buffer still in use? */
方法内的第459行:
这个handlePacketImpl方法内容同样不少,那么是谁调用了它呢?
闭环了朋友们,在上篇提到了上图第299行代码很重要,但是没有解释为什么,现在~
在继续分析之前,我们先来一张粗略图:
流程清楚了后:知道了如何接收到数据包后,接下来就好说了,去看看
func (s *baseServer) handlePacketImpl(p receivedPacket) bool
里的逻辑:
客户端请求下,代码如下:
go
package main
import (
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strconv"
"time"
"github.com/quic-go/quic-go"
)
const addr = "127.0.0.1:9000"
func main() {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"HLD"},
}
conn, err := quic.DialAddr(context.Background(), addr, tlsConf, nil)
if err != nil {
log.Fatalf("Error dialing address: %v", err)
}
defer conn.CloseWithError(0, "")
stream, err := conn.OpenStreamSync(context.Background())
if err != nil {
log.Fatalf("Error opening stream: %v", err)
}
defer stream.Close()
// 发送数据
var sendFlag = 0
go func() {
for {
// 发送业务类型
bizType := []byte{0x01} // 文本
err = sendData(stream, bizType)
if err != nil {
fmt.Errorf("error writing text biz type to stream: %v", err)
break
}
sendFlag++
sendBuffer := make([]byte, 1024)
numberStr := "HLD_" + strconv.Itoa(sendFlag)
copy(sendBuffer, numberStr)
log.Printf("Send: %v", string(sendBuffer))
time.Sleep(2 * time.Second)
err = sendData(stream, sendBuffer[:len(numberStr)])
if err != nil {
fmt.Errorf("Error writing to stream: %v", err)
break
}
// 为了方便调试,间隔设置得很大!
time.Sleep(10000 * time.Second)
}
}()
// 接收数据
go func() {
for {
recvBuffer := make([]byte, 1024)
recvBuffer, err = receiveData(stream, len(recvBuffer))
if err != nil {
fmt.Errorf("Error reading from stream: %v", err)
break
}
log.Printf("Recv: %v", string(recvBuffer))
}
}()
select {}
}
func sendData(stream quic.Stream, data []byte) error {
_, err := stream.Write(data)
if err != nil {
return fmt.Errorf("error writing to stream: %v", err)
}
return nil
}
func receiveData(stream quic.Stream, expectedLen int) ([]byte, error) {
readBuf := make([]byte, expectedLen)
n, err := stream.Read(readBuf)
if err != nil {
return nil, fmt.Errorf("error reading from stream: %v", err)
}
return readBuf[:n], nil
}
先debug启动server(在本篇最后附上完整server代码)
,再运行client:按照上面分析,在func (t *Transport) listen(conn rawConn)
方法内:读取Packet后,再t.handlePacket(p)
,然后程序往下走到如下所示代码行:
往下:
早有"人"在等着呢!从chan中读取数据:
下面我们看看func (s *baseServer) handlePacketImpl(p receivedPacket) bool
go
// 。。。 省略。。。
v, err := wire.ParseVersion(p.data) // Version1 Version = 0x1
// quic version校验。。。
// 判断是否是 0-RTT 包。。额外特殊分支处理。。。咱们这个不是
hdr, _, _, err := wire.ParsePacket(p.data) // 解析包...
go
s.handleInitialImpl(p, hdr) // 进入看看
上面来回是一个循环,一直重复重复,然后你在客户端控制台可能会发现报错了:timeout: no recent network activity
所以我们不再一行一行debug了,直接按照咱们分析,在func (s *baseServer) handleInitialImpl(p receivedPacket, hdr *wire.Header) error
方法内的第718行打断点,如下所示:
因为我们没有在其它地方打断点,所以放行后,发现有结果输出:
经过30秒后(MaxIdleTimeout默认值),发生timeout: no recent network activity
interface.go中第288行:
go
// MaxIdleTimeout is the maximum duration that may pass without any incoming network activity.
// The actual value for the idle timeout is the minimum of this value and the peer's.
// This value only applies after the handshake has completed.
// If the timeout is exceeded, the connection is closed.
// If this value is zero, the timeout is set to 30 seconds.
MaxIdleTimeout time.Duration
至此,咱们简略分析到了case s.connQueue <- conn:
那么谁去从connQueue 这个chan中读取conn呢?查找后发现在下面:
这不就是咱们server代码中的Accept嘛:
所以你看流程是不是通了。。。有兴趣的朋友可以打断点试试,我刚验证是这样的。
总结:本篇简单分析了获取conn连接的过程,transport 和 server之间的交互。
server端完整代码如下
:
go
package main
import (
"bufio"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/binary"
"encoding/pem"
"fmt"
"io"
"log"
"math/big"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/quic-go/quic-go"
)
const addr = "127.0.0.1:9000"
func main() {
quicConf := &quic.Config{
InitialStreamReceiveWindow: 1 << 20, // 1 MB
MaxStreamReceiveWindow: 6 << 20, // 6 MB
InitialConnectionReceiveWindow: 2 << 20, // 2 MB
MaxConnectionReceiveWindow: 12 << 20, // 12 MB
}
listener, err := quic.ListenAddr(addr, generateTLSConfig(), quicConf)
if err != nil {
log.Fatalf("Error listening on address: %v", err)
}
defer listener.Close()
for {
conn, err := listener.Accept(context.Background())
if err != nil {
log.Printf("Error accepting connection: %v", err)
continue
}
go handleConnection(conn)
fmt.Println("New client connected")
}
}
func handleConnection(conn quic.Connection) {
for {
// 接收数据流
stream, err := conn.AcceptStream(context.Background())
if err != nil {
log.Printf("Error accepting stream: %v", err)
return
}
fmt.Printf("New stream opened:%+v\n", stream.StreamID())
remoteAddr := conn.RemoteAddr().String()
fmt.Printf("Client connected from: %s\n", remoteAddr)
go func() {
defer stream.Close()
defer fmt.Println("video recv ok, ready to close stream for video...")
for {
// 读取业务类型
bizType := make([]byte, 1)
_, err = stream.Read(bizType)
if err != nil {
log.Printf("Error reading from stream: %v", err)
return
}
log.Printf("Recv: biz type %v\n", bizType)
if bizType[0] == byte(0x05) { // 视频
handleVideo(stream)
break
} else {
// 文本
data := make([]byte, 1024)
nr, err := stream.Read(data)
if err != nil {
log.Printf("Error reading from stream: %v", err)
return
}
log.Printf("Recv: %v\n", string(data))
time.Sleep(2 * time.Second)
nw, err := stream.Write(data[:nr])
if err != nil {
log.Printf("Error writing to stream: %v", err)
return
}
log.Printf("Send: %v, size: %d\n", string(data[:nr]), nw)
}
}
}()
}
}
func handleVideo(stream quic.Stream) {
//defer stream.Close()
var err error
// 读取文件信息
headLenBuf := make([]byte, 4)
n, err := stream.Read(headLenBuf)
if err != nil || n != 4 {
fmt.Println("video Error reading:", err.Error())
return
}
nn := binary.BigEndian.Uint32(headLenBuf)
bufferFileName := make([]byte, nn)
n, err = io.ReadFull(stream, bufferFileName)
if err != nil {
fmt.Println("video Error reading:", err.Error())
return
}
fileInfo := strings.Split(string(bufferFileName[:n]), "|")
fileName, fileSizeStr := fileInfo[0], fileInfo[1]
fileSize, _ := strconv.ParseInt(fileSizeStr, 10, 64)
// 创建文件
newFile, err := os.Create(filepath.Join("./", "received_"+fileName))
if err != nil {
fmt.Println("video Error creating file:", err)
return
}
writer := bufio.NewWriter(newFile)
defer writer.Flush()
defer newFile.Close()
if fileSize == 0 {
fmt.Println("video read file meta info err...")
return
}
var receivedBytes int64 = 0
var bufSize int64 = 1024 * 8
for receivedBytes < fileSize {
_, err := io.CopyN(writer, stream, bufSize)
if err != nil {
if err != io.EOF {
fmt.Println("Error copying:", err)
}
break
}
receivedBytes += bufSize
}
fmt.Println("File received successfully", fileName)
}
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{"HLD"},
}
}