Linux sockets: Difference between revisions

From miki
Jump to navigation Jump to search
Line 63: Line 63:
</source>
</source>


Changes:
;Changes
* Set socket option <code>SO_REUSEADDR</code> to allow reconnect to same port while previous server instance is closing the connection (socket in TIME_WAIT state).
* Set socket option <code>SO_REUSEADDR</code> to allow reconnect to same port while previous server instance is closing the connection (socket in TIME_WAIT state).
* Use <code>send</code> instead of <code>write</code> to avoid SIGPIPE signal if clients closed the socket.
* Use <code>send</code> instead of <code>write</code> to avoid SIGPIPE signal if clients closed the socket.

Revision as of 07:49, 28 September 2018

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 and tutorials

Some examples or tutorials on the internet:

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

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

C client

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

To use this code:

gcc client.c -o server
./client
// client - Sample code from https://www.binarytides.com/socket-programming-c-linux-tutorial/

#include <stdio.h>
#include <string.h>                                                  // strlen
#include <sys/socket.h>
#include <arpa/inet.h>                                               // inet_addr

int main(int argc, char *argv[])
{
    int                 socket_desc;
    struct sockaddr_in  server;
    char *              message, server_reply[2000];

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

    server.sin_addr.s_addr = inet_addr("216.58.206.4");
    server.sin_family      = AF_INET;
    server.sin_port        = htons(80);

    // Connect to remote server
    if ( connect(socket_desc, (struct sockaddr *)&server, sizeof(server)) < 0 ) {
        puts("connect error");
        return 1;
    }

    puts("Connected\n");

    // Send some data
    message = "GET / HTTP/1.1\r\n\r\n";
    if ( send(socket_desc, message, strlen(message), 0) < 0 ) {
        puts("Send failed");
        return 1;
    }
    puts("Data Send\n");

    // Receive a reply from the server
    if ( recv(socket_desc, server_reply, 2000, 0) < 0 ) {
        puts("recv failed");
    }
    puts("Reply received\n");
    puts(server_reply);

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