Unix sockets are a low-level way of achieving inter-process communication on Unix systems, including iOS. All of the other types of IPC in iOS, e.g. mach ports and the like, are built on top of Unix sockets.
For more/better reading material, check out beej's Unix socket guide or his excellent network programming guide.
Comparison to other IPC methods
Pros
- No dependencies
- Extremely portable
- Less overhead
- "Easier" to bypass sandbox restrictions (more on this later)
Cons
- API is in C instead of Objective-C
- Not "the Apple way"
- Takes more work to properly set up
If this is your first time doing IPC, it's probably better to use another method such as CPDistributedMessagingCenter or LightMessaging, which are more straightforward. Unix sockets should only really be used if you've used another IPC method and have found it lacking or restrictive.
Rudimentary setup
Server
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
void server_start()
{
// you can change this to whatever you want
// but it's good practice to put in /var/run
const char *socket_path = "/var/run/your_server.socket";
// setup socket
struct sockaddr_un local;
strcpy(local.sun_path, socket_path);
unlink(local.sun_path);
local.sun_family = AF_UNIX;
int listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
printf("listenfd: %d\n", listenfd);
// start the server
int r = -1;
while(r != 0) {
r = bind(listenfd, (struct sockaddr*)&local, sizeof(local));
printf("bind: %d\n", r);
usleep(200 * 1000);
}
int one = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
// start listening for new connections
r = -1;
while(r != 0) {
r = listen(listenfd, 20);
printf("listen: %d\n", r);
usleep(200 * 1000);
}
// wait for new connection, and then process it
int connfd = -1;
while(true) {
if(connfd == -1) {
// wait for new connection
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
printf("new connfd: %d\n", connfd);
}
// process incoming data
char buffer[4096];
int len = recv(connfd, buffer, sizeof(buffer), 0);
if(len == 0) {
printf("connfd %d disconnected!\n", connfd);
connfd = -1;
continue;
} else {
printf("connfd %d recieved data: %s", connfd, buffer);
// send some data back (optional)
const char *response = "got it!\n";
send(connfd, response, strlen(response) + 1, 0);
}
}
}
You should run server_start()
in a background thread, otherwise it will block.
To test, run the command socat - UNIX-CONNECT:/var/run/your_server.socket
which will give you a REPL.
Client
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
void client_start()
{
// setup socket
struct sockaddr_un remote;
remote.sun_family = AF_UNIX;
strcpy(remote.sun_path, "/var/run/your_server.socket");
int connfd = socket(AF_UNIX, SOCK_STREAM, 0);
printf("connfd: %d\n", connfd);
// connect to server
int r = -1;
while(r != 0) {
r = connect(connfd, (struct sockaddr *)&remote, sizeof(remote));
printf("connect: %d\n", r);
}
// send and receive messages
const char *message = "why hello there!\n";
send(connfd, message, strlen(message), 0);
char buffer[4096];
int len = recv(connfd, buffer, sizeof(buffer), 0);
if(len != 0) {
printf("recieved response: %s\n", buffer);
}
close(connfd);
}
Pitfalls of this example
There are a few issues with this example.
First off, the entire server logic is under one thread. That means only one client can connect at a time. If another client tries to connect, it will hang until the first one disconnects. This can be fixed by putting various parts of the client handling logic under a dispatch_async
, or something like that.
The other issue is that it doesn't handle messages that exceed 4096 characters. To mitigate this, it's best to decide on some sort of "separator" string between messages for your protocol (\0
is a good choice), and then just do the processing only once you hit that separator.
Bypassing sandbox with inet sockets
In iOS 10, daemons/processes with the "seatbelt" entitlement (e.g. mediaserverd) don't allow you to create unix domain sockets anymore. :( This is also a problem with the higher-level libraries that use RocketBootstrap.
Luckily for us, there is still a workaround: local internet sockets. The key difference with these is that they're not actual internet sockets. The way these will be set up is that they can only be accessed from localhost, so essentially it poses the same security risk as any other IPC method.
But seriously, don't use this method unless you absolutely have to. Using regular unix domain sockets is much, much better than this approach, since you can tie the socket to a file descriptor instead of a port. There are unlimited potential file descriptors, and only a finite number of available ports. Don't reserve a port unless you absolutely have to.
Code
Basically, all you'd have to change is the socket setup:
Server
// setup socket
int port = 80; // CHANGE THIS!!!!
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
local.sin_port = htons(80);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
Client
// setup socket
int port = 80; // CHANGE THIS!!!!!
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(port);
inet_aton("127.0.0.1", &remote.sin_addr);
int connfd = socket(AF_INET, SOCK_STREAM, 0);
Choosing a port
The only real pitfall of this approach is having to pick a port. You'll have to pick one between 1024-65535, and is not in Wikipedia's list of TCP port numbers and also not in the list of reserved ports below. Naturally, there is a very real chance that the port you pick will conflict with another service, so choose wisely. Typically something above 10000 and below 50000 will be unlikely to conflict with anything.