Socket Programming

Client-Server communication

  • Server
    • Passively waits for and responds to the clients
    • Passive Socket
  • Client
    • Initiates the communication
    • Must know the address and the port number of the server
    • Active socket

Sockets – Procedure

PrimitivesMeaning
socketCreates a new communication endpoint
bindAttach a local address and port to a socket
listenAnnounce willingness to accept connection
acceptBlock caller until a connection request arrives
connectActively attempt to establish connection
send/sendtoSend some data over the connection
recv/recvfromReceive some data over the connection
CloseRelease the connection

Create a socket:

   int sock_fd = socket (int domain, int type, int protocol)
  • The socket can be created by using an API called socket().
  • The first parameter is nothing but the address family which can be AF_INET (IPv4) or AF_INET6 (IPv6) or AF_UNIX (Unix file sockets).
  • The second parameter is the type of socket which can be SOCK_DGRAM (UDP Datagram socket) or SOCK_STREAM ( TCP Socket) or SOCK_RAW (Raw Socket).
  • The third parameter is the protocol that is used for communication which is usually determined by domain and type . Hence it is passed 0.
  • On success it return socket Descriptor which is used for other socket API(bind, connect,read, write,close) and 0 on failure.
  • One reason for failure can be exceeding the maximum number of socket descriptor allowed in the system.

Sample code:

int sock_fd = socket (AF_INET, SOCK_STREAM, 0);
if (sock_fd ==0){
    printf("\n Socket creation failed %d %s \n",errno,strerror(errno));
    exit(EXIT_FAILURE);
} 
printf("\n Socket creation success sock id : %d\n", sock_fd);

Naming a Socket:

int bind (int sock_fd, const struct sockaddr *addr, socklen_t 
          addrlen);
  • Naming a socket is nothing but binding the above created socket with IP address and port number.
  • The other process can use the socket once it is named.
  • The first parameter is the socket descriptor returned from socket() API.
  • The second parameter is the pointer to structure which holds port number, family and IP address.
  • The third parameter is the size of the structure.
  • On successful completion, it returns 0 and -1 on failure and sets errno.

Sample Code

    char *ip = "127.0.0.1";
    struct sockaddr_in addr;
    bzero(&addr,sizeof(addr));
    
    addr.sin_family = AF_INET;
    addr.sin_port = htons(1234);
    inet_aton(ip, &addr.sin_addr);
    int ret = bind (sock_fd, (struct sockaddr *)&addr, 
                    sizeof(struct sockaddr_in));
    if (ret == -1) {
       printf("\n Socket bind failed %d %s \n",errno,strerror(errno));
       exit(EXIT_FAILURE);
    }
    printf("\n Socket bind successful\n");

Note:

  • Generally, bind() function is not used on the client-side as a temporary port number is automatically assigned.
  • It becomes necessary when an application needs to connect(compulsory) with a specific port because of any firewall issues or security issues or connects to a specific interface on specific port, on machine having two interfaces


Request for a connections

int connect (int sock_fd,const struct sockaddr *dest_addr, socklen_t 
             addrlen);
  • The connect() API is used for initiating a connection with the server.
  • It connects the socket referred to by the file descriptor sock_fd to the server address specified by dest_addr.
  • The addrlen argument specifies the size of dest_addr.
  • It returns 0 on success and -1 on errors.

Note:

  • Generally, connect() API is used with SOCK_STREAM(TCP) to initiate a connection on a socket specified by addr.
  • But can also be used with the socket of type SOCK_DGRAM then addr is the address to which datagram is sent by default, and the only address from which datagram is received that is exactly one peer.
  • There are few applications where the UDP server communicates with a single client for a long time(e.g, TFTP); in this case, both client and server call connect() API.
  • Generally, connection-oriented protocol sockets may successfully connect() only once while connectionless protocol sockets may use connect() multiple times.
  • connect() API is a blocking call that is if a connection cannot be set up immediately, connect will block for an unspecified timeout period. Once this timeout has expired, the connection will be aborted, and connect will fail.
  • However, connect() API can be made non-blocking by setting the O_NONBLOCK flag on the file descriptor using fcntl() API.

Sample Code:

/* Making the socket Non-blocking(if required) */
 int flags = fcntl (sock_fd, F_GETFL, 0);
 fcntl (sock_fd, O_NONBLOCK|flags);
 
 ret = connect(sock_fd, (struct sockaddr *)&dest_addr, sizeof(struct 
              sockaddr_in));
 if (ret == -1) {
     printf("\n Socket connect failed %d %s \n",errno,strerror(errno));
     exit(EXIT_FAILURE);
 }
 printf("\n Socket connect successful\n");

Sending Data:
The data can be transferred to a socket by using either send() or sendto() API.
send() API:

int send (int sock_fd, const void *buf, size_t len, int flags)
  • The send() API can be used for sending data across sockets.
  • The first parameter is the socket descriptor returned from socket() API.
  • The second parameter is the pointer to buffer having a message to send and the third is the size of the message.
  • The flag is used for controlling the operation that is how the data is sent. The most common flag is MSG_DONTWAIT and MSG_DONTROUTE.
    • MSG_DONTWAIT makes it a non-blocking call that is to return immediately if there is no data to send or outbound traffic is clogged.
    • MSG_DONTROUTE makes sure that data is not over a router, just keep it local.
  • On success, it returns a number of bytes send and -1 in case of failure.

sendto() API:

int sendto (int sock_fd, const void *buf, size_t len, int flags,  const 
            const struct sockaddr *dest_addr, socklen_t dest_len);
  • Generally, send() API is used with the socket of type SOCK_STREAM(TCP) and sendto() API is used with the socket of type SOCK_DGRAM(UDP).
  • But even sendto() API can be used to transfer data across sockets for TCP socket.
  • dest_addr points to sockaddr structure containing the destination address and dest_len specify the length of the sockaddr structure.
  • If sendto() to be used with socket of type SOCK_STREAM, the argument addr and its len are ignored.
  • Even sendto() can be made non-blocking by using flag MSG_DONTWAIT and MSG_DONTROUTE can be used..
  • On success, it returns a number of bytes send and -1 in case of failure.

Sample Code:

/* Build Destination */
char *msg ="Hello World";
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(1239);
inet_aton("127.0.0.1", &dest_addr.sin_addr);

/* Socket of type SOCK_STREAM */
connect (sock_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr));  
send (sock_fd , msg, strlen(msg) , 0 );
sendto (sock_fd , msg, strlen(msg) , 0, 0, 0 );

Receiving Data:
The message can be received from a socket either by using recv() and revfrom() API.
recv() API:

int recv (int sock_fd, void * buf, size_t len, int flags);
  • The message can be received from a socket by calling recv() API.
  • The first parameter is the socket descriptor returned from the socket() API.
  • The second parameter is the pointer to buffer to keep read message and the third is the size of the message.
  • The flag is used for controlling how the data is received. The most common flag is MSG_DONTWAIT which makes recv() API non-blocking that is to return immediately if there is no data to read.
  • On success, it returns a number of bytes recv and -1 in case of failure.

recvfrom():

int recvfrom (int sock_fd, void *buf, size_t len, int flags, const 
              struct sockaddr *from_addr, socklen_t from_len);
  • The recvfrom() system call receives a message from a socket and captures the address from which the data was sent.
  • Generally recv() API is used with socket of type SOCK_STREAM(TCP) while recvfrom() API is used for a socket of type SOCK_DGRAM().
  • But even recvfrom() API can be used to transfer data across sockets for TCP type.
  • If recvfrom() to be used with socket of type SOCK_STREAM, the argument from_addr and its len are ignored.
  • If from_addr is non zero and its a UDP socket, the structure from_addr is filled in with client information like its family, port, IP address.
  • The value returned from_len is initialized to the size of this structure and is modified on return to indicate the actual size of the peer from_addr
  • Even recvfrom() can be made non blocking by using flag MSG_DONTWAIT.
  • On success it returns number of bytes received and -1 in case of failure.

Sample Code:

    /* Socket of type SOCK_STREAM(TCP) */
    valread = recv (sock_fd , buffer, 1024, 0);
    valread = recvfrom (sock_fd , buffer, 1024, 0, 0, 0);             

Closing the Connection:

int close(int sock_fd);
  • Once the data is send to and receive from a socket, it need to be closed.
  • This is done by calling close() API on socket descriptor.
  • sock_fd is file descriptor( socket being closed).
  • It closes a connection(for stream socket), frees up the port used by socket.
  • It returns 0 on success and -1 on failure.
  • Close() API should be used on both client and server to release the file descriptor to be reused.

Listening for the new connection:

int listen (int sock_fd, int backlog);
  • To accept incoming connections on a socket, a server program must create a queue to store pending requests from clients. This is done by using listen() API.
  • The first parameter is the socket descriptor.
  • A Linux system may limit the maximum length of the queue which is the maximum number of pending connections placed in a queue. This is done with the help of a second parameter backlog.
  • Incoming connections up to this queue length are held pending on the socket; further connections will be refused and client connections will fail.
  • This mechanism is provided by listen() API to allow incoming connections to be held pending while the server is busy dealing with the previous client.
  • It is a non-blocking call: returns immediately.
  • A value of 5 for the backlog is very common.
  • It returns 0 on success and -1 on error.

Accepting the Connection:

int sock_fd_server = accept (int sock_fd, struct sockaddr *cli_addr, 
                             addrlen_t cli_addrlen);
  • The server gets a socket for an incoming client connection by calling accept()
  • The accept() API will pick the first connection request from the queue.
  • The address of the calling client must be placed in second parameter that is sockaddr structure pointed by cli_addr upon return.
  • The third parameter is the sizeof(cli_addr) which must be set appropriately before the call and are updated to actual value on return.
  • The accept() system call creates a new socket to communicate with the client and returns its socket descriptor.
  • This is a blocking call that is if there is no connection request in the queue, accept will block until the client does make a connection.
  • This can be made non-blocking by using the O_NONBLOCK flag on the socket file descriptor using fcntl() API.
  • It returns a new socket descriptor if there is a client connection pending or -1 on error.
  • A new socket descriptor will be used for sending and receiving data from clients.

Sample Code

struct sockaddr_in addr;
struct sockaddr_in cli_addr;
bzero(&addr,sizeof(addr));
bzero(&cli_addr,sizeof(cli_addr));
char buffer[1024] = {0};
int cli_addr_len = sizeof(cli_addr);

int server_fd = socket (AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(4567);

bind(server_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
listen(server_fd, 3);

int new_socket = accept(server_fd, (struct sockaddr *)&cli_addr, 
                       (socklen_t*)&cli_addr_len);
                 

Summation:
The following set of APIs are used un TCP socket programming

TCP Client : socket() => connect() => send() => recv() => close()
TCP Server : socket() => bind() => listen() =>accept() => recv() => send() => close()

UDP Socket Program:
The following set of APIs are used in UDP socket programming

UDP Client : socket() => bind() => sendto() => recvfrom() => close()
UDP Server : socket() => bind() => recvfrom() => sendto () => close()

TCP Socket Client Program:

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int sock_fd = 0, valread;
    struct sockaddr_in serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    char *msg = "Hello from client";
    char buffer[1024] = {0};

    if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Socket creation error %d %s \n", errno, strerror(errno));
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    unsigned short int port = atoi(argv[1]);
    serv_addr.sin_port = htons(port);
    if (inet_aton ("127.0.0.1", &serv_addr.sin_addr) == 0)
    {
        printf("\n Invalid IP address\n");
        return -1;
    }

    if (connect (sock_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("\n Socket Connection Failed %d %s \n",errno,strerror(errno));
        return -1;
    }
     send (sock_fd, msg, strlen(msg), 0 );
     printf("\n Message send to server is: %s\n", msg);
     valread = recv (sock_fd, buffer, 1024, 0);
     printf("\n Server Message is: %s\n",buffer );
     
     /* sendto() and recvfrom() can also be used
     sendto(sock_fd , msg , strlen(msg) , 0 , 0, 0);
     valread = recvfrom (sock_fd, buffer, 1024, 0, 0, 0);
 
     sendto(sock_fd , msg , strlen(msg) , 0 , (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in));
     valread = recvfrom( sock_fd, buffer, 1024, 0, (struct sockaddr *)&serv_addr, (socklen_t *)sizeof(struct sockaddr_in));
 
     printf("\n Message send to server is %s\n", msg);
     printf("\n Server Message is %s\n", buffer);
     */
     close(sock_fd);
     return 0;
 }

TCP socket Server Program:

#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    int server_fd, new_socket, valread;
    struct sockaddr_in serv_addr;
    bzero (&serv_addr, sizeof(serv_addr));
    char buffer[1024] = {0};
    char *hello = "Hello from server";


    struct sockaddr_in cli_addr;
    bzero (&cli_addr, sizeof(cli_addr));
    int cli_addr_len = sizeof(cli_addr);

    /* Creating socket file descriptor */
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
    {
        printf("\n Socket creation error %d %s \n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }

    unsigned short int port = atoi(argv[1]);
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons( port );
    if (bind(server_fd, (struct sockaddr *)&serv_addr,
                                 sizeof(serv_addr))<0)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    if (listen(server_fd, 3) < 0)
    {
        printf("\n Socket creation error %d %s \n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }

    /* Getting a new socket for an incoming client and filling in cli_add structure on return */
    if ((new_socket = accept(server_fd, (struct sockaddr *)&cli_addr,
                            (socklen_t*)&cli_addr_len))<0)
    {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    valread = recv (new_socket, buffer, 1024, 0);
    printf("\n Message received from client is: %s\n",buffer );

    send(new_socket , hello , strlen(hello) , 0 );
    printf("\n Message send to client is: %s\n", hello);

    /* cli_addr is filled upon return of accept() with client IP and length, port */
    printf ("\n Client IP address is: %s\n",inet_ntoa(cli_addr.sin_addr));
    printf ("\n Client cli_addr_len is: %d\n", ntohs(cli_addr.sin_port));
    printf ("\n Client Socket Family is: %d\n", cli_addr.sin_family);


    /* recvfrom() and sendto() API can also be used
    valread = recvfrom (new_socket , buffer, 1024, 0, 0, 0);
    sendto(new_socket , hello , strlen(hello) , 0, 0 ,0 );

    valread = recvfrom (new_socket, buffer, 1024, 0, (struct sockaddr *)&cli_addr,
                       (socklen_t *)&cli_addr_len);
    sendto(new_socket , hello , strlen(hello) , 0, 0 ,0 );

    printf("Message received from client is: %s\n",buffer );
    printf("\n Message send to client is %s\n", hello);
    */

     close (new_socket);
     close (server_fd);
     return 0;
 }

Output:
Server:

[aprakash@wtl-lview-6 socket]$ gcc tcp_ser.c -o ser
[aprakash@wtl-lview-6 socket]$ gcc tcp_cli.c -o cli
[aprakash@wtl-lview-6 socket]$ ./ser 1234

 Message received from client is: Hello from client

 Message send to client is: Hello from server

 Client IP address is: 127.0.0.1

 Client cli_addr_len is: 60104
 Client Socket Family is: 2

Client:

[aprakash@wtl-lview-6 socket]$ gcc tcp_cli.c -o cli

[aprakash@wtl-lview-6 socket]$ ./cli 1234
 Message send to server is: Hello from client
 Server Message is: Hello from server

UDP Socket Client Program

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>

int main(int argc, char const *argv[])
{
    int sock = 0, valread;
    struct sockaddr_in serv_addr;
    char *msg = "Hello from client";
    char buffer[1024] = {0};
    memset(&serv_addr, 0, sizeof(serv_addr));
    /* Equivallent */
    //bzero(&serv_addr, sizeof(serv_addr));

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        printf("\n Socket creation error %d %s \n", errno, strerror(errno));
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    unsigned short int port = atoi(argv[1]);
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    /*
    if (inet_aton("127.0.0.1", &serv_addr.sin_addr) == 0)
    {
        printf("\n Invalid IP address\n");
        return -1;
    }
    int len;
    sendto(sock, msg, strlen(msg), 0, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in));
    printf("\n Message send to server is: %s\n", msg);

    valread = recvfrom(sock , buffer, 1024, 0, (struct sockaddr *)&serv_addr,(socklen_t *)&len);                           
    buffer[valread] = '\0';
    printf("\n Server responce is: %s\n",buffer);
    close(sock);

    /* send() and recv() can be used only when connect() is used
     * but on the server side, send() and recv() cannot be used as there is 
     * no client address to send data to and recv data from */
    /*
    connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
    send(sock, msg, strlen(msg), MSG_CONFIRM);
    valread = recv( sock , buffer, 1024, MSG_WAITALL);
    */

    return 0;
}

UDP socket Server Program:

#include <unistd.h>
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int server_fd, valread;
    struct sockaddr_in address, cli;
    bzero(&address,sizeof(address));
    bzero(&cli,sizeof(cli));

    char buffer[1024] = {0};
    char *hello = "Hello from server";

    if ((server_fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0)
    {
        printf("\n Socket creation error %d %s \n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    unsigned short int port = atoi(argv[1]);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons( port );
    /*
    if (inet_aton("127.0.0.1", &address.sin_addr) == 0)
    {
       printf("\n Invalid IP address\n");
       return -1;
    }
    */

     if (bind(server_fd, (struct sockaddr *)&address,
                                  sizeof(address))<0)
     {
         perror("bind failed");
         exit(EXIT_FAILURE);
     }
     int len;
     valread = recvfrom( server_fd, buffer, 1024, 0, (struct sockaddr *)&cli, (socklen_t *)&len);
     buffer[valread] = '\0';
     printf("\n Message received from client is:%s\n",buffer );
 
     sendto(server_fd, hello, strlen(hello), 0, (struct sockaddr *)&cli, len );
     printf("\n Message sent to client is: %s\n",hello);
     return 0;
 }

Output:
Server:

[aprakash@wtl-lview-6 socket]$ gcc tcp_ser.c -o ser

[aprakash@wtl-lview-6 socket]$ ./ser 1234
Message received from client is: Hello from client

Message send to client is: Hello from server

Client:

[aprakash@wtl-lview-6 socket]$gcc tcp_cli.c -o cli

[aprakash@wtl-lview-6 socket]$ ./cli 1234

Message send to server is: Hello from client
Server Message is: Hello from server

Blocking Calls
Many of the socket APIs block( by default) until a certain event:

  • connect: until the connection is established.
  • accept: until a connection comes in.
  • recv, recvfrom: until a packet(data) is received, packet may get lost in UDP socket.
  • send: until data are pushed into socket’s buffer.
  • sendto: until the data are given to network system.

The solution to blocking calls:
There are three solutions to blocking calls:

  • Non-blocking socket
  • By setting flag
  • Timeout

Non-blocking socket:
Make the socket non-blocking by use of O_NONBLOCK using fcntl() API.

int flags = fcntl (sock_fd, F_GETFL, 0);
fcntl (sock_fd, O_NONBLOCK|flags);

By setting flags:
Flag argument of send(), sendto(), recv() and recvfrom() can be set to MSG_DONTWAIT.

Timeout:
select() API can be used with timeout option to return once the time elapses.

Tricky Interview Questions:
1. Can connect() API be used with a UDP socket?
Answer: Yes, for details please refer to connect() API above.

2. Can listen() and accept() be used with a UDP socket?
Answer: No, Operation not permitted.

3. Can sendto() and recvfrom() API be used with TCP socket?
Answer: Yes, for details please refer sendto() and recvfrom() API above.

4. Can send() and recv() API be used with the UDP socket?
Answer: Yes, can be used with connect() but only on the client side.

5. Is bind() API mandatory on the client-side?
Answer: No, for details refer to bind() API above.

6. Is it possible that a mix of send() and recvfrom() be used or another similar combination?
Answer: Yes

Relevant Posts:

References:



Categories: Operating system (OS)

3 replies

Trackbacks

  1. Index of Operating System - Tech Access
  2. Socket Options - Tech Access
  3. Address Manipulation:inet_ntoa, inet_aton, inet_addr - Tech Access

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: