当前位置: 首页 > article >正文

Golang使用Quic-Go开源库实现Quic客户端和服务端

Quic-Go介绍

Quic-Go是Go语言Quic协议(RFC 9000、RFC 9001、RFC 9002)的实现。它支持HTTP/3(RFC 9114),包括QPACK(RFC 9204)和HTTP数据报(RFC 9297)。

  • Github地址

https://github.com/quic-go/quic-go

  • 下载Quic-Go开源库
go get -u github.com/quic-go/quic-go
  • 下述代码中Go版本和Quic-Go版本
  1. go version go1.22.6 linux/amd64
  2. github.com/quic-go/quic-go v0.46.0

Quic客户端代码

package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"log"
	"strconv"
	"time"

	"github.com/quic-go/quic-go"
)

const addr = "192.168.8.48:19940"

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 int = 0
	go func() {
		for {
			sendFlag++
			sendBuffer := make([]byte, 1024)
			numberStr := "HLD_" + strconv.Itoa(sendFlag)
			copy(sendBuffer, numberStr)
			log.Printf("Send: %v", string(sendBuffer))

			err = sendData(stream, sendBuffer[:len(numberStr)])
			if err != nil {
				fmt.Errorf("Error writing to stream: %v", err)
				break
			}

			time.Sleep(1 * 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))
		}
	}()

	for {
		time.Sleep(10 * time.Second)
	}

	fmt.Println("Echo test successful")
}

func sendData(stream quic.Stream, data []byte) error {
	_, err := stream.Write(data)
	if err != nil {
		return fmt.Errorf("error writing to stream: %w", err)
	}
	return nil
}

func receiveData(stream quic.Stream, expectedLen int) ([]byte, error) {
	readBuf := make([]byte, expectedLen)

	//readPos := 0
	//for readPos < expectedLen {
	//	n, err := stream.Read(readBuf[readPos:])
	//	if err != nil {
	//		return nil, fmt.Errorf("error reading from stream: %w", err)
	//	}
	//	readPos += n
	//}
	//return readBuf[:readPos], nil

	n, err := stream.Read(readBuf)
	if err != nil {
		return nil, fmt.Errorf("error reading from stream: %w", err)
	}
	log.Printf("recvLen: %d\n", n)
	return readBuf[:n], nil

	//n, err := io.ReadFull(stream, readBuf)
	//if err != nil {
	//	return nil, fmt.Errorf("error reading from stream: %w", err)
	//}
	//return readBuf[:n], nil
}

Quic服务端代码

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"log"
	"math/big"

	"github.com/quic-go/quic-go"
)

// go env -w GO111MODULE=on
// go get -u github.com/quic-go/quic-go
// go list -m github.com/quic-go/quic-go

const addr = "192.168.8.48:19940"

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
		}
		remoteAddr := conn.RemoteAddr().String()
		fmt.Printf("Client connected from: %s\n", remoteAddr)

		go func() {
			defer stream.Close()
			for {
				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))

				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 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"},
	}
}

Wireshark抓取Quic数据包

在这里插入图片描述


http://www.kler.cn/a/288534.html

相关文章:

  • openSUSE 环境下通过 zypper 安装软件
  • 移动端【01】面试系统的MVVM重构实践
  • vue3+element-plus==> el-form输入响应式失效踩坑!!!!!!!!!!
  • FreeRTOS学习13——任务相关API函数
  • 使用kalibr_calibration标定相机(realsense)和imu(h7min)
  • 【Qt-ROS开发】使用 Qt Creator 构建和编译含 ROS 库的 Qt 项目
  • Unity接入飞行遥杆外设
  • 【BES2500x系列 -- RTX5操作系统】Battery模块 -- 创建电池检测定时器 --(十五)
  • 利用Spring Boot的@Transactional注解保障业务数据的一致性
  • 2024 【Delphi 12】苹果ios开发环境配置(五星保姆级)
  • UML(ER) manual book
  • 深入理解Java序列化:从入门到实践
  • c++ std::advance 使用简介
  • MySQL库表设计规范
  • IMU助力预测青少年脊柱侧弯
  • Apache DolphinScheduler大规模任务调度系统对大数据实时Flink任务支持
  • TikTok运营:IP地址如何影响TikTok的内容运营?
  • 内存管理篇-20 Linux虚拟内存管理
  • 亚信安全荣获“2024年网络安全优秀创新成果大赛”优胜奖
  • 缓存预热有哪些方案?
  • Java面试题真题·项目介绍部分总结
  • 【测试】bug 相关知识点总结
  • 单片机原理图与PCB设计心得体会
  • 解决Qt Creator与MSVC不匹配的问题
  • WebView快速打开
  • 【Linux】FRP:内网穿透