Why didn't the SO_LINGER parameter take effect?

30 views Asked by At

The so_linger in the socket controls close() to close the socket.

when l_onoff is set to 0, the socket discards the data in the buffer and returns it when close is called;
When l_onoff is set to 1 and l_linger is set to 0, the socket returns an rst message to the other end if there is data in the buffer;
When l_onoff is set to 1 and l_linger is greater than 0, if data exists in the buffer, it will wait for the size of l_linger and then return.

But this doesn't seem to be the case when testing the so_linger parameter.

When I was researching sockets and looking at the SO_LINGER parameter, I ran the following test:

/* server.cc */
#include <arpa/inet.h>
#include <netdb.h>
#include <strings.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

void service(int port = 11115) {
  // create address struct
  struct sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(port);
  serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

  std::cout << "create service socket address successed." << std::endl;

  // create socket fd
  int serv_fd;
  if ((serv_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
    std::cerr << "socket failed." << std::endl;
    return;
  }
  std::cout << "create service socket: " << serv_fd << std::endl;
  // modify receive buffer size
  int recv_buf_size = 2048;
  socklen_t sz_recvbuf_opt = sizeof(recv_buf_size);
  setsockopt(serv_fd, SOL_SOCKET, SO_RCVBUF, (void*)&recv_buf_size,
             sz_recvbuf_opt);
  std::cout << "set receive buffer size successful." << std::endl;

  // bind address info to socket
  bind(serv_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  // listen the socket
  if (listen(serv_fd, 5) == -1) {
    std::cerr << "listen failed." << std::endl;
    close(serv_fd);
    return;
  }
  int clnt_fd;
  struct sockaddr_in clnt_addr;
  socklen_t clnt_addr_len = sizeof(clnt_addr);
  std::stringstream ss;
  char buf[256];

  while (1) {
    // accept connection fd
    clnt_fd = accept(serv_fd, (struct sockaddr*)&clnt_addr, &clnt_addr_len);

    ss << "client fd: " << clnt_fd << " | " << clnt_addr.sin_family << " | ";
    ss << inet_ntoa(clnt_addr.sin_addr) << " | " << clnt_addr.sin_port
       << std::endl;
    std::cout << ss.str();

    // read data from client
    char ch;
    int cnt = 0;
    std::cout << "Please enter flag to read data:" << std::endl;
    std::cin >> ch;
    char readbuf[1025];
    readbuf[1024] = 0;
    while ((cnt = read(clnt_fd, readbuf, 1024)) > 0) {
      std::cout << "read successful." << readbuf << std::endl;
      std::cout << "Enter flag to continue read:";
      std::cin >> ch;
    }
    std::cout << "read end.Please enter flag to close client fd" << std::endl;

    close(clnt_fd);
  }

  // close scoket fd
  close(serv_fd);
}

int main(int argc, char** argv) {
  if (argc > 1) {
    std::cout << "port: " << argv[1] << std::endl;
    int n_port = atoi(argv[1]);
    service(n_port);
  } else {
    service();
  }
  return 0;
}
/* client.cc */
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
#include <string>
int deft_loop_cnt = 4;

void client(const char* serv_ip, int serv_port = 11115) {
  struct sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(serv_port);

  struct hostent* phost;
  if ((phost = gethostbyname(serv_ip)) == nullptr) {
    std::cerr << "gethostbyname failed." << std::endl;
    return;
  }
  memcpy(&serv_addr.sin_addr, phost->h_addr, phost->h_length);

  int clnt_fd;
  // create client socket
  if ((clnt_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
    std::cerr << "socket failed." << std::endl;
    return;
  }
  std::cout << "create socket successful." << std::endl;
  // modify send buffer size
  int snd_buf_size = 1024;
  socklen_t opt_size = sizeof(snd_buf_size);
  setsockopt(clnt_fd, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf_size, opt_size);
  
  socklen_t sz_linger_opt = sizeof(struct linger);
  struct linger new_linger {
    0, 0
  };
  setsockopt(clnt_fd, SOL_SOCKET, SO_LINGER, (void*)&new_linger, sz_linger_opt);

  // connection socket
  if (connect(clnt_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
    std::cerr << "connect failed." << std::endl;
    close(clnt_fd);
    return;
  };
  std::cout << "connect to the service successful." << std::endl;

  int n;
  char buf[1024];
  // write information for test send buffer size
  for (int i = 0; i < deft_loop_cnt; i++) {
    // generate message
    for (int j = 0; j < 1024; j++) buf[j] = 'a' + i;
    if ((n = write(clnt_fd, buf, sizeof(buf))) < 0) {
      std::cout << "write error: " << n << std::endl;
      goto close_fd;
    }
    std::cout << "write 1024 bytes successfuls: " << i << std::endl;
  }
  // don't need last message

close_fd:
  close(clnt_fd);
}

int main(int argc, char* argv[]) {
  if (argc < 2) return 0;

  std::cout << "start client " << argv[1] << "......" << std::endl;
  if (argc > 2) {
    int n_port = atoi(argv[2]);
    std::cout << "client port: " << n_port << std::endl;
    client(argv[1], n_port);
  } else {
    client(argv[1]);
  }
  return 0;
}

At this time, according to google about so_linger document,
when linger.l_onoff is set to 0, after calling close, socket will discard all the information in the receive and send buffers.

However, by checking with "ss -tm", I found that the client program is still waiting for the server to accept the data!

Shouldn't it discard the data in the buffer? Why doesn't it discard the data in the buffer?

1

There are 1 answers

2
Remy Lebeau On

when l_onoff is set to 0, the socket discards the data in the buffer and returns it when close is called;

That is not entirely correct. You are disabling the linger option by setting l_onoff to 0, so close() will not discard the buffer.

Per the documentation:

https://man7.org/linux/man-pages/man7/socket.7.html

When enabled, a close(2) or shutdown(2) will not return until all queued messages for the socket have been successfully sent or the linger timeout has been reached. Otherwise, the call returns immediately and the closing is done in the background. When the socket is closed as part of exit(2), it always lingers in the background.

Also see Resetting a TCP connection and SO_LINGER, which goes into a rather lengthy explanation of how SO_LINGER actually behaves.