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
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
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
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)
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
Receive a portion of data from data socket to buffer via recv(data_socket, buffer, length, 0)
Datagram transmission has no control sockets, so the information about the sender address can be gathered by using recvfrom(socket, buffer, length, 0, address, &length)
Stream transmission complies «file as stream» abstraction, so we can just use read(socket, buffer, length) instead
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
Associate the socket with the specific remote server address/location via connect(socket, address, length) (see above for arguments explanation)
Send data to this server via send(socket, buffer, length, 0);
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 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 }
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 }
Some explanation:
To transmit non-byte data over network is to deal with byte ordering (endianness)
Various computer architectures can have various byte ordering
Endianness is easy to operate with memory
Network transmission protocols must have unique type of byte ordering
Endianness is preferred, because sending one byte of value, say, 0x56, is equivalent to sending four bytes 0x56, 0, 0 and then 0.
⇒ While dealing with address and port part of AF_INET address, we shall use convertors from current (host) endianness to network one and back. Hence function names:
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 }
Some explanation:
TCP protocol is bidirectional, so both client and server cat transmit data between each other.
We use read and write instead of send and recv, because we can!
Being simple, the client program can not decide, if it shall receive or send data first, so this is delegated to user: if empty string is entered, no message is sent and the program goes directly to receive part.
This can lead to protocol deadlock, when both client and server waits for the other side to send something forever.
Actual TCP/UDP universal tool, nectact (aka nc), can act as asynchronous network client or server with lot more functions.
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:
127.0.0.1 is so called loopback address, it will be useful to establish a IPv4 connection from host to the same host
Port number is random, but it must be >1024
The client program waits for input first, so to receive a response we need to press enter
Atfer printing a message, the client program waits for input again, hence second enter
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 }
Explanation:
- Now TCP traffic is fully bidirectional
We use send and recv instead of read and write, because we can!
We use non-reduced version of accept to record and then print peer address/port
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:
ip -4 addriess command on server side shows IPv4 addresses of all network interfaces, namely:
loopback (lo) with 127.0.0.1
wi-fi (wlan0) with 192.168.100.16
remote_host address is, apparently, 192.168.100.16
Note the source port invented by client's connect is random, but >=32768
…except when we've specified this port by nc -p 12345…
- Last message echoed incomplete due to buffer limitation (32 bytes)
We might read other parts of the message and send them all, but no, nothing like this in server code
Captain Obvious reminds
UDP sockets are AF_INET and SOCK_DGRAM ones.