Можно ли передать действующее TCP-соединение другому процессу без завершения сеансов в текущем процессе? - PullRequest
1 голос
/ 27 мая 2020

В прокси-сервере высокой доступности и других системах балансировки нагрузки у нас есть процесс под названием reload, который разветвляет новый процесс и позволяет ему принимать новые соединения, а существующее соединение будет придерживаться старого процесса, а старый процесс останется активным до тех пор, пока все соединения истощены.

Есть ли способ переместить существующие соединения из старого процесса в новый разветвленный и закрыть старый?

Может ли кто-нибудь объяснить, почему это невозможно?

1 Ответ

1 голос
/ 27 мая 2020

Вот пример, адаптированный из того, что я делал долгое время go.
Хитрость заключается в том, чтобы полагаться на вспомогательные данные системных вызовов sendmsg() / recvmsg() в локальном сокете для передачи открытого дескриптор файла.
В этом примере для иллюстрации передачи fd используются обычные файлы, но, конечно, вместо него можно использовать сокеты TCP (в среде, отличной от windows).

Есть много деталей, которые не так очевидно, не копаясь в документации, но после инкапсуляции в некоторые функции, такие как send_fd() / receive_fd() в этом примере, его довольно просто использовать.

Обратите внимание, что при получении дескриптор файла не обязательно иметь тот же номер, что и при отправке.
Его следует рассматривать как своего рода dup() между разными процессами: дескрипторы файлов с разными номерами фактически относятся к одному и тому же ресурсу.
Обратите внимание также, что, поскольку как только дескриптор файла был отправлен в другой процесс, его можно закрыть (точно так же, как с dup() / dup2()), потому что новый файл скриптор, который будет получен другим процессом, по-прежнему относится к исходному ресурсу.

/**
  gcc -std=c99 -o prog_c prog_c.c \
      -pedantic -Wall -Wextra -Wconversion \
      -Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla \
      -g -O0 -UNDEBUG -fsanitize=address,undefined

  $ ./prog_c
  parent-process: using fd 3
  child-process: using fd 4
  parent-process: using fd 3
  child-process: using fd 4
  parent-process: using fd 3
  child-process: using fd 4
  parent-process: using fd 3
  child-process: using fd 4
  parent-process: using fd 3
  child-process: using fd 4
  child-process: done
  $ cat file_?.txt
  file_0.txt written by parent-process when opening
  Now child-process uses this open file.
  file_1.txt written by parent-process when opening
  Now child-process uses this open file.
  file_2.txt written by parent-process when opening
  Now child-process uses this open file.
  file_3.txt written by parent-process when opening
  Now child-process uses this open file.
  file_4.txt written by parent-process when opening
  Now child-process uses this open file.
**/


#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int // 0: success    -1: error
send_fd(int control_fd,
        int fd_to_send)
{
  int dummy_msg=1234;
  struct iovec vec;
  vec.iov_base=&dummy_msg;
  vec.iov_len=sizeof(dummy_msg);
  struct msghdr msg;
  msg.msg_name=NULL;
  msg.msg_namelen=0;
  msg.msg_iov=&vec;
  msg.msg_iovlen=1;
  struct cmsghdr * cmsg;
  char buffer[CMSG_SPACE(sizeof(int))];
  msg.msg_control=buffer;
  msg.msg_controllen=CMSG_SPACE(sizeof(int));
  cmsg=CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level=SOL_SOCKET;
  cmsg->cmsg_type=SCM_RIGHTS; // fd passing
  cmsg->cmsg_len=CMSG_LEN(sizeof(int));
  *((int *)CMSG_DATA(cmsg))=fd_to_send; // send new file descriptor
  msg.msg_flags=0;
  return sendmsg(control_fd, &msg, 0)==sizeof(dummy_msg) ? 0 : -1;
}

int // fd or -1
receive_fd(int control_fd)
{
  char buffer[CMSG_SPACE(sizeof(int))];
  struct msghdr msg;
  struct iovec vec;
  struct cmsghdr *cmsg;
  int dummy_msg;
  vec.iov_base=&dummy_msg;
  vec.iov_len=sizeof(dummy_msg);
  msg.msg_name=NULL;
  msg.msg_namelen=0;
  msg.msg_iov=&vec;
  msg.msg_iovlen=1;
  msg.msg_control=buffer;
  msg.msg_controllen=CMSG_SPACE(sizeof(int));
  msg.msg_flags=0;
  if(recvmsg(control_fd, &msg, 0)!=sizeof(dummy_msg))
  {
    return -1;
  }
  int fd=-1;
  cmsg=CMSG_FIRSTHDR(&msg); // ancillary data?
  if(cmsg&&(cmsg->cmsg_len>=(socklen_t)CMSG_LEN(sizeof(int)))&&
     (cmsg->cmsg_level==SOL_SOCKET)&&
     (cmsg->cmsg_type==SCM_RIGHTS)) // fd passing?
  {
    fd=*((int *)CMSG_DATA(cmsg)); // store new file descriptor
  }
  return fd;
}

int
main(void)
{
  int control_pair[2];
  if(socketpair(PF_LOCAL, SOCK_STREAM, 0, control_pair)==-1)
  {
    perror("socketpair");
    exit(1);
  }
  pid_t p=fork();
  if(p==-1)
  {
    perror("fork");
    exit(1);
  }
  if(p==0) // child process
  {
    close(control_pair[1]); // used by parent-process (arbitrary)
    for(;;)
    {
      int fd=receive_fd(control_pair[0]);
      if(fd==-1)
      {
        printf("child-process: done\n");
        break;
      }
      printf("child-process: using fd %d\n", fd);
      const char *text="Now child-process uses this open file.\n";
      write(fd, text, strlen(text));
      close(fd);
    }
    close(control_pair[0]);
    exit(0); // child-process stops here
  }
  // parent process
  close(control_pair[0]); // used by child-process (arbitrary)
  for(int i=0; i<5; ++i)
  {
    char text[100];
    sprintf(text, "file_%d.txt", i);
    int fd=open(text, O_WRONLY|O_TRUNC|O_CREAT, 0644);
    if(fd==-1)
    {
      perror("open");
      break;
    }
    printf("parent-process: using fd %d\n", fd);
    strcat(text, " written by parent-process when opening\n");
    write(fd, text, strlen(text));
    if(send_fd(control_pair[1], fd)==-1)
    {
      perror("send_fd");
      close(fd);
      break;
    }
    close(fd);
    sleep(1);
  }
  close(control_pair[1]);
  if(waitpid(p, NULL, 0)!=p)
  {
    perror("waitpid");
    exit(1);
  }
  return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...