Socket Programming

In network programming, user interface to TCP/IP is through a set of C library functions and system calls, which are collectively known as the sockets API (Rago 1993; Stevens et al. 2004). In order to use the socket API, we need the socket address structure, which is used to identify both the server and the client. The socket address structure is defined in netdb.h and sys/socket.h.

1. Socket Address

In the socket address structure,

sin_family is always set to AF_INET for TCP/IP networks.

sin_port contains the port number in network byte order.

sin_addr is the host IP address in network byte order.

2. The Socket API

A server must create a socket and bind it with a sockaddr containing the server’s IP address and port number. It may use either a fixed port number, or let the OS kernel choose a port number if sin_port is 0. In order to communicate with a server, a client must create a socket. For UPD sockets, binding the socket to a server address is optional. If the socket is not bound to any specific server, it must provide a socket address containing the IP and port number of the server in subsequent sendto()/recvfromO calls. The following shows the socket() system call, which creates a socket and returns a file descriptor

(1). int socket(int domain, int type, int protocol)

Examples:

int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);

This creates a socket for sending/receiving UDP datagrams.

int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);

This creates a connect-oriented TCP socket for sending/receiving data streams.

A newly created socket does not have any associated address. It must be bound with a host address and port number to identify either the receiving host or the sending host. This is done by the bind() system call.

(2). int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);

The bind() system call assigns the address specified by addr to the socket referred to by the file descriptor sockfd, addrlen specifies the size in bytes of the address structure pointed to by addr. For a UDP socket intended for contacting other UDP server hosts, it must be bound to the address of the client, allowing the server to send reply back. For a TCP socket intended for accepting client connections, it must be bound to the server host address first.

(3). UDP Sockets

UDP sockets use sendto()/recvfrom() to send/receive datagrams.

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);

sendto() sends len bytes of data in buf to a destination host identified by dest_addr, which contains the destination host IP and port number. Recvfrom() receives data from a client host. In addition to data, it also fills src_addr with client’ s IP and port number, allowing the server to send reply back to the client.

(4). TCP Sockets

After creating a socket and binding it to the server address, a TCP server uses listen() and accept() to accept connections from clients

int listen(int sockfd, int backlog);

listen() marks the socket referred to by sockfd as a socket that will be used to accept incoming connection The backlog argument defines the maximum queue length of pending connections.

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

The accept() system call is used with connection-based sockets. It extracts the first connection request on the queue of pending connections for the listening socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket., which is connected with the client host. When executing the accept() system call, the TCP server blocks until a client has made a connection through connect().

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

The connect() system call connects the socket referred to by the file descriptor sockfd to the address specified by addr, and the addrlen argument specifies the size of addr. The format of the address in addr is determined by the address space of the socket sockfd;

If the socket sockfd is of type SOCK_DGRAM, i.e. UDP socket, addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. This restricts a UDP socket to communicate with a specific UDP host, but it is rarely used in practice. So for UDP sockets, connection is either optional or unnecessary. If the socket is of type SOCK_STREAM, i.e.TCP socket, the connect() call attempts to make a connection to the socket that is bound to the address specified by addr.

(5). send()/read() and recv/write()

After establishing a connection, both TCP hosts may use send() /write() to send data, and recv()/ read() to receive data. Their only difference is the flag parameter in send() and recv(), which can be set to 0 for simple cases.

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

ssize_t write(sockfd, void *buf, size_t, len)

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

ssize_t read(sockfd, void *buf, size_t len);

Source: Wang K.C. (2018), Systems Programming in Unix/Linux, Springer; 1st ed. 2018 edition.

Leave a Reply

Your email address will not be published. Required fields are marked *