UDP implementation
- Not connection-oriented (dial and listen will not block)
- No guarantee of reliable delivery
- Simplex (one conn can only read or write)
- Support one-to-many
package main
import (
"fmt"
"log"
"net"
"time"
)
const (
ip = "127.0.0.1"
port = 9090
clientNum = 3
)
func main() {
for i := 0; i < clientNum; i++ {
go func(i int) {
serverAddr := fmt.Sprintf("%s:%d", ip, port)
serverUdpAddr, err := net.ResolveUDPAddr("udp", serverAddr)
if err != nil {
log.Fatal(err)
}
connTo, err := net.DialUDP("udp", nil, serverUdpAddr)
Addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", ip, port+i+1))
if err != nil {
log.Fatal(err)
}
connFrom, err := net.ListenUDP("udp", Addr)
if err != nil {
log.Fatal(err)
}
for {
msg := []byte(fmt.Sprintf("hello from client%d", i))
connTo.Write(msg)
buf := make([]byte, 1024)
connFrom.Read(buf)
log.Printf("[Client%d]:%s", i, string(buf))
time.Sleep(time.Second)
}
}(i)
}
addr := fmt.Sprintf("%s:%d", ip, port)
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
log.Fatal(err)
}
connFrom, err := net.ListenUDP("udp", udpAddr)
if err != nil {
log.Fatal(err)
}
connTo := make([]net.Conn, clientNum)
for i := 0; i < clientNum; i++ {
addr0 := fmt.Sprintf("%s:%d", ip, port+i+1)
udpAddr0, err := net.ResolveUDPAddr("udp", addr0)
if err != nil {
log.Fatal(err)
}
connTo[i], _ = net.DialUDP("udp", nil, udpAddr0)
}
for {
for i := 0; i < clientNum; i++ {
buf := make([]byte, 1024)
connFrom.Read(buf)
log.Printf("[Server]:%s", string(buf))
msg := []byte("hello from server")
connTo[i].Write(msg)
}
}
}
TCP implementation
- Connection-oriented (dial and listen will block waiting)
- Guaranteed reliable delivery
- Duplex (a conn can both read and write)
- Only supports one-to-one
package main
import (
"fmt"
"log"
"net"
"time"
)
const (
ip = "127.0.0.1"
port = 9090
clientNum = 3
)
func main() {
for i := 0; i < clientNum; i++ {
go func(i int) {
serverAddr := fmt.Sprintf("%s:%d", ip, port)
conn, err := net.Dial("tcp", serverAddr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
for {
buf := []byte(fmt.Sprintf("hello from client%d", i))
conn.Write(buf)
buf0 := make([]byte, 1024)
conn.Read(buf0)
log.Printf("[Client%d]:%s", i, string(buf0))
}
}(i)
}
serverAddr := fmt.Sprintf("%s:%d", ip, port)
listener, err := net.Listen("tcp", serverAddr)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
conns := make([]net.Conn, clientNum)
for i := 0; i < clientNum; i++ {
conns[i], err = listener.Accept()
if err != nil {
log.Fatal(err)
}
}
for {
for i := 0; i < clientNum; i++ {
buf0 := make([]byte, 1024)
conns[i].Read(buf0)
log.Printf("[Server]%s", string(buf0))
buf := []byte("hello from server")
conns[i].Write(buf)
}
time.Sleep(time.Second)
}
}
QUIC implementation
- Connection-oriented (dial and listen will block)
- Guaranteed reliable delivery
- Simplex (one conn can only read or write)
- Only supports one-to-one
go get github.com/quic-go/quic-go (go version >=1.19)
const (
ip = "127.0.0.1"
port = 9090
clientNum = 5
)
func main() {
// Client
for i := 0; i < clientNum; i++ {
go func(i int) {
connTo, err := quic.DialAddr(fmt.Sprintf("%s:%d", ip, port), genTlsConf(), nil)
if err != nil {
log.Fatal(err)
}
listener, err := quic.ListenAddr(fmt.Sprintf("%s:%d", ip, port+i+1), generateTLSConfig(), nil)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
connFrom, err := listener.Accept(context.Background())
if err != nil {
log.Fatal(err)
}
for {
streamTo, err := connTo.OpenStream()
if err != nil {
log.Fatal(err)
}
streamTo.Write([]byte(fmt.Sprintf("hello from client%d", i)))
streamTo.Close()
streamFrom, err := connFrom.AcceptStream(context.Background())
if err != nil {
log.Fatal(err)
}
readBuf := make([]byte, 1024)
streamFrom.Read(readBuf)
streamFrom.Close()
log.Printf("[Client%d]:%s", i, string(readBuf))
}
}(i)
}
// Server
listener, err := quic.ListenAddr(fmt.Sprintf("%s:%d", ip, port), generateTLSConfig(), nil)
if err != nil {
log.Fatal(err)
}
defer listener.Close()
connFrom := make([]quic.Connection, clientNum)
connTo := make([]quic.Connection, clientNum)
for i := 0; i < clientNum; i++ {
connFrom[i], err = listener.Accept(context.Background())
if err != nil {
log.Fatal(err)
}
}
for i := 0; i < clientNum; i++ {
connTo[i], err = quic.DialAddr(fmt.Sprintf("%s:%d", ip, port+i+1), genTlsConf(), nil)
if err != nil {
log.Fatal(err)
}
}
for {
for i := 0; i < clientNum; i++ {
streamFrom, err := connFrom[i].AcceptStream(context.Background())
if err != nil {
log.Fatal(err)
}
readBuf := make([]byte, 1024)
streamFrom.Read(readBuf)
streamFrom.Close()
log.Println("[Server]:", string(readBuf))
streamTo, err := connTo[i].OpenStream()
if err != nil {
log.Fatal(err)
}
writeBuf := []byte("hello from server...")
streamTo.Write(writeBuf)
streamTo.Close()
}
time.Sleep(time.Second)
}
}
func genTlsConf() *tls.Config {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{
"quic-echo-example"},
}
return tlsConf
}
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"},
}
}