2016-01-26 2 views
3

Эта проблема беспокоила меня в течение нескольких недель, и я не мог найти решение в Интернете. Поэтому я должен задать вам новый вопрос.Ошибка сегментации в работе массивных сокетов

Я пытался читать/писать на огромном количестве сокетов, см. Тестовый код ниже. Он ведет себя нормально, когда число сокетов ниже 1500. Когда количество сокетов превышает 1500, программа неожиданно выйдет из строя. Я знаю, что я должен использовать команду ulimit -n 32768, чтобы увеличить ограничение числа открытых файлов. Но программа по-прежнему не может вести себя правильно.

#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <sys/socket.h> 
#include <arpa/inet.h> 
#include <stdint.h> 
#include <netdb.h> 
#include <errno.h> 
#include <malloc.h> 
#include <string.h> 

int main(int argc, char* argv[]) 
{ 
    if (argc!=2) 
    { 
     printf("usage: test <number of sockets>\n"); 
     return -1; 
    } 

    int socketsNum=atoi(argv[1]); 
    if (socketsNum<=0) 
    { 
     printf("error: invalid sockets number\n"); 
     return -1; 
    } 

    int *socketHandles=(int*)malloc(sizeof(int)*socketsNum); 
    if (socketHandles==NULL) 
    { 
     printf("error: failed to alloc socket handle memory\n"); 
     return -1; 
    } 

    for (int i=0;i<socketsNum;i++) 
    { 
     socketHandles[i]=-1; 
    } 


    printf("creating %d sockets ...\n",socketsNum); 
    int createdSocketsNum=0; 
    for (int i=0;i<socketsNum;i++) 
    { 
     int socketHandle=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); 
     if (socketHandle==-1) 
     { 
      int lastError=errno; 
      printf("warning: socket() failed: index: %d, error: %d\n",i+1,lastError); 
      continue; 
     } 

     sockaddr_in sockAddr; // 0.0.0.0:0 
     memset(&sockAddr,0,sizeof(sockAddr)); 
     sockAddr.sin_family = AF_INET; 
     sockAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
     sockAddr.sin_port = htons(0); 

     if (bind(socketHandle, (sockaddr*) &sockAddr, sizeof(sockAddr)) == -1) 
     { 
      int lastError=errno; 
      printf("warning: bind() failed: index: %d, error: %d\n",i+1,lastError); 
      close(socketHandle); 
      continue; 
     } 
     socketHandles[i]=socketHandle; 
     createdSocketsNum++; 
    } 

    printf("created %d sockets.\n",createdSocketsNum); 

    //test reading; 
    printf("testing reading ...\n"); 
    int readableNumber=0; 
    int unreadableNumber=0; 
    int readingSkippedNumber=0; 
    for (int i=0;i<socketsNum;i++) 
    { 
     int socketHandle=socketHandles[i]; 
     if (socketHandle==-1) 
     { 
      readingSkippedNumber++; 
      continue; 
     } 

     fd_set rset; 
     FD_ZERO(&rset); 
     FD_SET(socketHandle, &rset); 
     struct timeval timeout = {0, 0}; 
     int retCode=select(socketHandle + 1, &rset, NULL, NULL, &timeout); 
     if (retCode==-1) 
     { 
      int lastError=errno; 
      printf("warning: select() failed: index: %d, error: %d\n",i+1,lastError); 
     } 
     else if (retCode==0) 
     { 
      unreadableNumber++; 
     } 
     else 
     { 
      readableNumber++; 
     } 
    } 
    printf("readable: %d, unreadable: %d, skipped: %d, total: %d\n",readableNumber,unreadableNumber,readingSkippedNumber,socketsNum); 

    //test writing 
    printf("testing writing ...\n"); 
    int writableNumber=0; 
    int unwritableNumber=0; 
    int writingSkippedNumber=0; 
    for (int i=0;i<socketsNum;i++) 
    { 
     int socketHandle=socketHandles[i]; 
     if (socketHandle==-1) 
     { 
      writingSkippedNumber++; 
      continue; 
     } 
     fd_set wset; 
     FD_ZERO(&wset); 
     FD_SET(socketHandle, &wset); 
     struct timeval timeout = {0, 0}; 
     int retCode=select(socketHandle + 1, NULL, &wset, NULL, &timeout); 
     if (retCode==-1) 
     { 
      int lastError=errno; 
      printf("warning: select() failed: index: %d, error: %d\n",i+1,lastError); 
     } 
     else if (retCode==0) 
     { 
      unwritableNumber++; 
     } 
     else 
     { 
      writableNumber++; 
     } 
    } 
    printf("writable: %d, unwritable: %d, skipped: %d, total: %d\n",writableNumber,unwritableNumber,writingSkippedNumber,socketsNum); 

    printf("closing ...\n"); 
    for (int i=0;i<socketsNum;i++) 
    { 
     int socketHandle=socketHandles[i]; 
     if (socketHandle==-1) 
     { 
      continue; 
     } 
     close(socketHandle); 
    } 
    free(socketHandles); 
    printf("completed!\n"); 
    return 0; 
} 

Compile:

g++ TestSockets.cpp -ldl -g -ggdb -o TestSockets 

Config:

ulimit -n 32768 

Некоторые типичные результаты:

  1. Хороший результат ./TestSockets 1500:

    creating 1500 sockets ... 
    created 1500 sockets. 
    testing reading ... 
    readable: 0, unreadable: 1500, skipped: 0, total: 1500 
    testing writing ... 
    writable: 1372, unwritable: 128, skipped: 0, total: 1500 
    closing ... 
    completed! 
    
  2. Плохой результат ./TestSockets 1900:

    creating 1900 sockets ... 
    created 1900 sockets. 
    testing reading ... 
    warning: select() failed: index: 1797, error: 9 
    ...(more lines trimmed) 
    warning: select() failed: index: 1820, error: 9 
    warning: select() failed: index: 1821, error: 22 
    readable: 0, unreadable: 1878, skipped: 0, total: 1900 
    testing writing ... 
    warning: select() failed: index: 1641, error: 9 
    ...(more lines trimmed) 
    warning: select() failed: index: 1660, error: 9 
    warning: select() failed: index: 1661, error: 22 
    writable: 1751, unwritable: 128, skipped: 0, total: 1900 
    closing ... 
    completed! 
    

    Комментарий: потому что 1900> 1751 + 128, то кажется, что стек был поврежден.

  3. Плохой результат ./TestSockets 2000:

    creating 2000 sockets ... 
    created 2000 sockets. 
    testing reading ... 
    Segmentation fault 
    

Подробнее Исследование:

Согласно GdB информации. Кажется, что память стека была повреждена во время бега:

creating 2000 sockets ... 
    created 2000 sockets. 
    testing reading ... 

    Program received signal SIGSEGV, Segmentation fault. 
    0x08048b79 in main (argc=2, argv=0xffffd3b4) at TestSockets.cpp:78 
    78   int socketHandle=socketHandles[i]; 
    (gdb) print socketHandles 
    $1 = (int *) 0x0 
    (gdb) info local 
    socketHandle = 0 
    rset = {fds_bits = {0 <repeats 32 times>}} 
    timeout = {tv_sec = 0, tv_usec = 0} 
    retCode = 0 
    i = 1601 
    socketsNum = 2000 
    unreadableNumber = 1601 
    unwritableNumber = 134514249 
    socketHandles = 0x0 
    createdSocketsNum = 2000 
    readableNumber = 0 
    readingSkippedNumber = 0 
    writableNumber = -136436764 
    writingSkippedNumber = 0 
    (gdb) info stack 
    #0 0x08048b79 in main (argc=2, argv=0xffffd3b4) at TestSockets.cpp:78 
+0

Это выглядит как C, но вы компиляции как C++. Не могли бы вы его выбрать? – Biffen

+1

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

+0

Я пробовал, но я не нашел никакой справки. (gdb) set args 2000 (gdb) run Стартовая программа: ./TestSockets 2000 предупреждение: не удалось загрузить общие библиотечные символы для linux-gate.so.1. Вам нужен «set solib-search-path» или «set sysroot»? создание 2000 гнезд ... создано 2000 гнезд. test reading ... Запрограммированный сигнал SIGSEGV, ошибка сегментации. 0x08048b79 in main() (gdb) info stack # 0 0x08048b79 in main() (gdb) info local Информация отсутствует. – Tony

ответ

4

fd_set ограничен максимальным значением файлового дескриптора (не число дескрипторов файлов, установленных в то же время). Обычно это 1024.

Таким образом, если ваше гнездо значение больше 1023, вы не можете использовать select на нем вообще.

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

+0

Я могу подтвердить! В моем блоке FreeBSD я получил ошибку seg в 1500, но после определения FS_SETSIZE в 2048 я мог успешно работать в 2000 году. –

+0

Каким будет обходной путь для приложения, которому нужно использовать больше сокетов? –

+0

@ M.M, см. Мой ответ. Это обходной пример. – Tony

0

Я решил проблему с головной болью. Fd_set на windows и Linux совершенно разные. В Linux, если дескриптор сокета VALUE больше, чем FD_SETSIZE, в Linux-версии FD_SET будет реализована проблема переполнения. Я делаю обходное решение, чтобы выделить достаточно буфера для fd_set в Linux. таких как,

char rsetBuffer[10240]; 
memset(rsetBuffer,0,10240); 

fd_set& rset=(fd_set&)rsetBuffer; 
FD_ZERO(&rset); 
FD_SET(socketHandle, &rset); 

p.s.Определение fd_set структуры и FD_SET макрос на окнах и Linux:

на окнах:

typedef struct fd_set { 
    u_int fd_count;    /* how many are SET? */ 
    SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ 
} fd_set; 


#define FD_SET(fd, set) do { \ 
    u_int __i; \ 
    for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ 
    if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \ 
     break; \ 
    } \ 
    } \ 
    if (__i == ((fd_set FAR *)(set))->fd_count) { \ 
    if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \ 
     ((fd_set FAR *)(set))->fd_array[__i] = (fd); \ 
     ((fd_set FAR *)(set))->fd_count++; \ 
    } \ 
    } \ 
} while(0) 

на Linux:

/* fd_set for select and pselect. */ 
typedef struct 
    { 
    /* XPG4.2 requires this member name. Otherwise avoid the name 
     from the global namespace. */ 
#ifdef __USE_XOPEN 
    __fd_mask fds_bits[__FD_SETSIZE/__NFDBITS]; 
# define __FDS_BITS(set) ((set)->fds_bits) 
#else 
    __fd_mask __fds_bits[__FD_SETSIZE/__NFDBITS]; 
# define __FDS_BITS(set) ((set)->__fds_bits) 
#endif 
    } fd_set; 

#define __FD_SET(d, set) \ 
    ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d))) 
#define __FD_CLR(d, set) \ 
    ((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d))) 
#define __FD_ISSET(d, set) \ 
    ((__FDS_BITS (set)[__FD_ELT (d)] & __FD_MASK (d)) != 0) 
+0

Это не правильное решение. Он не потерпит крах, но это тоже не сработает. Я написал, почему в моем ответе. Я усвоил это с трудом. Если вы хотите научиться этому тоже, пожалуйста. –

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