先简单的贴出服务端和客户端的核心代码:
客户端:session,err:=quic.DialAddr(addr,tlsConf,nil)
服务端:listener,err:=quic.ListenAddr(addr,generateTLSConfig(),nil)
1.客户端
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"quic-echo-example"},
}
首先在QUIC配置TLS来保证安全性。
session,err:=quic.DialAddr(addr,tlsConf,nil)
拨号,即连接指定的服务器端IP地址。session类似于TCP/IP的套接字。
stream,err:= ession.OpenStreamSync(context.Background())
创建流,在stream上发送和接收信息。context.Background()类似于管道,相当于给予QUIC一个通信的手段。
//发送数据
stream.Write([]byte(message))
//接收数据
buf:=make([]byte,len(message))
io.ReadFull(stream,buf)
2.服务端
listener,err:=quic.ListenAddr(addr,generateTLSConfig(),nil)
监听addr,generateTLSConfig()代表TLS的配置,最后一个参数是quic.config一般是nil。
sess,err:=listener.Accept(context.Background())
sess与上面的session类似。
stream,err:=sess.AcceptStream(context.Background())
创建stream,接收信息,在server端新建一个context专门对这个连接进行通信。
3.仿照用例自己实现
分析完上面主要的代码之后,我们现在自己实现一个简单的QUIC通信,实现客户端发送Hello,服务端发送Hi。为了方便,我们把客户端和服务端写到一个文件夹里。
const addr="localhost:6688"
const clientMessage="Hello"
const serverMessage= Hi"
首先定义一下本地监听端口号和要发送的数据:
3.1客户端
session,err:=quic.DialAddr(addr,tlsConf,nil)
为了简单,我们使用最简单的TLS配置安全传输
stream,err:=session.OpenStreamSync(context.Background())
创建个流,使用流传输数据
_,err=stream.Write([]byte(clientMessage))
客户端发送数据
_,err=stream.Read(buf)
读取服务端发来的信息到buf,buf是一个字节数组。客户端至此完成。
3.2服务端
listener,err:=quic.ListenAddr(addr,generateTLSConfig(),nil)
服务端使用TLS检测安全,generateTLSConfig()类似于通用配置,可自定义。
sess,err:=listener.Accept(context.Background())
接收新的连接请求。
stream,err:=sess.AcceptStream(context.Background())
在刚才的连接请求上创建新的接收流。
_,err=stream.Read(buf)
_,err=stream.Write([]byte(serverMessage))
发送和接收数据。
以上完整代码如下:
package main
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/lucas-clemente/quic-go"
"math/big"
"time"
)
const addr = "localhost:4242"
const clientMessage = "Hello"
const serverMessage = "Hi"
func main() {
go func() {
err := server()
if err != nil {
panic(err)
}
}()
err := client()
if err != nil {
panic(err)
}
// 等待main和go程 执行完,防止server执行完自动结束
time.Sleep(time.Second * 5)
}
// 客户端
func client() error {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"quic-echo-example"},
}
session, err := quic.DialAddr(addr, tlsConf, nil)
if err != nil {
return err
}
stream, err := session.OpenStreamSync(context.Background())
if err != nil {
return err
}
fmt.Printf("Client: Sending '%s'\n", clientMessage)
_, err = stream.Write([]byte(clientMessage))
if err != nil {
return err
}
buf := make([]byte, 1024)
_, err = stream.Read(buf)
if err != nil {
return err
}
fmt.Printf("Client: Got '%s'\n", buf)
return nil
}
// 服务端
func server() error {
listener, err := quic.ListenAddr(addr, generateTLSConfig(), nil)
if err != nil {
return err
}
sess, err := listener.Accept(context.Background())
if err != nil {
return err
}
stream, err := sess.AcceptStream(context.Background())
if err != nil {
panic(err)
}
buf := make([]byte, 1024)
_, err = stream.Read(buf)
if err != nil {
return err
}
fmt.Printf("Server: Got '%s'\n", buf)
fmt.Printf("Server: Sending '%s'\n", serverMessage)
_, err = stream.Write([]byte(serverMessage))
return err
}
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{"quic-echo-example"},
}
}