While sockets are no new concept for me, I recently had to write an application in C that uses sockets to communicate. And while trying to figure out, what the best way of doing so is, I came across a lot of tutorials. But most of them either completely missed the point, were too complicated or used obsolete functions in the code. In this article, I want to try to give you a simple and short overview of sockets and an up to date ‘hello world’ example for a client and a server application.
Sockets in general
Sockets are an abstract representation for a local endpoint of a network and they are used to allow your application to communicate with other processes. Therefore sockets are usually used to implement network features in applications, where a client-process can communicate with a server-process and vice-versa. However, the processes don’t necessarily have to run on different machines, making sockets useful for inter-process communication in general.
Usually, the operating system creates and binds a socket to a specific network interface, which is defined by a hostname (or IP-address) and a port-number. Furthermore, you have to specify, what communication protocol should be used. The most common types are TCP (usually used for stream-sockets) and UDP (mainly used for datagram-sockets).
There are two main types of sockets: stream-sockets and datagram-sockets. The first type communicates over streams, the latter uses single messages to communicate, but I don’t want to go into too much detail here.
Anyway, after the OS managed to create and bind a socket, a server then has to begin listening for incoming client-requests and accept them, to begin communicating. A client only has to connect to a server, after creating the socket.
Sockets in C (Linux)
Sockets are described just like files under Linux, allowing you to easily write and read to them, just like you would with a local text file. After creating a socket, you get a file-descriptor for it and you are free to either use that directly or open a file and treat the socket just like a local file:
Server application
// Includes missing in this example #define PORT "50200" int main() { // The hints struct will hold information about the protocol // version and socket type. ai will hold the results of the // address lookup. struct addrinfo hints, *ai; int socket_filedescriptor = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; // Set the address to NULL to make the server listen to // ANY interface. getaddrinfo(NULL, PORT, &hints, &ai); // Tell the OS to create a socket and store // the resulting file-descriptor socket_filedescriptor = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); // Bind the socket and enable clients to connect to the server bind(socket_filedescriptor, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); // Make the socket listen to incoming requests from up to ten clients. listen(socket_filedescriptor, 10); // Accept an incoming client request. // This method-call is blocking. It will halt the program // execution as long as a client attempts to connect int client_filedescriptor = accept(socket_filedescriptor, NULL, NULL); // create a file pointer from the client's file // descriptor. FILE* client_file = fdopen(client_filedescriptor, "r+"); char* line = NULL; size_t line_length = 0; // Read all the lines sent by a client... while(getline(&line, &line_length, client_file) != -1) { // ... and simply print them to stdout fprintf(stdout, "%s\n", line); } // Close the file and the socket close(client_filedescriptor); close(socket_filedescriptor); return EXIT_SUCCESS; }
Please note, that I omitted all the error handling in this snippet, to keep it short and readable. Please check the man pages or download the attached files, if you don’t know, how to check for errors!
As you can see, I didn’t use the obsolete “gethostbyname”-method. Instead, I used “getaddrinfo”, which is easier to use and should be used instead of the obsolete counterpart. This way, you don’t have to worry about htonl or htons or any other conversions.
Client application
//Includes missing in this example #define PORT "50200" #define HOST "localhost" #define MESSAGE "Hello Server!" int main() { // The hints struct will hold information about the protocol // version and socket type. ai will hold the results of the // address lookup. struct addrinfo hints, *ai; int socket_filedescriptor = -1; // Set the complete memory area used by hints to 0 memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; // Try to get an IP-address to the given host name. getaddrinfo(HOST, PORT, &hints, &ai); // Tell the OS to create a socket and store // the resulting file-descriptor socket_filedescriptor = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); // Try to connect to the server connect(socket_filedescriptor, ai->ai_addr, ai->ai_addrlen); freeaddrinfo(ai); // Send a message to the server and then close the file write(socket_filedescriptor, MESSAGE, strlen(MESSAGE)); close(socket_filedescriptor); return EXIT_SUCCESS; }
And here are the promised C files, so you can try it yourself!
Click here to download the sources
Sockets in C (Windows)
They basically work the same way and the above code runs on windows too, at least it did for me. I just wanted to mention, that Windows has its own header files and a SOCKET type (just like FILE), instead of a plain int socket file-descriptor. On Windows, you can use:
winsock.h or the newer winsock2.h
And you might need to replace the int from the code above with a SOCKET when declaring the file-descriptor variables.
Conclusion
I hope this gave you a good basic understanding of sockets in general and how to use them in C to exchange information between processes.