2013-03-21 3 views
12

Возможно ли связать и прослушать несколько портов в Linux в одном приложении?Слушайте несколько портов с одного сервера

+1

да, что возможно, вам нужно использовать ' select' или threads, хотя – perreal

+4

Да. Лучшим ответом на эти вопросы является то, что вы должны написать небольшое тестовое приложение и * попробовать сами *. По мере того, как вы становитесь более опытными, вы обнаружите, что чаще всего пишите эти маленькие «тестовые программы», чтобы понять, что происходит. –

+0

Как можно выбрать? Я не уверен, как сделать много привязок для одного сокета. – user2175831

ответ

13

Для каждого порта, который вы хотите слушать, вы:

  1. Создать отдельную розетку с socket.
  2. Привяжите его к соответствующему порту с помощью bind.
  3. Вызовите listen на сокет, чтобы он был настроен с помощью очереди прослушивания.

В этот момент ваша программа прослушивает несколько сокетов. Чтобы принимать соединения на этих сокетах, вам нужно знать, к какому сокету подключается клиент. Вот где входит select.Как это происходит, у меня есть код, который делает именно это сидение, так что вот полный проверенный пример ожидания соединений в нескольких сокетах и ​​возврата файлового дескриптора соединения. Удаленный адрес возвращается в дополнительных параметрах (буфер должен быть предоставлен вызывающим абонентом, как и accept).

(socket_type здесь является ЬурейеЕ для int на системах Linux и INVALID_SOCKET является -1. Это там, потому что этот код был перенесен на Windows, а также.)

socket_type 
network_accept_any(socket_type fds[], unsigned int count, 
        struct sockaddr *addr, socklen_t *addrlen) 
{ 
    fd_set readfds; 
    socket_type maxfd, fd; 
    unsigned int i; 
    int status; 

    FD_ZERO(&readfds); 
    maxfd = -1; 
    for (i = 0; i < count; i++) { 
     FD_SET(fds[i], &readfds); 
     if (fds[i] > maxfd) 
      maxfd = fds[i]; 
    } 
    status = select(maxfd + 1, &readfds, NULL, NULL, NULL); 
    if (status < 0) 
     return INVALID_SOCKET; 
    fd = INVALID_SOCKET; 
    for (i = 0; i < count; i++) 
     if (FD_ISSET(fds[i], &readfds)) { 
      fd = fds[i]; 
      break; 
     } 
    if (fd == INVALID_SOCKET) 
     return INVALID_SOCKET; 
    else 
     return accept(fd, addr, addrlen); 
} 

Этот код не говорит вызывающий, к которому подключен клиент, но вы можете легко добавить параметр int *, который получит файловый дескриптор, который видел входящее соединение.

+0

Мне было интересно, может ли это произойти без выбора/опроса, но, вероятно, нет. – Nick

2

Вы только bind() к одной розетке, а затем listen() и accept() - сокет для привязки для сервера, файловый дескриптор из accept() для клиента. Вы делаете свой выбор на последнем, ищем какой-либо клиентский сокет, на котором есть данные, ожидающие ввода.

+0

Кстати, вы удалили свой ответ на мой вопрос или сделали мод? Вид сумасшедшего, что они сделали, если да, то прилагаемые комментарии были полезной информацией, которую мог использовать кто-то другой. –

+0

nope ... был не мной. Я думал, что это был довольно хороший разговор. : -/ –

+0

да, это было полезно, даже знать, что не работает ... моды здесь сумасшедшие; возможно, вы можете отредактировать его, чтобы включить некоторые из наших обсуждений и попросить его восстановить? вы могли бы даже сказать, что мы пробовали все это, и что делать магическое число - лучшая альтернатива всему этому, так что это был бы законный ответ, который я мог бы принять (так как это похоже на реальный ответ) к вам, я не против, в любом случае. Я, вероятно, просто собирался написать свой собственный ответ в любом случае, потому что тот, который был дан до сих пор, действительно не затрагивает мою проблему. –

0

В такой ситуации вас может заинтересовать libevent. Он сделает работу select() для вас, возможно, используя гораздо лучший интерфейс, такой как epoll().

Огромный недостаток select() является использование FD_... макросов, которые ограничивают число сокета до максимального количества битов в fd_set переменной (приблизительно от 100 до 256). Если у вас небольшой сервер с 2 или 3 подключениями, все будет в порядке. Если вы намереваетесь работать на гораздо большем сервере, то fd_set может легко переполняться.

Кроме того, использование select() или poll() позволяет избежать темы на сервере (например, вы можете poll() ваш сокет и знаете ли вы можете accept(), read() или write() к ним.)

Но если вы действительно хотите сделать это как Unix, тогда вы хотите рассмотреть fork() -ing перед тем, как позвонить accept(). В этом случае вам абсолютно не нужны select() или poll() (если вы не слушаете много IP-адресов/портов и хотите, чтобы все дети могли отвечать на любые входящие соединения, но у вас есть недостатки с этими ...ядро может отправить вам другой запрос, пока вы уже обрабатываете запрос, тогда как только с accept() ядро ​​знает, что вы заняты, если не в самом вызове accept() - ну, это не работает именно так, но как пользователь, это так, как это работает для вас.)

с fork() вы готовите гнездо в главном процессе, а затем вызвать handle_request() в дочернем процессе, чтобы вызвать функцию accept(). Таким образом, у вас может быть любое количество портов и один или несколько детей для прослушивания каждого из них. Это самый лучший способ действительно очень быстро реагировать на любые входящие соединения под Linux (т.е. как пользователь и до тех пор, пока у вас есть дочерние процессы ждут клиента, это происходит мгновенно.)

void init_server(int port) 
{ 
    int server_socket = socket(); 
    bind(server_socket, ...port...); 
    listen(server_socket); 
    for(int c = 0; c < 10; ++c) 
    { 
     pid_t child_pid = fork(); 
     if(child_pid == 0) 
     { 
      // here we are in a child 
      handle_request(server_socket); 
     } 
    } 

    // WARNING: this loop cannot be here, since it is blocking... 
    //   you will want to wait and see which child died and 
    //   create a new child for the same `server_socket`... 
    //   but this loop should get you started 
    for(;;) 
    { 
     // wait on children death (you'll need to do things with SIGCHLD too) 
     // and create a new children as they die... 
     wait(...); 
     pid_t child_pid = fork(); 
     if(child_pid == 0) 
     { 
      handle_request(server_socket); 
     } 
    } 
} 

void handle_request(int server_socket) 
{ 
    // here child blocks until a connection arrives on 'server_socket' 
    int client_socket = accept(server_socket, ...); 
    ...handle the request... 
    exit(0); 
} 

int create_servers() 
{ 
    init_server(80); // create a connection on port 80 
    init_server(443); // create a connection on port 443 
} 

Обратите внимание, что функция handle_request() показан здесь как обработка одного запроса. Преимущество обработки одного запроса состоит в том, что вы можете сделать это способом Unix: распределите ресурсы по мере необходимости и после ответа на запрос, exit(0). exit(0) позвонит вам close(), free() и т. Д.

В отличие от этого, если вы хотите обрабатывать несколько запросов в строке, вы должны убедиться, что ресурсы освобождены до того, как вы вернетесь к вызову accept(). Кроме того, функция sbrk() в значительной степени никогда не будет призвана уменьшить объем памяти вашего ребенка. Это означает, что время от времени оно будет расти немного. Вот почему сервер, такой как Apache2, настроен на то, чтобы ответить на определенное количество запросов на каждого ребенка до начала нового ребенка (по умолчанию он составляет от 100 до 1000 в эти дни.)

Смежные вопросы