Linux sockets

From miki
Revision as of 07:42, 28 September 2018 by Mip (talk | contribs) (→‎References)
Jump to navigation Jump to search

Tutorials

A thoough step-by-step example of client and socket code.
Simple example of client and server code using Linux sockets.

References

A very detailed answer about the differences between SO_REUSEADDR and SO_REUSEPORT.
A course on internet technologies, including Domain Naming (DNS...), the IP protocol, the TCP protocol, ...
  • Manpages:

API

C

read(2) - Read from a file descriptor
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
  • Mostly equivalent to recv except there is no specification for flags (e.g. to change blocking/non-blocking behaviour).
recv(2), recvfrom(2), recmmsg(2) - Receive a message from a socket
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
write(2) - Write to a file descriptor
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
  • The main difference with send is that when wrote returns EPIPE, the writing process will also receive a SIGPIPE signal, terminating it unless it catches it or ignore it.
send(2), sendto(2), sendmsg(2) - Send a message on a socket
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  • The main difference with write is the presence of flags, which among other can configured to prevent the generation of SIGPIPE signal.

Examples

C server

The following example comes from https://www.binarytides.com/socket-programming-c-linux-tutorial/.

To use this code:

gcc server.c -lpthread -o server
./server
# In another window:
telnet localhost 8888

Changes:

  • Set socket option SO_REUSEADDR to allow reconnect to same port while previous server instance is closing the connection (socket in TIME_WAIT state).
  • Use send instead of write to avoid SIGPIPE signal if clients closed the socket.
  • Don't use strlen on received message because there is no guarantee of terminating zero. Instead use read_size.
  • Test error code of send / write.
// Server - Example code from https://www.binarytides.com/socket-programming-c-linux-tutorial/
//
// Example of use:
//
// * In one window:
// |
// |   ./server
// |
// * In another window:
// |
// |   telnet localhost 8888
// |
// | Then type anything.

#include <stdio.h>
#include <string.h>                                                  // strlen
#include <stdlib.h>                                                  // strlen
#include <sys/socket.h>
#include <arpa/inet.h>                                               // inet_addr
#include <unistd.h>                                                  // write
#include <errno.h>

#include <pthread.h>                                                 // for threading , link with lpthread

void * connection_handler(void *);

int main(int argc, char *argv[])
{
    int                 sockfd, new_socket, c, *new_sock;
    struct sockaddr_in  server, client;
    char *              message;

    // Create socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if ( sockfd == -1 ) {
        printf("Could not create socket");
        return 1;
    }

    // Allow address/port reuse in a case server is killed (socket in TIME_WAIT state)
    if ( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1 }, sizeof(int)) < 0 ) {
        printf("setsockopt(SO_REUSEADDR) failed");
        return 1;
    }

    // Prepare the sockaddr_in structure
    server.sin_family      = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port        = htons(8888);

    // Bind
    if ( bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0 ) {
        printf("bind failed: %s (errno %d)", strerror(errno), errno);
        return 1;
    }
    puts("bind done");

    // Listen
    listen(sockfd, 3);

    // Accept and incoming connection
    puts("Waiting for incoming connections...");
    c = sizeof(struct sockaddr_in);
    while ((new_socket = accept(sockfd, (struct sockaddr *)&client, (socklen_t *)&c))) {
        puts("Connection accepted");

        // Reply to the client
        message   = "Hello Client , I have received your connection. And now I will assign a handler for you\n";
        if ( send(new_socket, message, strlen(message), MSG_NOSIGNAL) < 0 ) {
            perror("send failed");
        }

        pthread_t  sniffer_thread;
        new_sock  = malloc(1);
        *new_sock = new_socket;

        if ( pthread_create(&sniffer_thread, NULL, connection_handler, (void *)new_sock) < 0 ) {
            perror("could not create thread");
            return 1;
        }

        puts("Handler assigned");
    }

    // TODO: Join the running threads, so that we don't terminate before them
    // for( int i=0; ...; ... ) pthread_join( sniffer_thread[i] , NULL);

    if ( new_socket < 0 ) {
        perror("accept failed");
        return 1;
    }

    return 0;
}

/*
 * This will handle connection for each client
 * */
void * connection_handler(void *sockfd)
{
    // Get the socket descriptor
    int   sock = *(int *)sockfd;
    int   read_size;
    char *message, client_message[2048];

    // Send some messages to the client
    message = "Greetings! I am your connection handler\n";
    if ( send(sock, message, strlen(message), MSG_NOSIGNAL) < 0 ) {
        perror("send failed");
    }

    message = "Now type something and i shall repeat what you type \n";
    if ( send(sock, message, strlen(message), MSG_NOSIGNAL) < 0 ) {
        perror("send failed");
    }

    // Receive a message from client
    while ((read_size = recv(sock, client_message, sizeof(client_message), 0)) > 0 ) {
        // Output received message (assuming zero-terminated)
        write(STDOUT_FILENO, client_message, read_size);

        // Send the message back to client
        send(sock, client_message, read_size, MSG_NOSIGNAL);
    }

    if ( read_size == 0 ) {
        puts("Client disconnected");
        fflush(stdout);
    } else if ( read_size == -1 ) {
        perror("recv failed");
    }

    // Free the socket pointer
    free(sockfd);

    return 0;
}

Troubleshooting

Use errno / perror / strerror to get error

if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
{
    printf("bind failed: %s (errno %d)", strerror(errno), errno);
    return 1;
}

Or there is also the perror function:

if( bind(socket_desc,(struct sockaddr *)&server , sizeof(server)) < 0)
{
    perror("bind failed");
    return 1;
}

Use netstat to get socket status

./server
# bind done
# Waiting for incoming connections...
# Connection accepted
# Handler assigned
# ^C

./server
# bind failed: Address already in use (errno 98)

netstat -a | grep 8888
# tcp        0      0 zavcxl0005:58888        165.225.76.32:http      ESTABLISHED
# tcp        0      0 zavcxl0005:48888        10.75.126.1:https       ESTABLISHED
# tcp        0      0 localhost.localdom:8888 localhost.localdo:50310 TIME_WAIT  

# ... So port is still in use

bind failed: Address already in use (errno 98)

Call to bind fails with error

bind failed: Address already in use (errno 98)

On a server, this is probably due to an old instance of the server that tries to bind to the same port and address [1].

As a fix, try to bind with option SO_REUSEADDR and/or SO_REUSEPORT. From SO:

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) < 0)
    error("setsockopt(SO_REUSEADDR) failed");


For more information, see socket(7) and ip(7) manpages.

Program crashes on write with exit code -13

Exit code -13 means the program died because of SIGPIPE signal, meaning it tried to write to a closed socket.

Solutions:

  • Ignore SIGPIPE in the program [2]:
signal (SIGPIPE, SIG_IGN);
  • Use send instead of write. Send returns -1 and set errno to EPIPE rather than generating a fatal SIGPIPE like write does [3]:
n = send(newsockfd, data.c_str(), data.length()+1, MSG_NOSIGNAL);