Skip to content

TCP UDP System Calls

TCP/UDP System Calls

The foundation of network programming in Unix-like systems is the socket API. A socket is a communication endpoint that allows processes to exchange data across networks or within the same machine. This is the Programmer's interface to the socket layer provided to them by the Operating System.

socket() - Creating Communication Endpoints

The socket() system call creates a new communication endpoint and returns a file descriptor:

cpp
int socket(int domain, int type, int protocol);

Parameters:

  • domain: Communication domain (AF_INET for IPv4, AF_INET6 for IPv6, AF_UNIX for local)
  • type: Socket type (SOCK_STREAM for TCP, SOCK_DGRAM for UDP)
  • protocol: Protocol (usually 0 for default)

Flags:

  • SOCK_NONBLOCK: Creates a non-blocking socket
  • SOCK_CLOEXEC: Close-on-exec flag for security

bind() - Associating Sockets with Addresses

The bind() system call associates a socket with a specific address and port:

cpp
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

This is essential for servers to specify which port they'll listen on. Clients typically don't need to bind to a specific address.

listen() - Preparing for Connections

For TCP servers, listen() marks the socket as passive and starts listening for incoming connections:

cpp
int listen(int sockfd, int backlog);

The backlog parameter specifies the maximum length of the pending connections queue.

accept() - Accepting Connections

The accept() system call extracts the first connection from the listening queue and creates a new socket for that connection:

cpp
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

This is how servers handle multiple clients - each accepted connection gets its own socket.

connect() - Initiating Connections

Clients use connect() to establish a connection to a server:

cpp
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

For TCP, this initiates the three-way handshake. For UDP, this simply sets the default destination.

close() - Closing Connections

The close() system call closes the socket and releases associated resources:

cpp
int close(int sockfd);

send() and recv() - TCP Data Transfer

For TCP sockets, use send() and recv() for reliable data transfer:

cpp
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

Common flags:

  • MSG_DONTWAIT: Non-blocking operation
  • MSG_NOSIGNAL: Don't generate SIGPIPE
  • MSG_OOB: Out-of-band data

sendto() and recvfrom() - UDP Data Transfer

For UDP sockets, use sendto() and recvfrom() for connectionless communication:

cpp
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

setsockopt() - Configuring Sockets

The setsockopt() system call allows you to configure various socket options:

cpp
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

Common options:

  • SO_REUSEADDR: Allow binding to an address that's already in use
  • SO_KEEPALIVE: Enable TCP keep-alive mechanism
  • SO_LINGER: Control socket closure behavior
  • SO_RCVBUF/SO_SNDBUF: Set receive/send buffer sizes

getsockopt() - Retrieving Socket Options

The getsockopt() system call retrieves current socket option values:

cpp
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

Non-blocking Sockets

Creating Non-blocking Sockets

You can create non-blocking sockets in several ways:

  1. During creation:
cpp
int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
  1. After creation:
cpp
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

Handling Non-blocking Operations

Non-blocking sockets return EAGAIN or EWOULDBLOCK when operations would block:

cpp
ssize_t bytes = recv(sockfd, buffer, size, 0);
if (bytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
    // No data available, try again later
}

Socket Errors and Debugging

Common Socket Errors

  • EADDRINUSE: Address already in use
  • ECONNREFUSED: Connection refused by peer
  • ETIMEDOUT: Connection timed out
  • ENOBUFS: No buffer space available
  • EMSGSIZE: Message too large

Socket State Checking

Use getsockopt() with SO_ERROR to check for pending errors:

cpp
int error = 0;
socklen_t len = sizeof(error);
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
if (error != 0) {
    // Handle error
}

Buffer Sizing

Proper buffer sizing is crucial for performance:

cpp
int rcvbuf = 1024 * 1024;  // 1MB receive buffer
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));

TCP_NODELAY

For low-latency applications, disable Nagle's algorithm:

cpp
int flag = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

Socket Reuse

Enable address reuse to avoid "Address already in use" errors:

cpp
int reuse = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

Socket Flow Patterns

TCP Socket Flow

  1. socket(AF_INET, SOCK_STREAM, 0) - Create TCP socket
  2. bind() - Bind to address (server)
  3. listen() - Start listening (server)
  4. accept() - Accept connections (server)
  5. connect() - Connect to server (client)
  6. send()/recv() - Data transfer
  7. close() - Close connection

UDP Socket Flow

  1. socket(AF_INET, SOCK_DGRAM, 0) - Create UDP socket
  2. bind() - Bind to address (optional for client)
  3. sendto()/recvfrom() - Data transfer
  4. close() - Close socket

The key difference is that UDP doesn't require connection establishment - you can immediately start sending data with sendto().

File Descriptor Management

Sockets are file descriptors, so they can be used with:

  • select(), poll(), epoll() - Multiplexing
  • fcntl() - File descriptor control
  • dup(), dup2() - Duplication
  • close() - Closure

This integration with the file descriptor system makes sockets compatible with all standard Unix I/O operations and multiplexing mechanisms.

Questions

Q: Which system call is used to create a new socket?

socket() creates a new communication endpoint and returns a file descriptor.

Q: What does the bind() system call do?

bind() associates a socket with a specific address and port number.

Q: Which flag in socket() creates a non-blocking socket?

SOCK_NONBLOCK flag creates a non-blocking socket for asynchronous I/O.

Q: What is the purpose of setsockopt() with SO_REUSEADDR?

SO_REUSEADDR allows binding to an address that is already in use.

Q: What is the difference between TCP and UDP sockets?

TCP provides reliability through acknowledgments and retransmission, while UDP is connectionless.

Q: Which system call is used to accept incoming connections?

accept() extracts the first connection from the listening queue and creates a new socket.

Q: What does the listen() system call do?

listen() marks the socket as passive and starts listening for incoming connections.

Q: Which flag enables TCP keep-alive?

SO_KEEPALIVE enables TCP keep-alive mechanism to detect dead connections.