Tutorial: TCP/IP POSIX sockets using the C language By: Steven Arnow ------------------------------------------------------------------------------- All code examples in this tutorial is in the public domain. Tutorial itself is copyleft Steven Arnow 2010 Questions/errors/other feedback may be e-mailed to me, and I may answer. All information in this tutorial is provided as-is without any warranty. If you find this tutorial and/or code useful, good for you. But if it eats your cat or goes out with your daugter, I can't be held responsible. This tutorial assumes you got a basic understanding of the C programming language. ------------------------------------------------------------------------------- A simple socket client ======================= Following code is an example of an simple socket client that prints out everything the server sends. --- Start of code example --- #include #include #include #include #include int main(int argc, char **argv) { struct sockaddr_in address; struct hostent *hp; char buff[256]; int sock, port, i; if (argc <3) { printf("Usage: %s \n", argv[0]); return -1; } port = atoi(argv[2]); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Failed to create a socket.\n"); return -1; } if ((hp = gethostbyname(argv[1])) == NULL) { fprintf(stderr, "Failed to resolve host '%s'\n", argv[1]); return -1; } address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = *( u_long * ) hp->h_addr; if (connect( sock, (struct sockaddr *) &address, sizeof(struct sockaddr) ) == -1) { fprintf(stderr, "Failed to connect to host '%s'\n", argv[1]); return -1; } do { i = recv(sock, buff, 255, 0); buff[i] = 0; printf("%s", buff); } while (i != 0); close(sock); return 0; } --- End of code example --- Now, what does all this do and what do we need it for? Let's start with the headers. stdio.h - All file I/O functions (incl. printf()) stdlib.h - atoi() unistd.h - close() netdb.h - All socket functions and defines are declared in this header. sys/types.h - For backward-compatibility with older compilers. Now let's move on to the variable declarations. struct sockaddr_in address; - Used to store all details needed to connect. struct hostent *hp; - Used to store the address when resolving host. char buff[256]; - For storing data received from the socket. int sock; - Used to store our socket identifier. int port; - Variable to store the port number. int i - iterator, used in the do .. while loop And now, the code. if (argc <3) { printf("Usage: %s \n", argv[0]); return -1; } First we just check that we got the right amount of arguments, shouldn't be anything new to you. port = atoi(argv[2]); Then we convert the port number that was sent as an argument (that is in the form of a string) to an int using the atoi (ASCII to int) function. if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Failed to create a socket.\n"); return -1; } Now we need to tell the system we want a socket indentifier. The funcion »socket« do this for us. If it fails, we report an error and exit with a error code. We also supplies the function with the desired address family (AF_INET, IP) and transfer protocol (SOCK_STREAM, TCP). if ((hp = gethostbyname(argv[1])) == NULL) { fprintf(stderr, "Failed to resolve host '%s'\n", argv[1]); return -1; } We use the »gethostbyname« function to resolve the given hostname. An error is thrown and a error code returned if it fails (NULL is returned.) You may think, that because the pointer returned points to a memory location, you should free it afterwards. This is not the case. The struct pointer gethostbyname returns is automagically managed, and usually, you only get one pointer per thread and the memory is recycled everytime you use gethostbyname. Trying to free the pointer will most likely end up with libc killing your application nagging about a invalid pointer. address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = *( u_long * ) hp->h_addr; Then, the address struct is filled out with these values: Address family = AF_INET (Internet protocol [v.4] "IPv4") Port number = Since IP is big endian and x86 is little endian, it has to be converted. htons is used as a platform independent way of making sure it has the correct endianness. Address = Set to the address entry in the hostent struct where the resolved IP address is stored. if (connect( sock, (struct sockaddr *) &address, sizeof(struct sockaddr) ) == -1) { fprintf(stderr, "Failed to connect to host '%s'\n", argv[1]); return -1; } Now we can use the »connect« function, which actually connects to the host. If -1 is returned, an error has occured. If that happenes, a error is reported and the program exits with a error code. do { ... } while (i != 0); If everything has succeeded, the data can finally be received. i = recv(sock, buff, 255, 0); The recv function is used to receive data. Its prototype looks like this: int [bytes received] recv(int socket, void *buffer, int bufferlen, int flags); As you might have noted, the size of the buffer that we passes is one byte smaller than we declared. That's so we can NULL-terminate the string after so that it can be used with printf. If the connection is closed or any other error occurs and no bytes is read, zero is returned. When we no longer get any data, we can assume the host has closed the connection. Since blocking reads are the default setting, recv will wait for data, just like scanf will wait for user input. buff[i] = 0; printf("%s", buff); } while (i != 0); Finally, we NULL-terminate the string we get back, print it and loop until no more data can be read. If you're going to transmit binary data, you do not want to NULL-terminate it. close(sock); return 0; After the loop finishes, the socket is closed and the program stops. You can try the example code by compiling it and connect to: slaeshjag.org port 5555 Verify against telnet, which should show the same massage. A simple socket server ======================= The following code is a complete server. It can only handle one connection at a time, but has a queue for more. Most of it is identical to a client, but I'll point out the differences a little later. This simple server waits for a connection, accepts it and sends everything you type to the client. When "END" is typed on a single line, the connection is closed. When "EXIT" is typed, the connection is closed and the server exits. --- Start of example code --- #include #include #include #include #include #include int main(int argc, char **argv) { struct sockaddr_in address, acc; struct hostent *hp; char buff[256]; int sock, accsock, port; unsigned int acclen; if (argc <3) { printf("Usage: %s \n", argv[0]); return -1; } port = atoi(argv[2]); if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "Failed to create a socket.\n"); return -1; } if ((hp = gethostbyname(argv[1])) == NULL) { fprintf(stderr, "Failed to resolve host '%s'\n", argv[1]); return -1; } address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = *( u_long * ) hp->h_addr; if (bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) { fprintf(stderr, "Failed to bind to %s:%i\n", argv[1], port); return -1; } printf("Type \"END\" to disconnect the client\n"); printf("Type \"EXIT\" to quit the server gracefully\n"); if (listen(sock, 10)) { fprintf(stderr, "Unable to listen for connections\n"); return -1; } while (1) { printf("Listening for connections...\n"); acclen = sizeof(struct sockaddr_in); if ((accsock = accept(sock, (struct sockaddr *) &acc, &acclen)) < 0 ) { fprintf(stderr, "Could not accept the connection\n"); return -1; } printf("Connection accepted\n"); while(1) { printf("> "); fgets(buff, 256, stdin); if (strcmp(buff, "END\n") == 0) break; if (strcmp(buff, "EXIT\n") == 0) break; if (send(accsock, buff, strlen(buff), 0) == 0) { fprintf(stderr, "Connection died\n"); break; } } close(accsock); printf("Connection terminated\n"); if (strcmp(buff, "EXIT\n") == 0) break; } close(sock); return 0; } --- End of example code --- In this example, we use an aditional header, string.h. This is used for some string functions not needed by any netcode, but simplifies the example. We also need some more variables for this example. struct sockaddr_in acc; - Used to store some details about the client. int accsock - The socket descriptor for the client connection. unsigned int acclen - Accept uses this to store the lenght of acc. The defined string »buff« is this time used by fgets. Everything is identical with client until the point where the client connects. Instead, the server binds to the address and port, which in laymans terms is »reserving the address/port for it's own use«. While the program binds to a address/port, no other program can bind to it. And binding to a address/port is necessary if you want to listen on that port for connections. if (bind(sock, (struct sockaddr *) &address, sizeof(address)) < 0) { fprintf(stderr, "Failed to bind to %s:%i\n", argv[1], port); return -1; } Here we bind to the specified address and port. It pretty much works the same as connect. A error message is shown if it fails and the server exits. printf("Type \"END\" to disconnect the client\n"); printf("Type \"EXIT\" to quit the server gracefully\n"); Just some information to the user about how to end a transmission or exit the server. I really hope printf's ain't new to you. if (listen(sock, 10)) { fprintf(stderr, "Unable to listen for connections\n"); return -1; } This is the call that makes the socket listen for incoming connections. The second argument is the lenght of the queue (how many connections that can wait in line while the server serves a client.) Next is a infinate loop (well, until we break away from it that is...) printf("Listening for connections...\n"); Another message to tell the user what's going on. From now on, I'll stop mentioning these, as they're pretty self explaining. acclen = sizeof(struct sockaddr_in); Better set the acclen to the length of acc, funny things might happen otherwise. if ((accsock = accept(sock, (struct sockaddr *) &acc, &acclen)) < 0 ) { fprintf(stderr, "Could not accept the connection\n"); return -1; } Here we accepts a connection. The routine »accept« is blocking, so it will stay there until someone connects. The binded socket is also passed as a reference about what socket to accept from. When someone connects, the struct »acc« is filled out with the same stuff as »address« and acclen is set with the lenght of the data put in the struct. A new socket which point to the client is returned. The old socket is still valid so you can accept more connections. Another infinite loop (that also is finite since we break loose from it when a connection is closed) follows. In here we'll ask the user for information to send, and also send it. First a standard printf/fgets-combination to get a line from the user and make it look like a prompt. A few strcmp to check for »END« and »EXIT« that means we should break loose from the loop. If it is neither, the loop carry on to the part where we'll send the string the user typed in. if (send(accsock, buff, strlen(buff), 0) == 0) { fprintf(stderr, "Connection died\n"); break; } Send takes the same arguments as read. A socket, a buffer, length of the buffer and flags. I've never found a use for the flags, so I'm not going to expain them. Just leave a zero and you should be fine. Send returns the amount of bytes sent. If you're going to send large amount of data that migth not fit in a package, you may have to use the return value to increase the value of the pointer you send to make sure everything gets sent. NOTE: As you may have noticed, there's only a recv the client and only a send in the server. This is for the sake of simplicity. Sockets are full duplex, you can both send and receive data from the same socket. I did the client and server simplex for the sake of simplicity. The inner infinite loop ends here. And after it (execution will start here after all data has been transmitted) we close the socket to the client. After that, we check if the buffer contains »EXIT«. If that is the case, this loop is also braked loose from. After the second infinite loop, there's just the clean up work left of closing the binded socket. After that, we can exit the program with return code 0, everything went fine. NOTE: When testing this program, take note of what you bind to. If you bind to address »localhost« or »127.0.0.1«, it'll only listen for connection from your local machine. If you however bind to »0.0.0.0«, it'll listen to connections from any address. And when selecting port to bind to, keep in mind that ports 1-1024 are »privileged ports« that only root/a superuser can bind to. Use a port over 1024 when testing (nobody test stuff as super user.) You can use either the client earlier in this tutorial or telnet to test this server. What you type in the server should appear on your client