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版本
- go version go1.22.6 linux/amd64
- 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"},
}
}