02. Sockets

Socket
An abstraction for asynchronous data transfer:
  • Has two ends
  • Can be bidirectional
  • Can be organized over a variety of underlying layers
    • network; e. g. TCP, UDP etc. for IPv4 (internet socket)

    • special filesystem object; e. g. so called alled unix domain socket

  • see socket

Socket disciplines:

Stream
an ordered series of packets of reliable data
  • No transfer without connection is established
  • Data transferred is reliably equal to data received (including network packets corruption/loss/duplication correction)
  • Out-of-band state are eliminated (sender can not send more than reciever can receive)
Datagram
a single message
  • no need to establish a connection
  • when sending over a network, no need to order and count packets (because there's only one)

Socket programming

Probably it'll be better to read the following text in parallel with trying the examples down here

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 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 it's address length (if we don't 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 it's 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) ti 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.

   1 #include <stdio.h>
   2 #include <sys/socket.h>
   3 #include <sys/un.h>
   4 #include <stdlib.h>
   5 #include <string.h>
   6 
   7 #define USIZE (sizeof(struct sockaddr_un))
   8 #define DSIZE 16
   9 #define MAXCONN 3
  10 
  11 int main(int argc, char *argv[]) {
  12 	struct sockaddr_un srv;
  13 	int i, fd, rlen;
  14 	char dgram[DSIZE];
  15 
  16 	memset(&srv, 0, USIZE);
  17 
  18 	fd = socket(AF_UNIX, SOCK_DGRAM, 0); 
  19 
  20 	strncpy(srv.sun_path, argv[1], sizeof(srv.sun_path)-1);
  21 	srv.sun_family = AF_UNIX;
  22 
  23 	remove(argv[1]);
  24 	bind(fd, (struct sockaddr *) &srv, USIZE);
  25 	listen(fd, MAXCONN);
  26 
  27 	rlen = recv(fd, dgram, DSIZE, 0);
  28 	for(i=0; i<rlen; i++) printf("%02x ", dgram[i]);
  29 	putchar('\n');
  30 
  31 	remove(argv[1]);
  32 	return 0;
  33 }
unix_d_server.c

Unix domain datagram client

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

   1 #include <stdio.h>
   2 #include <sys/un.h>
   3 #include <sys/socket.h>
   4 #include <ctype.h>
   5 
   6 #define USIZE (sizeof(struct sockaddr_un))
   7 
   8 int main(int argc, char *argv[]) {
   9 	struct sockaddr_un srv;
  10 	int fd;
  11 
  12 	memset(&srv, 0, USIZE);
  13 
  14 	fd = socket(AF_UNIX, SOCK_DGRAM, 0);
  15 
  16 	strncpy(srv.sun_path, argv[1], sizeof(srv.sun_path)-1);
  17 	srv.sun_family = AF_UNIX;
  18 
  19 	sendto(fd, argv[2], strlen(argv[2]), 0, (struct sockaddr *) &srv, USIZE);
  20 
  21 	return 0;
  22 }
unix_d_sendO.c

How this works together

Server

Client

02_Sockets$ ./unix_d_server u_socket







4d 65 73 73 61 67 65

02_Sockets$ ls
Makefile           tcp_echo_serverSR.c  unix_d_send.c   unix_d_server
tcp_client.c       tcp_qq_srver.c       unix_d_sendO    unix_d_server.c
tcp_echo_server.c  tcp_qq_srverS.c      unix_d_sendO.c  u_socket
02_Sockets$ ls -l u_socket
srwxr-xr-x 1 george george 0 апр 20 11:49 u_socket
02_Sockets$ ./unix_d_sendO u_socket Message

Captain Obvious reminds

There are other types of unix domain socket, for example stream ones.

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.

   1 #include <stdio.h>
   2 #include <sys/socket.h>
   3 #include <arpa/inet.h>
   4 #include <stdlib.h>
   5 #include <string.h>
   6 #include <unistd.h>
   7 
   8 #define ISIZE (sizeof(struct sockaddr_in))
   9 #define MAXCONN 3
  10 #define BUFSIZE 32
  11 
  12 int main(int argc, char *argv[]) {
  13 	int fd, connfd, conncount=0;
  14 	struct sockaddr_in srv;
  15 	char buf[32];
  16 
  17 	memset(&srv, 0, ISIZE);
  18 	srv.sin_family = AF_INET;
  19 	inet_pton(AF_INET, argv[1], &(srv.sin_addr));
  20 	srv.sin_port = htons(atoi(argv[2]));
  21 
  22 	fd = socket(AF_INET, SOCK_STREAM, 0);
  23 
  24 	bind(fd, (struct sockaddr*) &srv, ISIZE);
  25 
  26 	listen(fd, MAXCONN);
  27 
  28 	while(1) {
  29 		connfd = accept(fd, NULL, NULL); 
  30 		snprintf(buf, BUFSIZE, "Connection %d!\n", ++conncount);
  31 		write(connfd, buf, strlen(buf));
  32 		close(connfd);
  33 	}
  34 	return 0;
  35 }
tcp_qq_server.c

Some explanation:

Warning!

This program never closes it's 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.

   1 #include <stdio.h>
   2 #include <sys/socket.h>
   3 #include <arpa/inet.h>
   4 #include <stdlib.h>
   5 #include <string.h>
   6 #include <unistd.h>
   7 
   8 #define ISIZE (sizeof(struct sockaddr_in))
   9 #define MAXCONN 3
  10 #define BUFSIZE 32
  11 
  12 int main(int argc, char *argv[]) {
  13 	int fd, sz;
  14 	struct sockaddr_in srv;
  15 	char buf[32];
  16 
  17 	memset(&srv, 0, ISIZE);
  18 	srv.sin_family = AF_INET;
  19 	inet_pton(AF_INET, argv[1], &(srv.sin_addr));
  20 	srv.sin_port = htons(atoi(argv[2]));
  21 
  22 	fd = socket(AF_INET, SOCK_STREAM, 0);
  23 
  24 	connect(fd, (struct sockaddr*) &srv, ISIZE);
  25 
  26 	do {
  27 		fgets(buf, BUFSIZE, stdin);
  28 		if(buf[0]!='\n')
  29 			write(fd, buf, strlen(buf));
  30 		sz = read(fd, buf, BUFSIZE);
  31 		if(sz>0) printf("%s\n", buf);
  32 	} while(sz);
  33 	return 0;
  34 }
tcp_client.c

Some explanation:

How it works

Server

Client

02_Sockets$ ./tcp_qq_srver 127.0.0.1 1213

02_Sockets$ ./tcp_client 127.0.0.1 1213
Connection 1!
02_Sockets$ ./tcp_client 127.0.0.1 1213
Connection 2!
02_Sockets$ ./tcp_client 127.0.0.1 1213
Connection 3!
02_Sockets$

Explanation:

TCP echo server

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

   1 #include <stdio.h>
   2 #include <sys/socket.h>
   3 #include <arpa/inet.h>
   4 #include <stdlib.h>
   5 #include <string.h>
   6 #include <unistd.h>
   7 
   8 #define ISIZE (sizeof(struct sockaddr_in))
   9 #define MAXCONN 3
  10 #define BUFSIZE 32
  11 
  12 int main(int argc, char *argv[]) {
  13 	int fd, connfd, sz, port;
  14 	struct sockaddr_in srv, peer;
  15 	char buf[32];
  16 	char addr[INET_ADDRSTRLEN+1];
  17 	unsigned peersz=ISIZE;
  18 
  19 	memset(&srv, 0, ISIZE);
  20 	srv.sin_family = AF_INET;
  21 	inet_pton(AF_INET, argv[1], &(srv.sin_addr));
  22 	srv.sin_port = htons(atoi(argv[2]));
  23 
  24 	fd = socket(AF_INET, SOCK_STREAM, 0);
  25 
  26 	bind(fd, (struct sockaddr*) &srv, ISIZE);
  27 
  28 	listen(fd, MAXCONN);
  29 
  30 	while(1) {
  31 		connfd = accept(fd, (struct sockaddr *) &peer, &peersz);
  32 		sz = recv(connfd, buf, BUFSIZE, 0);
  33 		inet_ntop(AF_INET, &peer.sin_addr, addr, INET_ADDRSTRLEN);
  34 		port = ntohs(peer.sin_port);
  35 		printf("Received %d bytes from %s, port %d\n", sz, addr, port);
  36 		send(connfd, buf, sz, 0);
  37 		close(connfd);
  38 	}
  39 	return 0;
  40 }
tcp_echo_serverSR.c

Explanation:

How this works with nc client

Server

Local client

02_Sockets$ ip -4 address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    inet 192.168.100.16/24 brd 192.168.100.255 scope global dynamic noprefixroute wlan0
02_Sockets$ ./tcp_echo_serverSR 0.0.0.0 2132
Received 9 bytes from 127.0.0.1, port 51874
Received 19 bytes from 192.168.100.16, port 33016

02_Sockets$ nc 127.0.0.1 2132
loopback
loopback
02_Sockets$ nc 192.168.100.16 2132
local IPv4 address
local IPv4 address

Server

Remote client

Received 20 bytes from 192.168.100.4, port 52282
Received 32 bytes from 192.168.100.4, port 12345

remote_host$ nc 192.168.100.16 2132
Remote IPv4 address
Remote IPv4 address
remote_host$ nc -p 12345 192.168.100.16 2132
Remote IPv4 address with specific client port
Remote IPv4 address with specifi

Explanation:

Captain Obvious reminds

UDP sockets are AF_INET and SOCK_DGRAM ones.

HSE/ArchitectureOS/02_SocketProgramming (последним исправлял пользователь FrBrGeorge 2020-04-20 21:25:32)