1. Intro

The accept() function in the socket API is a component of server-side socket programming code.

In this tutorial, we’ll explore socket API and learn how the accept() function works in socket programming.

2. Socket API Overview

The Socket API is a component of socket programming that encapsulates many low-level operations:

Socket Programming Components

Referring to the diagram above, socket API consists of three components: socket creation, socket connection, and socket communication.

Specifically, the accept() function is a component of socket listening, which, in turn, is part of socket creation.

Let’s explore the socket creation component.

3. Socket Creation

In a server application, socket creation can be separated into two parts: socket binding and socket listening.

3.1. Socket Binding

Socket binding involves creating a server socket using the socket() function and then using the bind() function to bind (or associate) the socket with a specific port. Additionally, if the server has multiple network interfaces (therefore multiple IP addresses), there’s an option to bind the socket to a specific network interface or IP address:

algorithm SocketCreation(port, ip):
// INPUT
// port = port number to listen
// ip = server IP address (optional, can be 0 or 0.0.0.0)
// OUTPUT
// client socket file descriptor

server_fd <- create socket file descriptor using socket()
bind the socket with server port and ip using bind() 

...

At this point, we’ve assigned the socket with the server’s port (and IP address).

3.2. Socket Listening

Afterward, we call the listen() function to start listening for incoming connections:

algorithm SocketCreation(port, ip):

...

listen for incoming connections using listen()

while true:
    new_socket <- accept incoming client request using accept()
    return new_socket
end

After a successful listen() call, the socket then enters a “listening” state. Additionally, some high-level languages, like Java, implicitly invoke the bind() and listen() APIs in the background to simplify the API calls.

Finally, we call the accept() function to handle incoming client requests. The accept() function is a blocking function by default, meaning it’ll wait and only return until there’s an incoming client request.

Subsequently, the accept() function creates a new socket, or to be precise, a socket file descriptor. The socket file descriptor is an integer value and unique per client connection. Most importantly, it’s associated with various values in the background by the OS:

Socket File Descriptor

By embedding the client and server profile information into each socket file descriptor, the server app – or in this case, the OS – knows where to send the response.

4. The accept() API Call for Handling Client Requests

In the previous section, we learned that the accept() function call is blocking by default. This is also called a synchronous operation. This mode could be useful for some use cases, such as for:

  • handling client connections consecutively to make some configuration changes
  • diagnostic tools to collect information from multiple sources sequentially
  • a simple HTTP server to serve the clients one at a time

However, we can configure the accept() function to be non-blocking or asynchronous too. This method is particularly useful for various use cases, for example:

  • network servers with concurrent connections, such as web servers, file servers, chat servers, etc
  • high-performance computing or networking applications, such as High-Frequency Trading (HFT) system
  • distributed computing, such as AI model training. It’s because the asynchronous sockets offer non-blocking communication between nodes, enabling data exchange, load sharing, data sync, and more

Some high-level languages, like Java, have their own libraries for asynchronous calls.

5. Conclusion

In this tutorial, we explored the socket programming components, specifically the socket listening component, which we call the accept() function.

The accept() function handles the incoming requests by creating unique socket file descriptors for each client connection. Subsequently, the operating system associates these socket file descriptors with various values of the client and server information in the background. As a result, the application server – or more precisely, the OS – knows where to send the response.

Finally, we learned that we can configure the accept() in either blocking (synchronous) or non-blocking (asynchronous) mode and discussed the real-life use cases for each mode.