There are many ways for a program to communicate with another program.
Depending on what you are trying to achieve, one or another method might be better suited.
Last time we looked into the easiest way of two programs to communicate with each other: Arguments
Today we are looking into an already well used way of programs to communicate with each other: Sockets.
What is so great about sockets?, you might be asking. Isn't it just normal IPC (Interprocess Communication) with a lot of overhead?
Well yeah, but it has one big advantage: it can be used by most programming languages and for different programs written in different programing languages to communicate with each other.
Practical Example: A service written in C that provides management information for different other processes (the Provider) and a process written in Java that can display that information (the Consumer).
Sticking to that example: It is really hard to get IPC working in Java, because Java utilizes a Virtual Machine Architecture. That means everything happenening in that JVM should stay in it. There are only a few exceptions which we can abuse for IPC, including Sockets.
Other than the integration of multiple programming languages, the Consumer and the Producer can sit on different host systems and form a distributed architecture, which might also be a reason for you to use this kind of communication.
You are propably not surprised if I tell you where Sockets are used for multiple processes to communicate with each other, but here I go:
Webbrowser making a request to a webserver
sshclient connecting to ansshserverServer sending notification to multiple clients about a state change
Alright, I guess you are ready for an implementation.\
The Producer:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"
void main(void) {
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
const char *sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo("127.0.0.1", DEFAULT_PORT, &hints, &result);
// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
break;
}
freeaddrinfo(result);
// Send an initial buffer
iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
printf("Bytes Sent: %ld\n", iResult);
// shutdown the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
// Receive until the peer closes the connection
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if ( iResult > 0 )
printf("Bytes received: %d\n", iResult);
else if ( iResult == 0 )
printf("Connection closed\n");
} while( iResult > 0 );
// cleanup
closesocket(ConnectSocket);
WSACleanup();
return;
}
Note: I omited error checking and some header files for clarity. This should not be used in production code.
Note 2: This program needs to be linked against Ws2_32.lib
Note 3: This is a stripped down version of a Network Socket Client Example for Windows
The Consumer:
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"
void main(void) {
WSADATA wsaData;
int iResult;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
struct addrinfo *result = NULL;
struct addrinfo hints;
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the server address and port
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
// Create a SOCKET for the server to listen for client connections.
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
freeaddrinfo(result);
iResult = listen(ListenSocket, SOMAXCONN);
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
// No longer need server socket
closesocket(ListenSocket);
// Receive until the peer shuts down the connection
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
// Echo the buffer back to the sender
iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
printf("Bytes sent: %d\n", iSendResult);
}
else if (iResult == 0)
printf("Connection closing...\n");
} while (iResult > 0);
// shutdown the connection since we're done
iResult = shutdown(ClientSocket, SD_SEND);
// cleanup
closesocket(ClientSocket);
WSACleanup();
return;
}
Note: I omited error checking and some header files for clarity. This should not be used in production code.
Note 2: This program needs to be linked against Ws2_32.lib
Note 3: This is a stripped down version of a Network Socket Server Example for Windows
To execute this simple example, first execute the compiled Consumer and then the compiled Producer. You should see both programs output the number of bytes sent/received to their console session and finally close the connection.
There are also some drawbacks to this approach of sharing data with another process:
As you need to link against multiple socket and web libraries, this kind of IPC has huge overhead.
It is not trivial to establish a web connection, meaning there is a lot that can go wrong and you need to do a lot of error checking and error handling.
Unfortunatly the socket API is a legacy from the old UNIX systems (which is why most of this code can be used one-to-one on a Linux machine). The downside with that is that the naming of some functions is suboptimal, which is why I had to keep many more comments in this code than usual.
You could use a wrapper interface or a facade for the common socket API, but that would lead to more overhead.It also takes a long time to share data, as all of the data has to go through the internet. Unless the Consumer and Producer are on the same system, but then the data still has to go through the whole internet driver stack.
There is still a lot of stuff one could add to this simple example, including the facade and error handling, but also stuff like the Consumer handling multiple Producers. That would turn the whole idea into a usual Client-Server-Architecture.
There is still a lot to be covered regarding the topic of IPC. Starting now, all the other concepts and sub-topics will start to become a lot more interesting and unique.
Exercise: Port the Client and Producer code to a Unix platform like Linux and discuss the significant changes. Why did Microsoft choose to use a similar API to the one found in UNIX systems and not design their own API that fits more into the rest of the Windows API set?