TCP client and server#
This page shows how to talk over TCP with ATcpSocket and ATcpServerSocket. For UDP, see UDP datagrams and ping.
Connecting to a server#
ATcpSocket connects in its constructor and blocks until the connection is established. The destination is an AInet4Address, which can be built from a hostname or an IP address together with a port.
try {
ATcpSocket socket(AInet4Address::fromString("example.com", 80));
// connected
} catch (const AIOException& e) {
ALogger::err("Net") << "Can't connect: " << e;
}
Both name resolution and connection failures throw AIOException (more precisely, its subclass SocketException).
Reading and writing#
The socket is an IInputStream and an IOutputStream, so it can be passed anywhere a stream is expected. For example,
Parsing JSON mentions that AJson::fromStream reads from sockets.
const AString request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
auto utf8 = request.toStdString();
socket.write(utf8.data(), utf8.size());
char buffer[1024];
while (size_t n = socket.read(buffer, sizeof(buffer))) {
// process n bytes
}
read returns 0 when the other side closes the connection. TCP does not preserve message boundaries: a single read
may return a part of what the peer wrote with one write, or several writes glued together. If you need exactly N
bytes, use readExact; for variable-sized messages, send the size first.
Note
Socket calls block the calling thread. Do not use sockets directly in the UI thread; run them in a background
thread (see AUI_THREADPOOL) and post the result back to the UI thread.
Writing a server#
ATcpServerSocket binds a port on construction. Each accept() call blocks until a client connects and returns a new
ATcpSocket for it.
AAsyncHolder clients;
ATcpServerSocket server(8080);
for (;;) {
_<ATcpSocket> client = server.accept();
clients << AUI_THREADPOOL {
try {
char buffer[1024];
while (size_t n = client->read(buffer, sizeof(buffer))) {
client->write(buffer, n); // echo
}
} catch (const AIOException& e) {
ALogger::warn("Net") << "Client dropped: " << e;
}
};
}
Every client is served by a task in the thread pool, which is kept alive by AAsyncHolder, so a slow client does not prevent others from connecting. The loop above
never ends; in a real application, run it in a dedicated thread and interrupt that thread to stop the server
(interrupting a thread blocked in a socket call makes it throw AThread::Interrupted).
Closing#
A socket is closed when it is destroyed. To close it earlier (e.g. to wake up a thread blocked in read), call
close().
Surprises#
- Only IPv4 is supported.