This post was updated on June 28, 2013 to reflect changes in the TcpServer/TcpSocket api in Julia.
Recently, I’ve been writing the WebSockets implementation for Julia.
TcpSocket
s were not well documented
when we started using them,
so I figure a tutorial might be useful
for anyone else who might want to use TCP sockets in Julia.
REPL server
I’m still figuring out this “try things out in the REPL first” thing (due to usually using compiled languages), but here’s a first example of how you might play with TCP sockets in the Julia REPL (on the right) and netcat in another terminal (on the left).
terminal1 | terminal2 |
---|---|
$ julia |
|
> server = listen(8080) |
|
TcpServer(listening) | |
> conn = accept(server) |
|
$ nc localhost 8080 |
|
TcpSocket(connected,0 bytes waiting) | |
> line = readline(conn) |
|
hi |
|
“hi\n” | |
> write(conn, "Hello") |
|
Hello | 5 |
> close(conn) |
|
$ | false |
The code-formated parts are what you type. The normally-formated parts are what the computer prints.
Basically, you start julia
, and open a TCP server for listening.
You can only accept
connections on a TcpServer
, not write
or read
.
After creating a TcpServer
, you wait for a connection to accept.
At this point, you’ll need to switch terminals and open a connection from netcat.
(You can switch the order of accept
and starting netcat.)
Notice that accept
returns a TcpSocket
.
We can do two things with a TcpSocket
: read or write.
Using readline
, we can block until we get a full line from the connection.
In order to stop blocking, you’ll need to enter a line in netcat.
You need to press enter in netcat for readline
to return in Julia.
You can also write text to the TCP connection from Julia.
The write
function takes a TcpSocket
and a string to write.
The text you write
from Julia will appear in netcat, as expected.
You can call close
on a TcpSocket
to close the TCP connection.
Closing the TCP connection makes the netcat session close.
Echo Server
A very simple server to run over TCP is an echo server. Each time you send this server a line of text, it will repeat that line back to you.
Here is the entire file:
server = listen(8080)
while true
conn = accept(server)
@async begin
try
while true
line = readline(conn)
write(conn,line)
end
catch err
print("connection ended with error $err")
end
end
end
Running the example
Put this file in echoserver.jl
.
Then, you can run it with julia echoserver.jl
.
With the server running, open up netcat again (nc localhost 8080
).
Type in some text and press enter.
You should see the same text printed again under what you typed.
The structure of the server
The core of this server is a pair of while true
loops.
The outer one accepts each incoming connection.
The inner one reads lines from the client and echos them.
The try
catch
block catches any error thrown in the while loop.
The thrown error is bound to err
.
The $err
in the string literal is how you embed values in Julia strings.
Given two strings, s1
and s2
, "$s1$s2"
would be their concatenation.
You can also embed arbitrary Julia expressions: "$(2+2)"
would be "4"
.
Handling Multiple Connections: @async
@async
is a macro. It affects the begin
to end
block that directly follows it.
@async
starts a new coroutine to handle the execution of the block.
Coroutines are like threads in that there are multiple threads of execution in the program at once, but do not involve concurrent execution. Only one coroutine is running at once, but the language runtime switches between them.
This means that the main connection-accepting routine will see the @async
block return immediately.
This allows the outer while loop to continue accepting connections, while another routine handles each existing connection.
If you remove the @async
part, your echo server will only deal with one client connection at a time.
You can test this using two (or more) instances of netcat, each in their own terminal window.
Inside the @async
block, we’re in a new coroutine.
We can sit in our while true
loop for as long as we want without affecting the server’s ability to accept connections.
Coroutines have very low overhead, so making a new one for each connection is reasonable. Webstack.jl works on this same one-thread-per-connection principle.
Basic TcpSocket
API
listen(8080)
binds aTcpServer
to a socket listening for connections on port 8080.accept(server)
blocks until there is a connection to accept onserver
, then accepts it and returns aTcpSocket
.readline(conn)
blocks until a complete line comes in onconn
, then returns it.read(conn,Uint8)
blocks until there is a byte to read fromconn
, and then returns it. You can useconvert(Char,u)
to convert aUint8
into aChar
. This will let you see the ASCII letter for theUint8
you read.read(conn,Char)
blocks until there is a byte to read fromconn
, and then returns it.write(conn,line)
writesline
toconn
.