The network concept of a Socket refers to the connection of one host to another through the combination of compatible IP address and ports.
The software concept of Socket is a class hierarchy and draws its roots from Berkeley distribution of UNIX or BSD (Berkeley Software Distribution). Since sockets were designed for UNIX systems, they will work well and are relevant to all form of Linux.
In addition, sockets have been implemented frequently with Windows WinSock programming, though not necessarily all of socket structures and TCP/IP/UDP code will function in Windows as they do in Linux.
Sockets receive endpoints of a connection and the attached connections between sockets that link two separate hosts are called Threads (not the same thing as process Threads).
Socket programming is generally lower level programming meaning higher level applications and graphical interfaces run over the top of its structures and objects.
In Bitcoin Core, the socket logic is in the lowest level code region, the net.{h,cpp}
. The next higher level, the net_processing.{h,cpp}
region just handles the protocol message without any knowledge of the underlying network logic.
There are certain rules that govern socket programming. For instance, there are different address domains and socket types that may be used in a connection. Two sockets must be the same type and in the same domain to enable communication between hosts.
Two main types of socket domains are Unix domain (a character string used where two Unix processes share a file system) and Internet domain (internet address and port number).
This text breaks down the basic structure of an application using sockets and review the sequence of the events.
The commit c7dd9ff71b can be used as a reference for the project’s codebase at the time of writing.
git clone https://github.com/bitcoin/bitcoin.git cd bitcoin git checkout -b text_branch c7dd9ff71b
The structure that contains the internet address is known as sockaddr_in
. The definition of that structure is:
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
/* Internet address */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
The structure’s data members are used as follows:
sin_family
specifies the address family, usually the constant AF_INET
for IPv4 or AF_INET6
for IPv6.
sin_port
specifies the port number and must be used with htons()
function which converts the host byte order to network byte order so it can be retransmitted and routed propely when opening the socket connection. The reason for this is that computer and network protocol order their bytes in a non-compatible fashion, each opposite of the other.
sin_addr
holds the IP address return by inet_addr()
to be used in the socket connection.
sin_zero
is used with a char array buffer, usually set to 0.
Big-endianness is the dominant ordering in networking protocols, such as in the internet protocol suite, where it is referred to as network order, transmitting the most significant byte first. Conversely, little-endianness is the dominant ordering for processor architectures (x86, most ARM implementations, base RISC-V implementations) and their associated memory.
The htonl()
function converts an IPv4 address (unsigned integer) from host to TCP/IP network byte order (which is big-endian). It takes a 32-bit number in host byte order and returns a 32-bit number in the network byte order used in TCP/IP networks (the AF_INET
or AF_INET6
address family).
uint32_t htonl(uint32_t hostlong);
The htons()
function takes a 16-bit number in host byte order and returns a 16-bit number in network byte order used in TCP/IP networks (the AF_INET or AF_INET6 address family). It can be used to convert an IP port number in host byte order to the IP port number in network byte order.
uint16_t htons(uint16_t hostshort);
In Bitcoin Core, the function and structures presented above are used in CService::GetSockAddr()
to obtain the IPv4/6 socket address from the CService
, which is a combination of a network address (CNetAddr
) and a (TCP) port.
bool CService::GetSockAddr(struct sockaddr* paddr, socklen_t *addrlen) const
{
if (IsIPv4()) {
if (*addrlen < (socklen_t)sizeof(struct sockaddr_in))
return false;
//...
if (!GetInAddr(&paddrin->sin_addr))
return false;
paddrin->sin_family = AF_INET;
paddrin->sin_port = htons(port);
return true;
}
if (IsIPv6()) {
// ...
paddrin6->sin6_family = AF_INET6;
paddrin6->sin6_port = htons(port);
return true;
}
return false;
}
The socket()
function creates an unbound socket in a communications domain, and returns a file descriptor that can be used in later function calls that operate on sockets.
File descriptor (FD, less frequently fildes) is a unique identifier for a file or other input/output resource, such as a pipe or network socket.
File descriptors are a part of the POSIX API and typically have non-negative integer values, with negative values being reserved to indicate "no value" or error conditions.
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
The socket()
function takes the following arguments:
domain
specifies the communications domain in which a socket is to be created. In Bitcoin Core, sockaddr::sa_family
, which was mentioned in the section about sockaddr_in
, will be used as an argument to domain
(usually AF_INET
or AF_INET6
).
type
specifies the type of socket to be created. Bitcoin Core always use SOCK_STREAM
in this argument, what means the socket type is Transmission Control Protocol (TCP).
Other common option is SOCK_DGRAM
for UDP connections, but they are not used in Bitcoin Core.
protocol
specifies a particular protocol to be used with the socket. Specifying a protocol of 0 causes socket() to use an unspecified default protocol appropriate for the requested socket type.
Bitcoin Core uses IPPROTO_TCP
, which is the expected value when the domain
parameter is AF_INET
or AF_INET6
and the type
parameter is SOCK_STREAM
.
std::unique_ptr<Sock> CreateSockTCP(const CService& address_family)
{
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (!address_family.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
LogPrintf("Cannot create socket for %s: unsupported network\n", address_family.ToString());
return nullptr;
}
SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
// ...
return std::make_unique<Sock>(hSocket);
}
Note that the POSIX function socket()
returns non-negative integer, the socket file descriptor and it is atributed to hSocket
variable, but the CreateSockTCP(…)
return type is std::unique_ptr<Sock>
.
Sock
class was introduced in PR #20788. It manages the lifetime of a socket - when the object that contains the socket goes out of scope, the underlying socket will be closed.
In addition, the new Sock
class has a Send()
, Recv()
and Wait()
methods that can be overridden by unit tests to mock the socket operations.
The setsockopt()
function provides an application program with the means to control socket behavior. An application program can use setsockopt()
to allocate buffer space, control timeouts, or permit socket data broadcasts.
#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
const void *option_value, socklen_t option_len);
The level
argument specifies the protocol level at which the option resides. To set options at the socket level, specify the level argument as SOL_SOCKET
. To set options at other levels, supply the appropriate level identifier for the protocol controlling the option. For example, to indicate that an option is interpreted by the TCP (Transport Control Protocol), set level to IPPROTO_TCP
.
The following table shows some levels that can be passed to setsockopt()
.
Level | Description |
---|---|
|
Indicates that the options are protocol independent (socket layer). |
|
Indicates that the TCP protocol is to be used. |
|
Indicates that the options apply to sockets created for the IPv6 address family (AF_INET6). |
The socket option for which the value is to be set (for example, SO_BROADCAST). The optname
parameter must be a socket option defined within the specified level, or behavior is undefined.
The following table shows some options that can be applied to sockets.
Option | Description |
---|---|
|
If this flag is set to true (nonzero), then the socket is restricted to sending and receiving IPv6 packets only. |
|
Indicates that the rules used in validating addresses supplied in a |
|
Socket option enables developers to place access restrictions on IPv6 sockets. Such restrictions enable an application running on a private LAN to simply and robustly harden itself against external attacks. |
|
Set the no-delay option (disable Nagle’s algorithm) on the TCP socket. |
In Bitcoin Core, the options are set when creating or binding the socket.
bool CConnman::BindListenPort(...)
{
// ...
setsockopt(sock->Get(), SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne, sizeof(int));
// ...
if (addrBind.IsIPv6()) {
#ifdef IPV6_V6ONLY
setsockopt(sock->Get(), IPPROTO_IPV6, IPV6_V6ONLY, (sockopt_arg_type)&nOne, sizeof(int));
#endif
#ifdef WIN32
int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED;
setsockopt(sock->Get(), IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (const char*)&nProtLevel, sizeof(int));
#endif
}
//...
}
The WSAStartup
function initiates use of the Winsock DLL by a process. It is used in Bitcoin Core within #ifdef WIN32
directive in src/util/system.cpp:SetupNetworking()
function.
int WSAStartup( WORD wVersionRequired, LPWSADATA lpWSAData);
The bind()
function associates a socket with a sockaddr_in
structure containing the IP address and port used to build the connection.
It is required on an unconnected socket before subsequent calls to the listen function. It is normally used to bind to either connection-oriented (stream) or connectionless (datagram) sockets.
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
The bind()
function takes the following arguments:
socket
specifies the file descriptor of the socket to be bound.
address
points to a sockaddr structure containing the address to be bound to the socket. The length and format of the address depend on the address family of the socket.
address_len
specifies the length of the sockaddr structure pointed to by the address argument.
In Bitcoin Core, bind()
function is called in CConnman::BindListenPort()
right after the socket creation.
bool CConnman::BindListenPort(const CService& addrBind, ...)
{
// ...
std::unique_ptr<Sock> sock = CreateSock(addrBind);
// ...
if (::bind(sock->Get(), (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
{
int nErr = WSAGetLastError();
if (nErr == WSAEADDRINUSE)
strError = strprintf(_("Unable to bind to %s on this computer. %s is probably already running."), addrBind.ToString(), PACKAGE_NAME);
else
strError = strprintf(_("Unable to bind to %s on this computer (bind returned error %s)"), addrBind.ToString(), NetworkErrorString(nErr));
LogPrintf("%s\n", strError.original);
return false;
}
LogPrintf("Bound to %s\n", addrBind.ToString());
// ...
}
The listen()
places a socket in a state in which it is listening for an incoming connection.
#include <sys/socket.h>
int listen(int socket, int backlog);
It takes takes two arguments:
socket
is the unconnected socket to listen on.
backlog
is the maximum number of connections. If set to SOMAXCONN, the underlying service provider responsible for socket s will set the backlog to a maximum reasonable value.
In Bitcoin Core, listen()
is called in CConnman::BindListenPort()
right after the socket be bound.
Note that at the end of the method, the socket is added to vhListenSocket
, which tracks the listening sockets.
bool CConnman::BindListenPort(const CService& addrBind, ...)
{
// ...
if (listen(sock->Get(), SOMAXCONN) == SOCKET_ERROR)
{
strError = strprintf(_("Error: Listening for incoming connections failed (listen returned error %s)"), NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError.original);
return false;
}
vhListenSocket.push_back(ListenSocket(sock->Release(), permissions));
return true;
}
The accept()
function permits an incoming connection attempt on a socket.
#include <sys/socket.h>
int accept(int socket, struct sockaddr *restrict address,
socklen_t *restrict address_len);
This function takes the following arguments:
socket
specifies a socket that was created with socket()
, has been bound to an address with bind()
, and has issued a successful call to listen()
.
address
is either a null pointer, or a pointer to a sockaddr
structure where the address of the connecting socket shall be returned.
address_len
is an optional pointer to an integer that contains the length of structure pointed to by the addr parameter.
Upon successful completion, accept() returns the non-negative file descriptor of the accepted socket. Otherwise, -1 will be returned.
In Bitcoin Core, this function is called in CConnman::AcceptConnection()
. It retrieves the listening sockets from the vhListenSocket
list mentioned earlier.
void CConnman::AcceptConnection(const ListenSocket& hListenSocket) {
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
SOCKET hSocket = accept(hListenSocket.socket, (struct sockaddr*)&sockaddr, &len);
// ...
}
A fair number of socket calls, like accept()
and recv()
, are blocking. This is a problem for real-life network applications, where a socket server needs to handle a large number of clients.
It is easy to see that with large number of clients, the application would end up blocking most of the time and hence, would hardly scale. The way around this problem is to use the socket select()
call, which allows us to monitor a large number of sockets, all in one shot without having to block individually for each socket.
The select()
function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.
This function gives instructions to the kernel to wait for any of the multiple events to occur (for example, data received from a peer) and awakens the process only after one or more events occur or a specified time passes.
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
struct timeval {
long int tv_sec;
long int tv_usec
};
The select()
call takes several arguments:
nfds
is the highest file descriptor plus one. Thus, if two file descriptors with values 2 and 10 should be selected, then the nfds parameter should be 10 + 1 or 11 and not 2. The maximum number of sockets supported by select() has an upper limit, represented by FD_SETSIZE
(typically 1024
). For simpler programs, passing FD_SETSIZE
as nfds should be more than sufficient.
The next three parameters represent the three different types of events monitored by the select()
: read, write, and exception events.
readfds
is an optional pointer to a set of sockets to be checked for readability. A read event means that for a given fd, there is either some data to be read (so the application can call recv()
) or a new connection has been established (so the application can call accept()
).
writefds
is an optional pointer to a set of sockets to be checked for writability. A write event means that for a given fd, the local send buffer has become non-empty and the application can send more data.
exceptfds
is an optional pointer to a set of sockets to be checked for errors. An exception event means that there is some exception event like receiving out-of-band data.
The sixth and the last argument to select()
is a timeout value in the form of a pointer to a timeval
structure.
tv_sec
stores the number of whole seconds of elapsed time.
tv_usec
stores the rest of the elapsed time (a fraction of a second) in the form of microseconds.
If timeout
argument is NULL, then the select() waits indefinitely for events.
Bitcoin Core uses FD_SETSIZE to calculate the maximum number of connections (nMaxConnections
).
bool AppInitParameterInteraction(const ArgsManager& args)
{
// ...
#ifdef USE_POLL
int fd_max = nFD;
#else
int fd_max = FD_SETSIZE;
#endif
nMaxConnections = std::max(std::min<int>(nMaxConnections, fd_max - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS - NUM_FDS_MESSAGE_CAPTURE), 0);
// ...
}
Note that FD_SETSIZE
only if the USE_POLL
directive is true. This directive will be more detailed in the next section.
The select()
function is used in CConnman::SocketEvents(…)
to select sockets to receive and send data asynchronously.
#ifdef USE_POLL
// ...
#else
void CConnman::SocketEvents(...)
{
std::set<SOCKET> recv_select_set, send_select_set, error_select_set;
if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) {
interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS));
return;
}
// ...
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = SELECT_TIMEOUT_MILLISECONDS * 1000;
fd_set fdsetRecv;
// ...
FD_ZERO(&fdsetRecv);
// ...
SOCKET hSocketMax = 0;
for (SOCKET hSocket : recv_select_set) {
FD_SET(hSocket, &fdsetRecv);
hSocketMax = std::max(hSocketMax, hSocket);
}
// ...
int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, &fdsetError, &timeout);
// ...
for (SOCKET hSocket : recv_select_set) {
if (FD_ISSET(hSocket, &fdsetRecv)) {
recv_set.insert(hSocket);
}
}
}
GenerateSelectSet()
retrieves the sockets to that will be used as argument to select. As mentioned previously the first parameter of select()
is the highest file descriptor plus one. hSocketMax
tracks the highest value.
The fd_set
structure is used by sockets functions and service providers, such as the select function, to place sockets into a "set".
FD_ZERO(fdsetp)
initializes the descriptor set pointed to by fdsetp
to the null set. No error is returned if the set is not empty at the time FD_ZERO()
is invoked.
FD_SET(fd, fdsetp)
adds the file descriptor fd
to the set pointed to by fdsetp
. If the file descriptor fd
is already in this set, it has no effect on the set.
FD_ISSET(fd, fdsetp)
evaluates to non-zero if the file descriptor fd
is a member of the set pointed to by fdsetp
, and evaluates to zero otherwise.
The code snippet just shows the process for std::set<SOCKET> &recv_set
. Note that the same steps are apply to std::set<SOCKET> &send_set
and std::set<SOCKET> &error_set
. This method not only selects the sockets to be monitored, but also divides them in three sets for better handling.
Until PR #14336, the only way to concurrently monitor multiple network connections (sockets) in Bitcoin Core was via the select()
function, shown in the previous section.
This PR introduced the use of poll()
function and removed the restriction on the maximum number of socket descriptors, the FD_SETSIZE
(typically 1024
).
With poll()
, the application must allocate an array of pollfd
structures, and pass the number of entries in this array, so there’s no fundamental limit.
poll()
has some advantages over select()
. It does not require that the user calculate the value of the highest- numbered file descriptor plus one.
An it is more efficient for large-valued file descriptors.
To watch a single file descriptor with the value 900, for example, via select()
— the kernel needs to check each bit of each passed-in set, up to the 900th bit.
#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
This function is very similar to select()
in that they both watch sets of file descriptors for events, such as incoming data ready to recv()
, socket ready to send()
data to, out-of-band data ready to recv()
, errors, etc.
The basic idea is that you pass an array of struct pollfd
in fds
, the size of the array in nfds
along with a certain timeout
in milliseconds.
The timeout can be negative if you want to wait forever. If no event happen on any of the socket descriptors by the timeout, poll()
terminates.
Each element in the array of struct pollfd
represents one socket descriptor, and contains the following fields:
struct pollfd {
int fd; // the socket descriptor
short events; // bitmap of events we're interested in
short revents; // when poll() returns, bitmap of events that occurred
};
The array’s members are pollfd
structures within which fd
specifies an open descriptor. The events and revents are bitmasks constructed by OR’ing a combination of the following event flags.
Event | Description |
---|---|
POLLIN |
Data other than high-priority data may be read without blocking. |
POLLOUT |
Normal data may be written without blocking. |
POLLERR |
An error has occurred on the device or stream. This flag is only valid in the |
POLLHUP |
The remote side of the connection hung up. |
There other flags, but Bitcoin Core uses only these.
The poll()
function is used only in Linux. As the comments below inform, the poll()
of WIN32 and macOS is broken. if the enviroment is Linux, the USE_POLL
directive is defined.
// Note these both should work with the current usage of poll, but best to be safe
// WIN32 poll is broken https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
// __APPLE__ poll is broke https://github.com/bitcoin/bitcoin/pull/14336#issuecomment-437384408
#if defined(__linux__)
#define USE_POLL
#endif
There are two different versions of CConnman::SocketEvents(..)
. One of them uses select()
and the other uses poll()
. The directive USE_POLL
mentioned above decides which version will be used.
#ifdef USE_POLL
void CConnman::SocketEvents(std::set<SOCKET> &recv_set, std::set<SOCKET> &send_set, std::set<SOCKET> &error_set)
{
std::set<SOCKET> recv_select_set, send_select_set, error_select_set;
if (!GenerateSelectSet(recv_select_set, send_select_set, error_select_set)) {
interruptNet.sleep_for(std::chrono::milliseconds(SELECT_TIMEOUT_MILLISECONDS));
return;
}
std::unordered_map<SOCKET, struct pollfd> pollfds;
for (SOCKET socket_id : recv_select_set) {
pollfds[socket_id].fd = socket_id;
pollfds[socket_id].events |= POLLIN;
}
// ...
std::vector<struct pollfd> vpollfds;
vpollfds.reserve(pollfds.size());
for (auto it : pollfds) {
vpollfds.push_back(std::move(it.second));
}
if (poll(vpollfds.data(), vpollfds.size(), SELECT_TIMEOUT_MILLISECONDS) < 0) return;
if (interruptNet) return;
for (struct pollfd pollfd_entry : vpollfds) {
if (pollfd_entry.revents & POLLIN) recv_set.insert(pollfd_entry.fd);
if (pollfd_entry.revents & POLLOUT) send_set.insert(pollfd_entry.fd);
if (pollfd_entry.revents & (POLLERR|POLLHUP)) error_set.insert(pollfd_entry.fd);
}
}
#else
void CConnman::SocketEvents(...)
{
// it uses `select()`, as seen in the previous section
}
This version that uses the poll(…)
starts exactly in the same way as the one using select(…)
: retrieving via GenerateSelectSet(…)
the sockets that will be monitored.
For each of these sockets, a struct pollfd
is created and the fd
field of that structure receives the value of the file descriptor (socket id) and the event
field receives the event type (POLLIN
to receive data, POLLOUT
for writing data and POLLERR | POLLHUP
for error or disconnect events).
After poll(…)
is called, the method loops through vpollfds
and splits the vector in three: std::set<SOCKET> &recv_set
, std::set<SOCKET> &send_set
and std::set<SOCKET> &error_set
, according to event type. It keeps both methods (poll()
and select()
with same output type).
The recv()
function receives a message from a connected socket.
#include <sys/socket.h>
ssize_t recv(int socket, void *buffer, size_t length, int flags);
This function takes the following arguments:
socket
specifies the socket file descriptor.
buffer
points to a buffer where the message should be stored.
length
specifies the length in bytes of the buffer pointed to by the buffer argument.
flags
specifies the type of message reception. Bitcoin Core uses MSG_DONTWAIT
, which enables non-blocking operation.
Bitcoin Core calls recv()
for every node when CConnman::SocketEvents()
indicates there are sockets being monitored for read and exception events (recv
and error_set
).
void CConnman::SocketHandler()
{
std::set<SOCKET> recv_set, send_set, error_set;
SocketEvents(recv_set, send_set, error_set);
// ...
{
// ...
recvSet = recv_set.count(pnode->hSocket) > 0;
sendSet = send_set.count(pnode->hSocket) > 0;
errorSet = error_set.count(pnode->hSocket) > 0;
}
if (recvSet || errorSet)
{
// typical socket buffer is 8K-64K
uint8_t pchBuf[0x10000];
int nBytes = 0;
{
// ...
nBytes = recv(pnode->hSocket, (char*)pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
// ...
}
// ...
}
}
The recv()
function is also called in Sock::Recv
when connected through proxy.
ssize_t Sock::Recv(void* buf, size_t len, int flags) const
{
return recv(m_socket, static_cast<char*>(buf), len, flags);
}
The send()
function sends data on a connected socket.
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);
socket
specifies the socket file descriptor.
buffer
points to the buffer containing the message to send.
length
specifies the length of the message in bytes.
flags
specifies the type of message transmission. Bitcoin Core uses MSG_DONTWAIT
, which enables non-blocking operation and MSG_NOSIGNAL
, which will turn the SIGPIPE behavior off.
A SIGPIPE is sent to a process if it tried to write to a socket that had been shutdown for writing or isn’t connected (anymore).
The send()
function is also called in Sock::Send
when connected through proxy.
ssize_t Sock::Send(const void* data, size_t len, int flags) const
{
return send(m_socket, static_cast<const char*>(data), len, flags);
}
A Socket refers to the connection of one host to another through the combination of compatible IP address and ports.
The structure that contains the internet address is known as sockaddr_in
and it is used to create a socket.
Big-endianness is the dominant ordering in networking protocols, while little-endianness is the dominant ordering for processor architectures (x86, most ARM implementations, base RISC-V implementations). htonl()
and htons()
converts from host byte order to network byte order.
The socket()
function creates an unbound socket in a communications domain, and returns a file descriptor that can be used in later function calls that operate on sockets.
The setsockopt()
function provides an application program with the means to control socket behavior.
The bind()
function associates a socket with a sockaddr_in
structure containing the IP address and port used to build the connection.
The listen()
places a socket in a state in which it is listening for an incoming connection.
The accept()
function permits an incoming connection attempt on a socket.
The select()
function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O (receive and write data).
The pool()
function is very similar to select()
in that they both watch sets of file descriptors for events, but more efficient.
The recv()
function receives a message from a connected socket and the send()
function sends data on a connected socket.