Unix Socket Essentials
In a shell, there are 3 socket fds used when using sockets. 2 by the server: 1 to listen and establish connections (sersock), and 1 to
perform communication (consock). Then 1 on the client (clisock) which is used for communication.
See necessary includes on the bottom
Summary:
This is a very common form of interprocess communication that can be easily
parallelized on multiple threads to communicate with multiple clients, or just as easily utilized to
force clients to wait until the server is ready for another client. Like pipes, data is transferred as
individual bytes, usually utilizing a buffer to send large chunks at a time.
It is important when developing your application that you consider the communication
flow of the client-server connection as you approach your problem. For example, when the
connection is established, should the server write data to the client and then wait for a response
or should the client write data first?
For one of my projects, I had the client write data to the server. The type of
communication or request the client wanted to make could be 1 of 6 different things. In order for
the server to identify which request it was going to be, I had the client prepend their message
with one of these characters: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, or ‘5’. Then, the server would read exactly one
byte and use a switch to determine what action should be taken. This is a very informal
“protocol”, feel free to experiment and establish your own paradigm.
It is very important that the client sends the character ‘0’ instead of the integer 0,
because using string functions on the buffer would interpret the integer 0 as the null character
‘\0’ and produce undesirable results.
Note that this document is purely a supplement to the C/Linux manual pages for socket(), bind(),
listen(), accept(), read(), and write().
Steps to establishing a connection on the server:
1. Setup the address struct (this is used to identify where the socket will be placed in your
filesystem)
a. .sun_family (socketaddress_unix) = AF_UNIX
i. This is mandatory and the only option for .sun_family
b. .sun_path (so cketaddress_unix) = the (absolute or relative) path on your
filesystem to the socket you are going to create, this will be the same for client
and server
2. socket()
a. Creates a socket (returns a fd so you can use read(), write(), or other
system calls that use fd’s)
b. NOTE: the socket we are making here will not be used to perform communication
- it will only be used to establish a connection with a client(s)
3. bind()
a. Attempts to create a socket file at the path specified, will error if the file already
exists! (calling unlink(sock_path) will remove the file if it exists)
b. You will use sock_path on the client to refer to the same socket the server has
created
4. listen()
a. This tells the socket to listen for incoming connections, the 10 specifies how
many clients can be waiting to connect at a time
struct sockaddr_un addr;
int sersock;
int len = sizeof(addr);
memset(&addr, 0, len); // clear out the field
char sock_path[60];
strcpy(sock_path, getenv("HOME"));
strcat(sock_path, "/Desktop/.socket");
addr.sun_family = AF_UNIX; // sun_family is always AF_UNIX
strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path)-1); // copy the
path
sersock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sersock < 0) { // get a fd for the socket
perror("socket error");
return -1;
}
if (bind(sersock, (struct sockaddr*) &addr, sizeof(addr)) < 0) { // create socket file
perror("bind error");
return -1;
}
if (listen(sersock, 10) < 0) { // start enqueueing incoming connections
perror("listen error");
return -1;
}
return sersock;
5. accept()
a. This will block until a client calls connect() to this socket, returns the new fd to
use to communicate
b. Since both accept() [server] and connect() [client] block, this will make
processing one client at a time very easy since the connection won’t end (and
new clients won’t start) until you call close()
int consock = accept(sersock, NULL, NULL);
if (consock < 0) {
perror("Error accepting");
return -1;
}
read(consock, buffer, BUF_SIZE);
write(consock, buffer, strlen(buffer));
…
close(consock)
6. read() & write()
a. These are used on both the server and client to send and receive data, note that
read() will block until there has been the amount of bytes specified (1 byte, in
the example above) written into the socket
b. NOTE: these functions send/receive bytes/chars, not ints or structs, so functions
like sscanf(), strtol(), sprintf() will be very useful for sending info
7. When either side calls close(consock/clisock), the connection ends
a. I believe write() on a socket that either end has closed will block indefinitely,
so be sure that you have equal reads/writes on your client and server
Steps to connect to the server from the client:
1. Setup the address struct (this is used to identify where the socket will be placed in your
filesystem)
a. .sun_family (socketaddress_unix) = AF_UNIX
i. This is mandatory and the only option for .sun_family
b. .sun_path (so cketaddress_unix) = the (absolute or relative) path on your
filesystem to the socket you are going to create, this will be the same for client
and server
2. socket()
a. Creates a socket fd to communicate with a server
3. connect()
a. Blocks until the server calls accept()
4. read() and write()
struct sockaddr_un addr;
int clisock;
char sock_path[60];
strcpy(sock_path, getenv("HOME"));
strcat(sock_path, "/Desktop/.socket");
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path)-1);
clisock = socket(AF_UNIX, SOCK_STREAM, 0);
if (connect(clisock, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
perror("Error connecting");
return 0;
}
// read and write here
write(clisock, buf, strlen(buf));
read(clisock, buf, BUF_SIZE);
…
close(clisock)
Includes I used
The bold ones are for sure needed for sockets, idk about the others
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysinfo.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>