
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: June 5, 2024
Sockets play a critical role as the endpoints for sending and receiving data across a network. They form the backbone of many Internet applications, enabling communication between clients and servers. Whether it’s a web browser fetching a webpage, an email client retrieving messages, or a streaming service delivering content, sockets are integral to these operations.
However, what happens when an application using a socket is abruptly terminated? This scenario is common in real-world computing environments. For instance, applications may crash, be forcefully killed by users, or be terminated by the system.
In this tutorial, we’ll learn how Linux decides to close a socket when an application on one end is killed. We’ll explore the impact of process termination on sockets and the detailed mechanisms employed by the Linux kernel to handle these situations.
Network connections in Linux are primarily handled by the kernel, which is responsible for overseeing the creation, maintenance, and termination of sockets. The kernel’s networking subsystem, or network stack, handles the complexities of data transmission, ensuring correct routing of packets, retransmission if lost, and reassembly upon arrival.
From the moment of creating a socket to its point of closing, the kernel ensures that all operations adhere to the relevant networking protocols and standards.
The key components that handle this process are:
These components collectively ensure the smooth flow of data between applications and network interfaces.
When an application using a network connection is terminated, Linux doesn’t simply abandon the connection. Instead, the kernel steps in to ensure a graceful and controlled closure, following the established TCP protocol. This helps prevent data loss and maintain a healthy network environment.
Let’s consider two scenarios where the Linux kernel takes charge of connection closing.
When an application using a socket is terminated normally, using signals like SIGTERM or SIGINT, or when it crashes, the kernel attempts to gracefully close the socket connection. It achieves this by sending a FIN (finish) packet to the remote end, notifying it that the application is no longer sending data.
The kernel then waits for an ACK (acknowledgment) packet from the remote end, confirming receipt of the FIN packet. Once acknowledged, the kernel can then safely close the socket and release associated resources.
Sometimes, an application might inadvertently close its socket using methods other than the standard close() function. This could be due to programming errors or unexpected termination scenarios.
In such cases, the orphaned socket remains open until the kernel detects its abandoned state. After a configurable timeout period, the kernel initiates a graceful closing process similar to the first scenario, sending a FIN packet and waiting for an ACK before finally closing the socket.
This proactive behavior of the Linux kernel ensures that it terminates network connections cleanly, even when applications don’t follow the recommended procedures. However, it’s important to note that the brutal SIGKILL signal bypasses normal cleanup routines. Consequently, we might not always observe a graceful close in such cases.
Even though the Linux kernel manages connection closing, we might occasionally see connections lingering in the ESTABLISHED state with netstat. Here are some reasons why this might happen:
To minimize the risk of connections lingering indefinitely, we should consider setting a reasonable timeout for keepalive retries. Additionally, we can incorporate logic to handle unanswered keepalive packets.
We’ve seen the role of the Linux kernel in managing connection termination, but the responsibility doesn’t lie solely with the operating system. Applications that use sockets have a significant part to play in ensuring graceful and timely closing.
When an application finishes using a socket, it’s essential to call the close() function on the socket descriptor. This function informs the kernel that the application no longer requires the socket and initiates the closing process. By calling close(), the application allows the kernel to send the necessary FIN packet to the remote end, initiating the TCP termination handshake.
There are several advantages to using close() for application-level closing:
By calling close() when finished with a socket, applications actively participate in a clean and efficient connection termination process. This benefits both the application itself, ensuring predictable behavior and resource management, and the overall system health by preventing resource leaks.
In this article, we explored how Linux terminates network connections after killing the application on one end. We’ve seen that the Linux kernel plays an important role in ensuring a graceful and controlled closing process, following the established TCP protocol. This helps prevent data loss and resource leaks and promotes overall network stability.
We discussed scenarios where the kernel intervenes, such as situations where applications forget to call close(), or network issues disrupt the termination handshake. Additionally, we delved into the concept of TCP keepalive and other reasons a connection might remain established. Finally, we emphasized the importance of application-level closing using the close() function.