Basic concepts:
The SO_REUSEADDR socket option has four different functions:
(1) SO_REUSEADDR allows you to start a listening server and bundle well-known ports, even though previously established ports still exist as their local ports.
This condition is usually met as follows:
A) Start a listening server;
b) Connect to the request and derive a subprocess to process the customer;
c) The listening server terminates, but the subprocess continues to serve customers on existing connections;
d) Restart the listening server.
By default, when the listening server calls socket, bind, and listen to restart in step d, the bind call fails because it attempts to bundle ports on an existing connection. But if the server sets the SO_REUSEADDR socket option between socket and bind calls, then bind will succeed. All TCP servers should specify this socket option to allow the server to be restarted in this case.
(2) SO_REUSEADDR allows multiple instances of the same server to be started on the same port as long as each instance is bundled with a different local IP address.
(3) SO_REUSEADDR allows a single process to bundle the same port to multiple sockets, as long as each bundle specifies a different local IP address.
(4) SO_REUSEADDR allows full duplicate binding: when an IP address and port are bound to a socket. If the transport protocol supports, the same IP address and port can also be bundled to another socket. Generally speaking, this feature only supports UDP sockets.
The SO_REUSEPORT socket option performs the following two different functions:
(1) This option allows full duplicate bundling, but only if each socket that wants to bundle the same IP address and port specifies this socket option.
(2) If the bound IP address is a multicast address, SO_REUSEADDR and SO_REUSEPORT are considered equivalent.
Here we focus on:
SO_REUSEADDR's first (1) function (blue font)
SO_REUSEPORT's first (1) function (blue font)
Application scenario: The nginx smoothing upgrade is applied to relevant knowledge.
Example 1:
We need to know,
(a) If there is no restriction on TCP socket options, if two processes are started, the second process will make an error when calling the bind function (Address already in use).
b) If we set SO_REUSEADDR before calling bind, but do not close the socket before the second process starts, then the second process will still make an error when calling the bind function (Address already in use).
c) If we set SO_REUSEADDR before calling bind and receive a client connection, and close the bind socket before the second process starts, then the first process has only one socket (the connection with the client), then the second process can bind successfully and meet expectations.
Code:
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <errno.h>
- #include <arpa/inet.h>
- #include <stdlib.h>
- void Perror(const char *s)
- {
- perror(s);
- exit(EXIT_FAILURE);
- }
- int main()
- {
- int sockfd = socket(AF_INET, SOCK_STREAM, 0); //TCP
- int backlog = 100;
- short port = 9527; //port
- struct sockaddr_in servaddr;
- servaddr.sin_family = AF_INET; //IPv4
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //Represents that the IP address is selected by the kernel
- servaddr.sin_port = htons(port);
- int flag = 1;
- if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) {
- Perror("setsockopt fail");
- }
- int res = bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr));
- if (0 == res)
- printf("server bind success, 0.0.0.0:%d\n", port);
- else {
- Perror("bind fail");
- }
- if (-1 == listen(sockfd, backlog)) {
- Perror("listen fail");
- }
- //Waiting for connection
- struct sockaddr_in cliaddr;
- socklen_t len = sizeof(cliaddr);
- int connfd = accept(sockfd, (sockaddr *)&cliaddr, &len);
- if (-1 == connfd) {
- Perror("accept fail");
- }
- //Resolving Client Address
- char buff[INET_ADDRSTRLEN + 1] = {0};
- inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);
- uint16_t cli_port = ntohs(cliaddr.sin_port);
- printf("connection from %s, port %d\n", buff, cli_port);
- //Close sockfd of bind
- close(sockfd);
- //
- sleep(1200);
- return 0;
- }
Compile:
g++ server.cpp -o s1
g++ server.cpp -o s2
Operation results:
Example 2,
SO_REUSEPORT may not be supported in older kernel versions. The kernel version of my test environment is 3.10. Compared with SO_REUSEADDR, SO_REUSEPORT does not have so many restrictions. It allows two unrelated processes to use the same IP address to listen on the same port at the same time and does not appear. thundering herd.
Code:
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <errno.h>
- #include <arpa/inet.h>
- #include <stdlib.h>
- void Perror(const char *s)
- {
- perror(s);
- exit(EXIT_FAILURE);
- }
- int main()
- {
- int sockfd = socket(AF_INET, SOCK_STREAM, 0); //TCP
- int backlog = 100;
- short port = 9527; //port
- struct sockaddr_in servaddr;
- servaddr.sin_family = AF_INET; //IPv4
- servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //Represents that the IP address is selected by the kernel
- servaddr.sin_port = htons(port);
- int flag = 1;
- if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &flag, sizeof(flag))) {
- Perror("setsockopt fail");
- }
- int res = bind(sockfd, (sockaddr *)&servaddr, sizeof(servaddr));
- if (0 == res)
- printf("server bind success, 0.0.0.0:%d\n", port);
- else {
- Perror("bind fail");
- }
- if (-1 == listen(sockfd, backlog)) {
- Perror("listen fail");
- }
- //Waiting for connection
- while (1) {
- struct sockaddr_in cliaddr;
- socklen_t len = sizeof(cliaddr);
- int connfd = accept(sockfd, (sockaddr *)&cliaddr, &len);
- if (-1 == connfd) {
- Perror("accept fail");
- }
- //Resolving Client Address
- char buff[INET_ADDRSTRLEN + 1] = {0};
- inet_ntop(AF_INET, &cliaddr.sin_addr, buff, INET_ADDRSTRLEN);
- uint16_t cli_port = ntohs(cliaddr.sin_port);
- printf("connection from %s, port %d\n", buff, cli_port);
- }
- return 0;
- }
Compile:
g++ server.cpp -o s1
g++ server.cpp -o s2
Operation results: