Returning to our previous discussion on TCP data exchange, we have yet to explore the intricacies of connection initiation.

Figure 3.3a Three-way handshake.
Socket programming plays a pivotal role in facilitating this process, allowing applications to establish connections for data transfer. Let's delve deeper into how socket programming fits into this mechanism.
The sequence of function calls for the client and a server participating in a TCP connection is presented in below.

Figure 4.1 TCP client-server.
<aside> <img src="/icons/map-pin_gray.svg" alt="/icons/map-pin_gray.svg" width="40px" /> For reference, the TCP client is the sender and TCP server is the receiver in Figure 3.3a.
</aside>
The Berkeley Socket System provides a set of low-level system primitives for network communication, as shown in Figure 4.1.

FIgure 4.2 Primitive used in Berkeley Socket:
Refer to the page below for the full definition:
Recall that for TCP, both sender (client) and receiver (server) must synchronize.
Server side:
sd = socket(AF_INET, SOCK_STREAM, 0); // Create a socket
bind(sd, (struct sockaddr *) &server, sizeof(server)); // Bind the socket to a specific address and port
listen(sd, 5); // Listen for incoming connections
new_sd = accept(sd, (struct sockaddr *) &client, &client_len); // Accept an incoming connection
socket() function.bind() function.listen() function.accept() function system call. This call typically blocks until a client connects with the server.Client side:
sd = socket(AF_INET, SOCK_STREAM, 0); // Create a socket
connect(sd, (struct sockaddr *) &server, sizeof(server)); // Connect to a server
socket() function.
connect() function.read() and write() functions.To provide visual context, the following diagram of TCP communication over the network is shown below.
.jpg)
Figure 4.4 Three-way handshake and Data transmission.
<aside>
<img src="/icons/map-pin_gray.svg" alt="/icons/map-pin_gray.svg" width="40px" /> Recall that connect() returns a 0, when successful and accept() returns a new socket — new_sd — descriptor for the accepted connection.
</aside>
Clients are not limited to just one; a server can handle multiple client connections simultaneously. In such scenarios, the server can deal with them in two ways:
Iterative servers: The server process one client at a time. They are fairly simple and are suitable for transactions that do not last long.

Concurrent servers: The server handle multiple clients simultaneously. The simplest technique for a concurrent server is to call the fork function.

You should already be familiar with the concept of forking, as it was previously covered in more detail through COE628 - Notes. The fork() function is the only way in Unix to create a new process. It is defined as follows:
#include <unist.h>
pid_t fork(void);
After connection establishment, the child process serves the client on the connected socket (connfd), while the parent process awaits new connections on the listening socket (listenfd).

Figure 4.5 Interaction among a client and a concurrent server.
The connected socket is closed by the parent since the child handles the client.
As we already learned, there are some fundamental differences between TCP and UDP sockets. UDP is a connection-less, unreliable, datagram protocol. Client applications using UDP can operate in two basic modes: