Skip to content

Sending big chunks of data through kcp-go #265

@karelbilek

Description

@karelbilek

Hello, I am experimenting with kcp-go and I guess I am doing something wrong.

I want to send a bigger amount of data through kcp connection... but it's weirdly slow?

Let's have this - a bit contrived, but minimal example - almost-echo server, that reads a number as a string, and sends back that many bytes of data.

package main

import (
	"crypto/rand"
	"fmt"
	"io"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/xtaci/kcp-go/v5"
)

const dataAmount = 1024 * 1024

func main() {
	block, _ := kcp.NewNoneBlockCrypt(nil)
	if listener, err := kcp.ListenWithOptions("127.0.0.1:12345", block, 10, 3); err == nil {
		// spin-up the client
		go client()
		for {
			s, err := listener.AcceptKCP()
			if err != nil {
				log.Fatal(err)
			}
			go handleRandData(s)
		}
	} else {
		log.Fatal(err)
	}
}

func readNumber(conn *kcp.UDPSession) (int, error) {
	bl := &strings.Builder{}
	bs := make([]byte, 1)
	for {
		_, err := io.ReadFull(conn, bs)
		if err != nil {
			return 0, err
		}
		if bs[0] == '\n' {
			break
		}
		bl.WriteByte(bs[0])
	}
	return strconv.Atoi(bl.String())
}

// handleRandData reads a number and returns that many random bytes
func handleRandData(conn *kcp.UDPSession) {
	for {
		n, err := readNumber(conn)
		if err != nil {
			log.Println(err)
			return
		}

		fmt.Println("server read")

		bs := make([]byte, n)
		_, err = rand.Read(bs)
		if err != nil {
			log.Println(err)
			return
		}

		_, err = conn.Write(bs)
		if err != nil {
			log.Println(err)
			return
		}
		fmt.Println("server sent")
	}
}

func client() {
	block, _ := kcp.NewNoneBlockCrypt(nil)

	// wait for server to become ready
	time.Sleep(time.Second)

	// dial to the echo server
	if sess, err := kcp.DialWithOptions("127.0.0.1:12345", block, 10, 3); err == nil {
		f := time.Now()
		_, err := fmt.Fprintln(sess, dataAmount)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("client sent")

		buf := make([]byte, dataAmount)
		_, err = io.ReadFull(sess, buf)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("client read", time.Since(f))
	} else {
		log.Fatal(err)
	}
}

You can see that it works with lesser numbers; and with bigger numbers it also, sort of, works, but really really slow.

When I send 1024*1024 bytes - 1 MB - it takes 10 seconds. That is weird...? That is a lot on localhost-to-localhost! Am I sending it in a wrong way?

In comparison, when I use just TCP, it is 9 ms. See when I just convert the listeners/servers to net.Dial/net.Listen and "tcp"

package main

import (
	"crypto/rand"
	"fmt"
	"io"
	"log"
	"net"
	"strconv"
	"strings"
	"time"
)

const dataAmount = 1024 * 1024

func main() {
	if listener, err := net.Listen("tcp", ":12345"); err == nil {
		// spin-up the client
		go client()
		for {
			s, err := listener.Accept()
			if err != nil {
				log.Fatal(err)
			}
			go handleRandData(s)
		}
	} else {
		log.Fatal(err)
	}
}

func readNumber(conn net.Conn) (int, error) {
	bl := &strings.Builder{}
	bs := make([]byte, 1)
	for {
		_, err := io.ReadFull(conn, bs)
		if err != nil {
			return 0, err
		}
		if bs[0] == '\n' {
			break
		}
		bl.WriteByte(bs[0])
	}
	return strconv.Atoi(bl.String())
}

// handleRandData reads a number and returns that many random bytes
func handleRandData(conn net.Conn) {
	for {
		n, err := readNumber(conn)
		if err != nil {
			log.Println(err)
			return
		}

		fmt.Println("server read")

		bs := make([]byte, n)
		_, err = rand.Read(bs)
		if err != nil {
			log.Println(err)
			return
		}

		_, err = conn.Write(bs)
		if err != nil {
			log.Println(err)
			return
		}
		fmt.Println("server sent")

	}
}

func client() {
	// wait for server to become ready
	time.Sleep(time.Second)

	// dial to the echo server
	if sess, err := net.Dial("tcp", "127.0.0.1:12345"); err == nil {
		f := time.Now()
		_, err := fmt.Fprintln(sess, dataAmount)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("client sent")

		buf := make([]byte, dataAmount)
		_, err = io.ReadFull(sess, buf)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("client read", time.Since(f))
	} else {
		log.Fatal(err)
	}
}

I probably do something wrong, but I miss what

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions