Hello, sockets!
In the previous tutorial we looked at reading from and writing to files. In this tutorial we'll instead look at reading from and writing to network sockets.
To start things off, create a file called socket.inko
with the following
contents:
import std.net.ip (IpAddress)
import std.net.socket (UdpSocket)
import std.stdio (STDOUT)
class async Main {
fn async main {
let stdout = STDOUT.new
let server = UdpSocket.new(IpAddress.v4(0, 0, 0, 0), port: 0).get
let client = UdpSocket.new(IpAddress.v4(0, 0, 0, 0), port: 0).get
let addr = server.local_address.get
client.connect(addr.ip.get, addr.port).get
client.write_string('Hello, world!').get
let bytes = ByteArray.new
server.read(into: bytes, size: 32).get
stdout.write_bytes(bytes).get
}
}
Now run it using inko run socket.inko
, and the output should be:
Hello, world!
Explanation
Compared to previous tutorials there's quite a bit going on here, so let's take a look at what this code does.
First, we import two types not seen before: IpAddress
and UdpSocket
. The
first is used to represent IPv4 and IPv6 addresses, the second is used for UDP
sockets. We use UDP sockets in this example as it keeps things as simple as
possible.
Our sockets are created as follows:
let server = UdpSocket.new(IpAddress.v4(0, 0, 0, 0), port: 0).get
let client = UdpSocket.new(IpAddress.v4(0, 0, 0, 0), port: 0).get
What happens here is that we create two sockets that bind themselves to IP address 0.0.0.0, using port 0. Using port 0 results in the operating system assigning the socket a random unused port number. This way we don't need to worry about using a port that's already in use.
Next, we encounter the following:
let addr = server.local_address.get
client.connect(addr.ip.get, addr.port).get
client.write_string('Hello, world!').get
Here we get the address of the server we need to connect the client to, which we
do using connect()
. We then write the string "Hello, world!" to the client,
sending it to the server.
We then read the data back from the server:
let bytes = ByteArray.new
server.read(into: bytes, size: 32).get
stdout.write_bytes(bytes).get
When using sockets you shouldn't use read_all
as we did in the files tutorial,
because read_all
won't return until the socket is disconnected.
Just as in the files tutorial, we use get
to handle errors for the sake of
brevity.
Using TCP sockets
Let's change the program to use TCP sockets instead. We'll start by changing
sockets.inko
to the following:
import std.net.ip (IpAddress)
import std.net.socket (TcpServer)
import std.stdio (STDOUT)
class async Main {
fn async main {
let stdout = STDOUT.new
let server = TcpServer.new(IpAddress.v4(0, 0, 0, 0), port: 9999).get
let client = server.accept.get
let bytes = ByteArray.new
client.read(into: bytes, size: 32).get
stdout.write_bytes(bytes).get
}
}
This time we're using a fixed port number (9999) as that makes this particular example a little easier.
Next, we'll create another file called client.inko
with the following
contents:
import std.net.ip (IpAddress)
import std.net.socket (TcpClient)
class async Main {
fn async main {
let client = TcpClient.new(IpAddress.v4(0, 0, 0, 0), port: 9999).get
client.write_string('Hello, world!').get
}
}
To run these programs, run inko run server.inko
first in one terminal
window, then open a separate terminal window and run inko run client.inko
in
this new window. If all went well, the server.inko
program writes "Hello,
world!" to the terminal, then terminates.
What we did here is create a simple TCP server using the aptly named TcpServer
type, connected to address 0.0.0.0 and port 9999, then connected a client to it
using the similarly aptly named TcpClient
type.