Socket Programming

Socket Programming aims to connect two computers or processes by specifying with socket, which is a pair of IPv4 Address# and port number#. By establishing a socket, both of them could communicate in duplex mode or both directions, in and out, using Internet Protocol Suite (TCP/IP)#. However, in socket programming, we usually define two entity: server and client. Server as the sender who send the messages, client as the receiver who receive and/or process the messages.

Library Used

There are four libraries to use in Linux: sys/types.h and sys/socket.h for socket definitions and functions, netinet/in.h for address information and unistd.h for closing sockets.

There would be the case that we will need to import the library arpa/inet.h in order to call inet_aton. It is used to convert string to IP address.

Server

To establish a Transmission Control Protocol (TCP)# connection, a server often has such procedure: First, it needs to establish a socket. Next, it needs to bind the corresponding socket to a dedicated IP and port. Then, it will listen to the socket and wait for accepting incoming connection. If there is a client established a connection, it can then send any data, if they are available, to the client.

The following is its implementation in C:

int serverSocket = socket(AF_INET, SOCK_STREAM, 0); // Use TCP for connection

struct sockaddr_in serverAddress = {
  .sin_family = AF_INET,          // IPv4
  .sin_port = htons(9002),        // convert to network bytes which is big-endian
  .sin_addr.s_addr = INADDR_ANY,  // To 0.0.0.0
  // .sin_addr.s_addr = inet_addr("198.164.2.10") // define an IP address instead of INADDR_ANY
};

int bindStatus = bind(serverSocket, (struct sockaddr *) &serverAddress, sizeof(serverAddress));
if (bindStatus == -1) {
  perror("Fail to bind the socket");
}

listen(serverSocket, 5); // 5 connection request that can be queue by the system for the socket

int clientSocket = accept(serverSocket, (struct sockaddr_in *) &clientAddress, sizeof(clientAddress));

send(clientSocket, &serverData, sizeof(serverData), 0);

close(clientSocket);
close(serverSocket);

Instead of send() data to the socket, one could receive data from the client invoking read() function on the client socket. It is especially useful in determining the validity of the client request such as in #Hypertext Transfer Protocol (HTTP) socket programming. Other than that, the server could send a response to the client by write() instead of read(). The difference is that send() will return error when the message sent is too big for the protocol to handle. Otherwise, in most cases, they are almost identical.

close() call could be replaced by shutdown(socketfd, 2) which behaves identically. However, shutdown() provides more flexibility to the programmer as it could be used to close send and/or receive channel for the socket. Passing 0 indicates the receive channel is close for the socket, 1 for disallowing send channel to be used on the socket.

Note: If there is a need to receive client’s data, declare a struct sockaddr and pass it to the second parameter of accept. Remember to pass in the size for struct sockaddr.

Note: Always ensure that the port number and IP address and is stored in Big-Endian#. This can be done by calling htons on the port number and htonl on the IP address if both of them are of integer values (type of short or long). The translation could be reversed with the function ntohs and ntohl.

Note: If the IPv4 Address is a string with dotted format, we can translate it to 32 bit network address with the function inet_addr. Vice versa, we can get an IPv4 address string with dotted format from a network address with the function inet_ntoa.

Client

In Transmission Control Protocol (TCP)# connection, client requires fewer steps than server. First, like server, it needs to establish a socket. Thereafter, client will connect itself to the socket. And then, it will wait for the response from the server and receive any data transmitted from there.

The following is its implementation in C:

int networkSocket = socket(AF_INET, SOCK_STREAM, 0);  // use TCP for connection

struct sockaddr_in serverAddress = {
  .sin_family = AF_INET,          // IPv4
  .sin_port = htons(9002),        // convert to network bytes which is big-endian
  .sin_addr.s_addr = INADDR_ANY,  // To 0.0.0.0
  // .sin_addr.s_addr = inet_addr("198.164.2.10") // define an IP address instead of INADDR_ANY
};

inet_pton(AF_INET, argv[1], &serverAddress);  // or assigns IP address
                                              // according to user input

int connectionStatus = connect(networkSocket, (struct sockaddr *) &serverAddress, sizeof(serverAddress));
if (connectionStatus == -1) {
  perror("Fail to establish socket connection");
}

recv(networkSocket, &serverResponse, sizeof(serverResponse), 0);

close(networkSocket);
return 0;

Note: The opposite function for inet_pton is inet_ntop which converts network address to string.

UDP Connection

Socket programming in User Datagram Protocol (UDP)# is much fewer steps to set up compare to TCP. Both server and client just need to establish socket (pass in SOCK_DGRAM instead of SOCK_STREAM for UDP connection) and bind it to an address for themselves. Then, they could call either sendto() or recvfrom() to send or receive any data transmitted by them. Note that both sendto() and recvfrom() requires to know the destination address. Otherwise, its structure is almost identical to the above TCP implementation.

Note: For send(), recv(), sendto(), and recvfrom(), there is an option to pass a flag as a parameter to the function. They are 0 as nothing, MSG_OOB (sends out-of-band data, that bypass normal data, on sockets), and MSG_DONTROUTE (ignore routing conditions, seems like deprecated).

Miscellaneous

Additionally, we could alter the socket’s default option for more convenient set up using the system call setsockopt(). The parameters that should be passed are the socket File Descriptor, the level (who in the system to interpret this option), the option name, the option’s value, and the value’s length. The usage is shown below:

// Allow local address reuse for the sockfd in the level of general socket code
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *) &yes, sizeof(yes)) == -1) {
  perror("setsockopt error");
  exit(1);
}

We can get the socket’s name (server or client) with the functions getsocketname() and getpeername() by passing socket File Descriptor, socket address, and its length. The former returns the local associated address (server) and the latter returns the foreign associated address (client).

Asynchronous

See I/O Multiplexing#.

Caution

See Producer-Consumer Problem

Links to this page
#c #networking