Lecture 13
Sockets
Lecture
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 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 is only one)
Socket Programming
Probably it will be better to read the following text in parallel with trying the examples below.
To initialize a socket
- Create a
socket(domain, type, 0)
domain
is underlying layer type (various networks, filesystem etc), aka address familytype
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 socketaddress
is domain-specificstruct
filled with actual address of the serverlength
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
- 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 number of unreceived streams/datagrams is equal to
- 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
andlength
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
- socket given by
- Receive a portion of data from data
socket
tobuffer
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 its 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)
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:
- To transmit non-byte data over network is to deal with byte ordering (endianness)
- Various computer architectures can have various byte ordering
- Little-endian is easy to operate with memory
- Network transmission protocols must have unique type of byte ordering
- Big-endian is preferred, because sending
one byte of value, say,
0x56
, is equivalent to sending four bytes0x56
,0
,0
and then0
.
- Big-endian is preferred, because sending
one byte of value, say,
- ⇒ While dealing with
address
andport
part ofAF_INET
address, we shall use convertors from current (host) endianness to network one and back. Hence function names:
- Various computer architectures can have various byte ordering
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:
- 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
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:
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.
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:
- Now TCP traffic is fully bidirectional
- We use
send
andrecv
instead ofread
andwrite
, because we can! :)) - We use non-reduced version of
accept
to record and then print peer address/port
Workshop
Socket system calls
Common
Client
Server
Send/Receive
Homework
TODO
References
- OSI model (Wikipedia)
- Internet protocol suite (Wikipedia)