View on GitHub

Computer Architecture and Operating Systems

Course taught at Faculty of Computer Science of Higher School of Economics

Lecture 13

Sockets

Lecture

Slides (PDF, PPTX).

Sockets

Socket

An abstraction for asynchronous data transfer:

Socket Disciplines

Stream

An ordered series of packets of reliable data:

Datagram

A single message:

Socket Programming

Probably it will be better to read the following text in parallel with trying the examples below.

To initialize a socket

  1. Create a socket(domain, type, 0)
    • domain is underlying layer type (various networks, filesystem etc), aka address family
    • type is discipline (stream, datagram or others)

To run a server

  1. Associate the socket with the specific address/location via bind(socket, address, length)
    • socket is the descriptor of the socket
    • address is domain-specific struct filled with actual address of the server
    • length is the address structure size
    • Because of various address families has different address size, we need to provide it
    • For the same reason, we need to cast actual structure type to const struct sockaddr *, which is merely a placeholder
  2. Start to listen(socket, queue_length)
    • if the number of unreceived streams/datagrams is equal to queue_length
    • all other stream connections are refused (sender got an error message)
    • all other datagrams are dropped (sender got nothing)
  3. If the socket supports connections (e.g. stream type socket),
    • socket given by listen() is control socket, used to accept connections
    • got a new connection data socket descriptor returned by accept(socket, address, &length)
      • address and length are filled with peer address and its address length (if we do not need them, we can use both NULL here)
    • Use data socket to receive data
  4. Receive a portion of data from data socket to buffer via recv(data_socket, buffer, length, 0)
  5. Do not forget to close() data the sockets after transmission is done
    • Also, close control socket before finishing a service. Not closing TCP stream control makes its port unavailable for further use for next couple of minutes.

To run a client

  1. Associate the socket with the specific remote server address/location via connect(socket, address, length) (see above for arguments explanation)
  2. Send data to this server via send(socket, buffer, length, 0)
  3. Do not forget to close() sockets after transmission is done

Datagram socket

When using a datagram socket, we can use sendto(socket, buffer, length, 0, address, &length) to send a single datagram instead of connect() and then send(). No connection is established anyway, and connect() here serves only informational purpose.

When using stream socket, we can use read(socket, buffer, length) as well.

Examples

Unix domain + datagram

Unix domain datagram server

Unix domain datagram server, that receives only one datagram, dumps it in hexadecimal, and exits. First argument is Unix domain socket name.

unix_d_send.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define USIZE sizeof(struct sockaddr_un)

int main(int argc, char *argv[]) {
    struct sockaddr_un srv;
    int fd;

    fd = socket(AF_UNIX, SOCK_DGRAM, 0);
    srv.sun_family = AF_UNIX;
    strncpy(srv.sun_path, argv[1], sizeof(srv.sun_path)-1);

    bind(fd, (const struct sockaddr *) &srv, USIZE);
    sendto(fd, argv[2], strlen(argv[2]), 0, (const struct sockaddr *) &srv, USIZE);

    return 0;
}

Unix domain datagram client

Unix domain datagram sender, first argument is socket, second argument is string to send.

unix_d_server.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
 
#define USIZE sizeof(struct sockaddr_un)
#define BLOG 3
#define DSIZE 16

int main(int argc, char *argv[]) {
    struct sockaddr_un srv;
    char dgram[DSIZE];
    int fd, rsz, i;

    fd = socket(AF_UNIX, SOCK_DGRAM, 0);
    srv.sun_family = AF_UNIX;
    strncpy(srv.sun_path, argv[1], sizeof(srv.sun_path)-1);

    remove(argv[1]);
    bind(fd, (const struct sockaddr *) &srv, USIZE);
    listen(fd, BLOG);

    rsz = recv(fd, dgram, DSIZE, 0);
    for(i=0; i<rsz; i++)
        printf("%02x ", dgram[i]);
    putchar('\n');

    remove(argv[1]);
    return 0;
}

How this works together

acos@acos-vm:~/unixsocket$ gcc unix_d_send.c -o unix_d_send
acos@acos-vm:~/unixsocket$ gcc unix_d_server.c -o unix_d_server
acos@acos-vm:~/unixsocket$ ./unix_d_server u_socket &
[1] 71717
acos@acos-vm:~/unixsocket$ ls -l
total 48
srwxrwxr-x 1 acos acos     0 июн 11 01:08 u_socket
-rwxrwxr-x 1 acos acos 16928 июн 11 01:08 unix_d_send
-rw-rw-r-- 1 acos acos   492 июн 11 01:07 unix_d_send.c
-rwxrwxr-x 1 acos acos 17064 июн 11 01:08 unix_d_server
-rw-rw-r-- 1 acos acos   652 июн 11 01:07 unix_d_server.c
acos@acos-vm:~/unixsocket$ ./unix_d_send u_socket Message
4d 65 73 73 61 67 65 
[1]+  Done                    ./unix_d_server u_socket

Internet (IPv4) + stream (TCP)

Simple TCP server

Internet IPv4 domain stream socket (TCP) server that accepts connections and sends back a number of connection. First argument is IPv4 address, second one is port to listen to.

tcp_qq_srver.c:

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

#define ISIZE (sizeof(struct sockaddr_in))
#define MAXCONN 3
#define BUFSIZE 32

int main(int argc, char *argv[]) {
    int fd, connfd, conncount=0;
    struct sockaddr_in srv;
    char buf[32];

    memset(&srv, 0, ISIZE);
    srv.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &(srv.sin_addr));
    srv.sin_port = htons(atoi(argv[2]));

    fd = socket(AF_INET, SOCK_STREAM, 0);
    bind(fd, (struct sockaddr*) &srv, ISIZE);
    listen(fd, MAXCONN);

    while(1) {
        connfd = accept(fd, NULL, NULL);
        snprintf(buf, BUFSIZE, "Connection %d!\n", ++conncount);
        write(connfd, buf, strlen(buf));
        close(connfd);
    }
    return 0;
}

Some explanation:

Warning:

This program never closes its control socket. After killing the program, we have to wait a pair of minutes, until OS decides to purge unused socket structure down.

Simple TCP client

Simple TCP client that connects to a server and repeatedly receives a message from the server and prints it to standard output then reads a message from standard input and sends it to the server.

tcp_client.c:

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

#define ISIZE (sizeof(struct sockaddr_in))
#define MAXCONN 3
#define BUFSIZE 32

int main(int argc, char *argv[]) {
    int fd, sz;
    struct sockaddr_in srv;
    char buf[32];

    memset(&srv, 0, ISIZE);
    srv.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &(srv.sin_addr));
    srv.sin_port = htons(atoi(argv[2]));

    fd = socket(AF_INET, SOCK_STREAM, 0);
    connect(fd, (struct sockaddr*) &srv, ISIZE);

    do {
        fgets(buf, BUFSIZE, stdin);
        if(buf[0]!='\n')
            write(fd, buf, strlen(buf));
        sz = read(fd, buf, BUFSIZE);
        if(sz>0) printf("%s\n", buf);
    } while(sz);
    return 0;
}

Some explanation:

How it works

acos@acos-vm:~/inetsocket$ gcc tcp_client.c -o tcp_client
acos@acos-vm:~/inetsocket$ gcc tcp_qq_srver.c -o tcp_qq_srver
acos@acos-vm:~/inetsocket$ ./tcp_qq_srver 127.0.0.1 1213 &
[1] 71838
acos@acos-vm:~/inetsocket$ ./tcp_client 127.0.0.1 1213

Connection 1!

acos@acos-vm:~/inetsocket$ ./tcp_client 127.0.0.1 1213
dddd
Connection 2!

acos@acos-vm:~/inetsocket$ ./tcp_client 127.0.0.1 1213

Connection 3!

Explanation:

TCP echo server

Simple TCP server that accepts one connection at the time, receives a message and sends it back.

tcp_echo_serverSR.c:

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

#define ISIZE (sizeof(struct sockaddr_in))
#define MAXCONN 3
#define BUFSIZE 32

int main(int argc, char *argv[]) {
    int fd, connfd, sz, port;
    struct sockaddr_in srv, peer;
    char buf[32];
    char addr[INET_ADDRSTRLEN+1];
    unsigned peersz=ISIZE;

    memset(&srv, 0, ISIZE);
    srv.sin_family = AF_INET;
    inet_pton(AF_INET, argv[1], &(srv.sin_addr));
    srv.sin_port = htons(atoi(argv[2]));

    fd = socket(AF_INET, SOCK_STREAM, 0);

    bind(fd, (struct sockaddr*) &srv, ISIZE);
    listen(fd, MAXCONN);

    while(1) {
        connfd = accept(fd, (struct sockaddr *) &peer, &peersz);
        sz = recv(connfd, buf, BUFSIZE, 0);
        inet_ntop(AF_INET, &peer.sin_addr, addr, INET_ADDRSTRLEN);
        port = ntohs(peer.sin_port);
        printf("Received %d bytes from %s, port %d\n", sz, addr, port);
        send(connfd, buf, sz, 0);
        close(connfd);
    }
    return 0;
}

Explanation:

Workshop

Socket API

Socket system calls

Common

Client

Server

Send/Receive

Homework

TODO

References