New way to set up Linux development environment in Windows with WSL
Background
I like system programming which can allow you to touch more software development skills in the bottom level.
Linux is the perfect platform when you want to do system programming. But if you’re using a computer running Windows on it, then you have to spend some time to set up the Linux development environment. Generally speaking there are two traditional ways to do that: virtual machine
and dualboot
, both need some effort. Or you can try to do that with container
technology, for example, I once shared one article about how to do it with Docker
.
In this article, I will introduce a new and easier way to do this without too much overhead.
Windows Subsystem for Linux
The new way is Windows Subsystem for Linux (WSL)
. I have to admit that the operating system is complex and difficult so for now I don’t know how Microsoft make WSL
works. In details, you can refer to this article to learn how WSL
allows Windows to access Linux files. In this article let’s focus on how to set it up and what kind of benefits it can provide to developers.
Based on the official document, with WSL
you can
- Run common command-line tools such as
grep
,sed
,awk
. - Run Bash shell scripts and GNU/Linux command-line applications including:
- Tools: vim, emacs, tmux.
- Languages: NodeJS, Javascript, Python, Ruby, C/C++, C# & F#, Rust, Go, etc.
- Services: SSHD, MySQL, Apache, lighttpd, MongoDB, PostgreSQL.
- Install additional software using your own GNU/Linux distribution package manager.
With these conditions, you can set up a completed Linux development environment.
Install WSL
For detail steps to install WSL
, you can find it on the official document. Based on my experience, I follow the document to download and install Linux Ubuntu distribution smoothly, which is much easier than settig the virtual machine.
File mount
By default, you can also access your local machine’s file system from within the Linux Bash shell. Since your local drives are mounted under the /mnt folder of the subsystem.
In this way, you can develop the code with the productivity tools in Windows and build it in Linux environment.
Network
This is another convenient point. WSL
shares the IP address of Windows, as it is running on Windows.
As such you can access any ports on localhost e.g. if you had a web server running on port 8080, you could access it just by visiting http://localhost:8080 into your Windows browser.
Set up the development environment
After install the Ubuntu system, I also install tools to prepare the development environment. For example, GCC
to develop C language program as below.

Summary
Based on my testing and experience, WSL
can save developers’ time to set up Linux environment.
How to write a Golang HTTP server with Linux system calls
Background
HTTP
is everywhere. As a software engineer, you’re using the HTTP
protocol every day. Starting an HTTP
server will be an easy task if you’re using any modern language or framework. For example, in Golang you can do that with the following lines of code:
1 | package main |
You can finish the job easily because net/http
package implements the HTTP
protocol completely. How can you do that without net/http
package ? That’s the target of this article.
Note: This article is inspired by Joe Schafer’s post a lot. My implementation has something different which totally removes dependency on Golang’s net
package, but the idea of using system call
in Golang to setup the TCP/IP connetion is the same. Thanks very much for Joe Schafer’s interesting post.
Another thing I need to mention is this article will cover many concepts, but it’s very difficult to discuss all of them in detail. To understand this article smoothly, you need some prerequisite knowledge such as OSI model
, TCP/IP stack
, socket programming
, HTTP protocol
and system call
. I will add some explanations on these topics to help you understand this article and give some references and links to let you continue exploring more in advanced level.
OSI network model
OSI model
partitions the data flow in a communication system into seven abstraction layers. These layers form a protocol stack, with each layer communicating with the layer above and the layer below as follows:

For example, HTTP
is in layer 7, TCP
is in layer 4 and IP
is in layer 3.
OSI is a general model, which was first specified in the early 1980s. But neither traditional nor modern networking protocols fit into this model neatly. For example, TCP/IP
stack does not define the three upper layers: session, presentation, and application. In fact, it does not define anything above the transport layer. From the viewpoint of TCP/IP
, everything above the transport layer is part of
the application. So the layered network model more consistent with Linux (TCP/IP stack is implemented in Linux kernel) is as follows:
- Application Layer (telnet, ftp, http)
- Host-to-Host Transport Layer (TCP, UDP)
- Internet Layer (IP and routing)
- Network Access Layer (Ethernet, wi-fi)
Once again, it is important to point out that the upper layers—Layers 5, 6, and 7—are not part of the TCP/IP stack.
Another critical point to understand is data encapsulation
. The data flow goes from the bottom physical level to the highest-level representation of data in an application.
Each layer has administrative information that it has to keep about its own layer. It does this by adding header information to the packet it receives from the layer above, as the packet passes down. Each header contains information regarding the message contents. For example, one HTTP
server sends data from one host to another. It uses the TCP
protocol on top of the IP
protocol, which may be sent over Ethernet
. This looks like:

The packet transmitted over ethernet, is the bottom one. On the receiving side, these headers are removed as the packet moves up.
Next let’s see how TCP/IP
stack encapsulates HTTP
message and send it over the network through socket
. The idea can be illustrated with the following image:

I will explain how it works by writing a HTTP server from scratch, you can refer to this Github repo to get all the code.
TCP/IP
TCP/IP
stack is originated from ARPANET
project, which is integrated into Unix BSD OS as the first implementation of TCP/IP
protocols.
Nowadays, TCP/IP
is still implemented in the operating system level. For Linux system, you can find the source code inside the kernel. The detailed implementation is outside the scope of this article. You can study it in this Github link.
Socket
As I mentioned in the above sections, HTTP server is running in the application level. How it can work with TCP/IP
stack which lives in the kernel? The answer is socket
.
The socket
interface was originally developed as part of the BSD operating system. Sockets provide an interface between the application level programs and the TCP/IP stack. Linux (or other OS) provides an API and sockets, and applications use this API to access the networking facilities in the kernel.
The socket interface is really TCP/IP’s window on the world. In most modern systems incorporating TCP/IP, the socket interface is the only way that applications make use of the TCP/IP suite of protocols.
One main advantage of sockets in Unix or Linux system is that the socket is treated as a file descriptor
, and all the standard I/O functions work on sockets in the same way they work on a local file. File descriptor is simply an integer associated with an open file.
You may heard everything in Unix is a file. The file can be a network connection, a pipe, a real file in the disk, a device or anything else. So when you want to send data to another program over the Interent you will do it through a file descriptor.
In our HTTP server case, it will get the request by reading data from the socket and send the response by writing data to the socket.
Next, let’s review the source code to see how the HTTP server is implemented.
First, we need setup the TCP connection through socket, the process can be described in the following image:

In Golang, net
package provides all the socket related functionalities. Since this article’s purpose is writing a HTTP server from scratch, so I create a package named simplenet to provide the very basic implementation.
1 | package simplenet |
netSocket data model is created to represent the socket, which contains only one field fd means file descriptor. And all the socket related APIs: Read, Write, Accept and Close, are defined. The usage of socket API is not in this article’s scope, you can easily find a lot of great documents about it online.
The logic of netSocket is not complicated, because it delegates the job to the kernel by system call
. A system call is a programmatic way a program requests a service from the kernel, in detail you can refer to this article. In Golang, all the system calls are wrapped inside the syscall
standard package.
One thing need to mention is different platform have different syscall
usages, so the demo code shown in this article can only be compiled and build on Linux system.
Now we setup the TCP server and wait for connection request from client side. Next, let’s see how to read or write HTTP request and response through socket.
HTTP
The main workflow is as follows:
1 | import ( |
As you can see, the HTTP request parsing logic is defined in the ParseRequest method in simplenet package.
1 | package simplenet |
The HTTP request message can be divided into three parts request line
, request headers
and request body
as follows:

The logic inside ParseRequest handles these 3 parts step by step. You can refer to the comments in the demo code.
One thing need to emphasis is that ParseRequest method doesn’t depends on net
package. Because I want to show how HTTP server works in the bottom level, so I copy the request parsing logics from net
package into my simplenet
package. The parsing for request header part is kind of complex, but it doesn’t influence your understanding about the main concept of HTTP server. If you want to know the details, you can refer to the simplenet/simpleTextProto
package. The important thing to understand is HTTP server reads the request message with Read method of netSocket . And the Read method makes socket read system call to get network data from TCP stack:
1 | syscall.Read(ns.fd, p) |
On the other side, HTTP response is sent back by calling WriteString
method of simplenet
package
1 | func WriteString(c *netSocket, s string) (n int, err error) { |
WriteString
simply calls Write method of netsocket, which makes socket write system call to send data over Interent with TCP stack:
1 | syscall.Write(ns.fd, p) |
That’s all for the code part. Next let’s try to run this simple HTTP server we build from scratch.
Demo
Build (need Linux platform) and run this HTTP server with default options setting and send request to it with curl
. The result goes as follows:

the server works as expected.
How to write a load performance test CLI tool
Background
When you want to do the load performance test to your HTTP backend service, a handy and powerful tool can make your job much easier. For example, ApacheBench
(short for ab) is widely used in this field. But it is not today’s topic. Instead, I want to introduce Hey written in Golang
and supports the same functionality as ab
.
Hey
usage goes as follows:
1 | Usage: hey [options...] <url> |
I didn’t list all of the options but only show several related to this article’s content. As you can see in the above list, Hey
can support different practical features, such as multiple workers to run in the concurrent style and rate limit by queries per second (QPS). It can also support run by duration and run by request number two modes.
In this article, we can review the design and implementation of Hey
to see how to make a load performance testing tool.
Architecture Design
The design of Hey
is not complex, and the architecture can be divided into the following three parts:
- Control logic: the main workflow like how to set up multiple concurrent workers, how to control QPS rate limiter, and how to exit the process when duration is reached;
- HTTP request configuration: the headers or parameters needed to send request;
- Test report: print or save the result after the load testing finish.
The architecture diagram goes as follows, after reading this article you’ll understand every element in this diagram:

This article will focus on the first item (since it is the real interesting part) to show how to use Golang
‘s concurrent programming techniques to realize these features.
Exit the process
In the hey.go
file, you can find the entry point main function. Let’s hide the boilerplate code and review the core logic in the main function as follows:
1 | w := &requester.Work{ |
requester.Work struct contains all the option settings, including request numbers, concurrent workers, and QPS (it also contains the test result report).
After creating an instance of requester.Work, then call the Init() method.
1 | func (b *Work) Init() { |
Init() method will initialize two channel
: results and stopCh. results channel is used for request response communication. And stopCh channel is used for signal to stop the concurrent workers.
Note that there are two ways to exit from the program. The first one is the user manually stops the program, for example, by pressing ctrl + c. In this case, the signal.Notify()
method from the std library can catch the signal to terminate the process. The second one is by the time duration option. Both of the process exiting logics are running in a Goroutine
.
To stop the worker, Stop() method will be called:
1 | func (b *Work) Stop() { |
What it does is sending several values to the stopCh channel. Note that it sends b.C values to the channel, which is the same as the number of concurrent workers.
You can imagine that each worker should wait for the value from the stopCh channel. When the worker receives one value, it should stop sending requests. Right? Then in this way, I can stop all the concurrent workers. Let’s check our guess in the following sections.
Concurrent Workers
In the above main function, you can see that Run() is called:
1 | func (b *Work) Run() { |
There are several points worthy of discussion. In this section, let’s review runWorkers(). And runReporter() and Finish() are related to test result reports, and we will revisit them later in this article.
runWorkers() goes as follows:
1 | func (b *Work) runWorkers() { |
This is a very typical pattern to launch multiple goroutine
via sync.WaitGroup
. Each worker is created by calling b.runWorker in a goroutine. In this way, multiple concurrent workers can run together.
Note that before all workers finish their tasks, wg.Wait() will block Finish() to run, which is used to report test results. And we will talk about it in the following sections.
Next step, the logic goes into runWorker method, and let’s review how QPS rate limit works?
QPS
The core code of runWorker goes as follows:
1 | func (b *Work) runWorker(client *http.Client, n int) { |
The first parameter of method runWorker is client for sending requests. We need more analysis about the second parameter n denoting the number of requests this worker needs to send out. When runWorker is called, b.N/b.C is passed to it. b.N is the total number of request need to be sent out, and b.C is the number of concurrent workers. b.N divided by b.C is just the number of requests for each worker. Right?
But if the user sets the duration option, what is the number of requests? You can find the following logic in the main entry function:
1 | if dur > 0 { |
When the user sets duration option, the request number will be math.MaxInt32
. In this method, Hey can combine run by duration and run by request number two modes together.
As we mentioned in the introduction part, Hey
can support QPS rate limit, and this strategy is written inside the runWorker method. Note that a receive-only channel
throttle is created with time.Tick
, which sends out a value in each time period. And the time period is defined by
1 | time.Duration(1e6/(b.QPS)) * time.Microsecond |
For example, QPS = 1000, then the time period is 100ms, every 100ms throttle channel will receive a value.
throttle is placed before makeRequest() call, and in this way, we can realize the rate limit effect.
Stop Worker
In the runWorker method, you can also see the select and case
usage.
1 | select { |
As we mentioned in the above section, stopCh channel is used to stop the worker. Right? Now you can see how it is implemented. It maps to the Stop method we reviewed above as follows:
1 | // Send stop signal so that workers can stop gracefully. |
b.C
numbers of value are sent to stopCh channel, and there are b.C
numbers of concurrent workers as well. Each worker can receive one value from the channel and stop running.
Result Report
Let’s also have a quick review of how the result report work. Firstly in the makeRequest method, each request’s result is sent to the results channel as follows:
1 | func (b *Work) makeRequest(c *http.Client) { |
And in the runReporter
method, you can see the logic like this:
1 | func runReporter(r *report) { |
In this case, a for
is used to receive all the values from the channel. Note that the loop will continue until the channel is closed. It is another very typical concurrent programming pattern in Golang
. We can realize the same functionality by using select case
pattern, as long as we can add one more channel to send the exit signal. But on the syntax level, for
loop pattern is much more cleaner.
So there must be one place where the channel is closed, or else the deadlock
issue will occur. In detail, you can refer to my previous article for more advanced explanations.
The channel is closed in the Finish method like this:
1 | func (b *Work) Finish() { |
Please also note that how the done channel works. Finish method firstly close
the results channel, then the for loop will break and r.done <- true
can have chance to run. Finally b.report.finalize() can print the result since <-b.report.done is not blocked. t
Summary
In this article, I show you how to write a load performance testing CLI tool by reviewing Hey as an example. In the code level we discussed several concurrent programming patterns provided by Golang. Concurrent(or parallel) programming is difficult, and Golang is build just for that. Keep practice.