Networking is a common requirement in modern applications. Phobos offers a module, std.socket
, which provides a foundation for networked applications. We'll write a client and server pair to demonstrate how to use it. The client will send lines from standard input to the server. The server will accept any number of connections, say hello to new clients, and then echo back whatever it received.
We'll be building two separate applications: the client and the server.
Let's create a client by executing the following steps:
Socket
object.The code is as follows:
void main() { import std.socket, std.stdio; auto socket = new Socket(AddressFamily.INET, SocketType.STREAM); char[1024] buffer; socket.connect(new InternetAddress("localhost", 2525)); auto received = socket.receive(buffer); // wait for the server to say hello writeln("Server said: ", buffer[0 .. received]); foreach(line; stdin.byLine) { socket.send(line); writeln("Server said: ", buffer[0 .. socket.receive(buffer)]); } }
Let's create a server by executing the following steps:
Socket
object.listen()
method to start listening.connectedClients
objects and a SocketSet
object for multiplexing.SocketSets
object.readSet
function.Socket.select()
and pass your SocketSet
objects to it.receive
method and reply to the client with the socket’s send
method.readSet
function, call accept
to accept a new connection and send our welcome message.The code is as follows:
void main() { import std.socket; auto listener = new Socket(AddressFamily.INET, SocketType.STREAM); listener.bind(new InternetAddress("localhost", 2525)); listener.listen(10); auto readSet = new SocketSet(); Socket[] connectedClients; char[1024] buffer; bool isRunning = true; while(isRunning) { readSet.reset(); readSet.add(listener); foreach(client; connectedClients) readSet.add(client); if(Socket.select(readSet, null, null)) { foreach(client; connectedClients) if(readSet.isSet(client)) { // read from it and echo it back auto got = client.receive(buffer); client.send(buffer[0 .. got]); } if(readSet.isSet(listener)) { // the listener is ready to read, that means // a new client wants to connect. We accept it here. auto newSocket = listener.accept(); newSocket.send("Hello! "); // say hello connectedClients ~= newSocket; // add to our list } } } }
Phobos' std.socket
module is a wrapper of the BSD socket API available on all major operating systems. The std.socket
module is a thin wrapper. If you've used sockets in other languages, you'll feel at home with Phobos.
Clients simply connect and then send and receive data. The send
and receive
functions both take a buffer as their argument instead of returning a newly allocated array. This provides maximum performance by allowing you to reuse memory. Note that they may not use the whole buffer. These methods will wait until they can do something (unless you set the blocking
property to false
), and then they will do as much as they can immediately before returning. If only one byte is available on the network connection, receive
will write that byte to the buffer and return the value; it won't wait to fill the buffer entirely. This lets you handle partial and variable size messages as soon as possible. Similarly, send
may not send your whole message at once. A robust socket code should check the return value to ensure that all the data actually made it out.
On the server side, instead of connecting, you bind the Socket
object to an address and then listen for connections. The argument to listen
is the maximum number of clients to queue. This is typically a relatively small number, for example, 10.
There are several techniques to handle multiple connections. You can use threads, processes, or various event-based checks. Each technique has pros and cons in simplicity and speed. In the example, we used std.socket
, an easy-to-use and portable function to multiplex several connections in an application. The select
function doesn't scale well to large numbers of connections because it must loop through each one to check isSet
, but for small numbers of connections, it performs adequately and is fairly simple to use.
The SocketSet
class in std.socket
is used to pass collections of sockets to select
. You can create it outside your loop and then add your connections to it. Don't forget to add the listening socket too when it is ready to read. This means that a new client is trying to connect.
When select
indicates that a socket is set in the SocketSet
object, it is ready to be used immediately. You should handle the data as quickly as possible to minimize the wait time for other clients. The server simply continues these same steps in a loop until it is terminated.
Here, we created a socket that uses TCP, the Internet protocol upon which many common application protocols are built, including HTTP for the Web, SMTP for e-mail, and more. You can also create UDP and Unix sockets with std.socket
. To create a Unix socket (not available on Windows operating systems), pass AddressFamily.UNIX
to the Socket
constructor instead of AddressFamily.INET
. Then, when connecting or binding, use the new UnixAddress
instead of InternetAddress
. Other than that, the API is the same as the TCP socket. Unix sockets only work with clients on the same computer, so they provide more security and speed than a TCP socket. However, they are only useful for specialized purposes. To use UDP—a connectionless protocol that is faster but less reliable than TCP—you can create the socket with SocketType.DGRAM
and then use the sendTo
and receiveFrom
methods instead of send
, receive
, and connect
.
18.118.12.50