Connecting to a server is done in three steps: Resolve the address, create a socket, and connect the socket to the address. Resolving the address looks like this:
addrinfo hints, *info, *cur;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &info);
if (ret != 0) handle_error();
Once we've resolved the address, we create a socket. Since
getaddrinfo()
returns a linked list of address results, we need to find one that has an address type we can connect to. This way, it will automatically use IPv6 if that's all that's available. Once we have a connected socket we can free the returned address info. for (cur = info; cur != NULL && m_data->socket == -1; cur = cur->ai_next) {
m_data->socket = socket(cur->ai_family,
cur->ai_socktype, cur->ai_protocol);
if (m_data->socket != -1) {
// we can bind via this protocol, can we connect?
if (::connect(m_data->socket, cur->ai_addr, cur->ai_addrlen) == -1) {
::close(m_data->socket);
m_data->socket = -1;
} else {
m_data->remotehost = host + ":" + port;
}
}
}
freeaddrinfo(info);
To read from the socket, use either
read()
(which works with all file descriptors) or recv()
which also takes socket-specific flags. Note that by default, either will block if there's nothing to be read yet. int num = recv(m_data->socket, m_data->buffer, BUFSIZE, 0);
if (num <= 0) handle_socket_closed();
To check whether there's anything to read on a socket, use the
select()
function: timeval waittime = { 0, 0 };
fd_set readset;
FD_ZERO(&readset);
FD_SET(m_data->socket, &readset);
select(m_data->socket+1, &readset, NULL, NULL, &waittime);
if (FD_ISSET(m_data->socket, &readset)) read_socket_data();
If you want to listen for incoming network connections the setup is slightly different. We use a 'listener socket' which listens on a given port, and then when a connection attempt is made, a call to
accept()
will return another socket which is connected to the remote client.To listen:
// bind to the requested port
addrinfo hints, *info, *cur;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
int r;
if ((r = getaddrinfo(NULL, port.c_str(), &hints, &info)) != 0) return false;
// try to get a socket to bind to the port
for (cur = info; cur != NULL && m_data->socket == -1; cur = cur->ai_next) {
m_data->socket = socket(cur->ai_family,
cur->ai_socktype, cur->ai_protocol);
if (m_data->socket != -1) {
// insert lame joke about rings here
if (bind(m_data->socket, cur->ai_addr, cur->ai_addrlen) == -1)
close();
}
}
freeaddrinfo(info);
// if we have a socket, listen on it
listen(m_data->socket, m_data->backlog);
// accept the incoming connection
sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
sock.m_data->socket = ::accept(m_data->socket, (sockaddr *)&addr, &addrlen);
if (sock.m_data->socket == -1) return false; // fail :(
So there you go; almost everything you need to know to write socket code. And if you just want something that works, here's the full source of tcpstream.cpp and tcpstream.h. I've released it under attribution license, so feel free to use it in whatever projects you want, commercial or otherwise.
No comments:
Post a Comment