Objectives

Upon completion of this lesson, you will be able to:

  • explain sockets and TCP
  • open socket communication between a client and a server process

Overview

Sockets are a common mechanism for communicating between processes that support TCP. They support communication between a client and a server. The server opens a port and listens for incoming connection requests from clients on that port. The port number is advertised to clients. Once a client connects to that known port, the client or server may communicate through that connection. The connection is called a socket and it acts like a file, sending and receiving streams of bytes. The semantics of messages is part of a protocol that must be designed uniquely for each server.

While this lesson shows how to use sockets in C, the process is the same for any programming language using TCP sockets; that includes C++, Java, and C#.

Demonstration

Before inspecting the code, watch the tutorial where you will see how to download, compile, and run the server and client programs. After that, read the lesson to learn how the code works.

Code Files1: server.c | client.c | makefile

Communication Model

Communicating via TCP sockets follows a standard flow:

SERVER CLIENT
Create Server Socket Create Client Socket
call bind()
call listen()
call accept() (wait for client) call connect()
recv() (wait for client) send()
send() (send reply) recv() (wait for server)

Server Code

Let’s first look at the code for the server. The full code can be found here: server.c

Include the header files sys/socket.h, arpa/inet.h, and unistd.h which contain the definitions for the functions, types, and structs that we will need:

#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

The first step is to create a socket which will return a socket descriptor. We will use the descriptor later in the code to read and write from the socket. The socket descriptor is a file descriptor and as you will see we can use the file reading and writing functions for working with sockets. In TCP on Unix-like operating systems, sockets are essentially file streams.

int socket_desc = socket(AF_INET, SOCK_STREAM, 0);

The server-side code keeps the address information of both the server and the client in a variable of type sockaddr_in, which is a struct.

Initialize the server address by the port and IP:

Recall that the IP address “127.0.0.1” is a special address and always refers to the local host or your own computer. Rather than hardcoding an address, we could query the system’s IP address and use that, but that would add a bit of extra complexity that we want to avoid for this example.

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(2000);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

Bind the socket descriptor to the server address:

bind(socket_desc, (struct sockaddr*)&server_addr, 
     sizeof(server_addr);

Turn on the socket to listen for incoming connections:

listen(socket_desc, 1);

Store the client’s address and socket descriptor by accepting an incoming connection:

struct sockaddr client_addr;
int client_size = sizeof(client_addr);
int client_sock = accept(socket_desc, 
                         (struct sockaddr*)&client_addr, 
                         &client_size);

The server-side code stops and waits at accept() until a client calls connect().

Communicate with the client using send() and recv():

recv(client_sock, 
     client_message, 
     sizeof(client_message), 0);

send(client_sock, 
     server_message, 
     strlen(server_message), 0);

When recv() is called, the code stops and waits for a message from the client.

Close Connection

The final step is to close the server and client sockets to end communication and release resources. This is the last step and should not be omitted. If a program needs to exist prematurely, perhaps due to an error, it must close all files and sockets before exiting. Naturally, once a socket is closed, it can no longer be used to send and receive messages.

close(client_sock);
close(socket_desc);

Client Code

Include header files, create a socket, and initialize the server’s address information in a variable of type sockaddr_in, similar to how it was done at the server-side:

#include <sys/socket.h>
#include <arpa/inet.h>

int socket_desc = socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(2000);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

Send a connection request to the server, which is waiting for a connection in its call to accept():

connect(socket_desc, 
        (struct sockaddr*)&server_addr, 
        sizeof(server_addr));

Communicate with the server using send() and recv():

send(socket_desc, 
     client_message, 
     strlen(client_message),0);

recv(socket_desc, 
     server_message, 
     sizeof(server_message),0);

The client blocks, waiting for the server to send a message when recv() is called, i.e., the function does not return until a message is received from the server. If the server crashes or does not respond for some reason, then a deadlock can occur.

Lastly, once all message have been received and there is no further communication with the server, the connection must be closed.

close(socket_desc);

Deadlocks

Since the client and the server are running independently as separate processes on the same or different systems, deadlocks are a possibility and must be managed. For example, a deadlock will occur if both the client and the server are waiting at the same time for message from the other.

Explorations

  1. Try running the client first before the server has started. What happens and why?
  2. Try running the server on a different system (perhaps on another computer on your local network or a cloud virtual machine). What modifications do you need to make to get the client and server to connect?
  3. How would you send multiple messages?
  4. What would happen if two clients were trying to communicate with the same server? How would you multi-thread or multi-process the server so this is possible?
  5. What if the messages were very large (perhaps files) or if you wanted to stream data? How would you break the data up into chunks?
  6. This example used TCP sockets. What about UDP sockets? What are those and how would they work?
  7. Can you write the client in Java and connect to the server written in C? How about the server written in Java and the client in C? Try it.

Summary

Sockets are a file-like mechanism for inter-process communication using the TCP/IP network stack.


All Files for Lesson 93.202

Errata

None collected yet. Let us know.


  1. To download files to your local drive, do not click on the link; instead, right-click and choose Save Link As… or a similar choice.↩︎