2013-10-14 3 views
2

Edit: Вот выход ТСРйитра при запуске программы на OSX:Libevent код не подсоединение или время на OSX Mountain Lion

Matthew-Mitchell:calm-ocean-4924 matt$ sudo tcpdump -X -i lo0 'port 45564' 
Password: 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode 
listening on lo0, link-type NULL (BSD loopback), capture size 65535 bytes 
22:52:41.620969 IP localhost.53685 > localhost.45564: Flags [S], seq 3787197032, win 65535, options [mss 16344,nop,wscale 4,nop,nop,TS val 465807577 ecr 0,sackOK,eol], length 0 
    0x0000: 4500 0040 ba9e 4000 4006 0000 7f00 0001 [email protected]@[email protected] 
    0x0010: 7f00 0001 d1b5 b1fc e1bc 0a68 0000 0000 ...........h.... 
    0x0020: b002 ffff fe34 0000 0204 3fd8 0103 0304 .....4....?..... 
    0x0030: 0101 080a 1bc3 a8d9 0000 0000 0402 0000 ................ 
22:52:41.621062 IP localhost.45564 > localhost.53685: Flags [S.], seq 1362049502, ack 3787197033, win 65535, options [mss 16344,nop,wscale 4,nop,nop,TS val 465807577 ecr 465807577,sackOK,eol], length 0 
    0x0000: 4500 0040 8dac 4000 4006 0000 7f00 0001 [email protected]@[email protected] 
    0x0010: 7f00 0001 b1fc d1b5 512f 39de e1bc 0a69 ........Q/9....i 
    0x0020: b012 ffff fe34 0000 0204 3fd8 0103 0304 .....4....?..... 
    0x0030: 0101 080a 1bc3 a8d9 1bc3 a8d9 0402 0000 ................ 
22:52:41.621075 IP localhost.53685 > localhost.45564: Flags [.], ack 1, win 9186, options [nop,nop,TS val 465807577 ecr 465807577], length 0 
    0x0000: 4500 0034 fdb1 4000 4006 0000 7f00 0001 [email protected]@....... 
    0x0010: 7f00 0001 d1b5 b1fc e1bc 0a69 512f 39df ...........iQ/9. 
    0x0020: 8010 23e2 fe28 0000 0101 080a 1bc3 a8d9 ..#..(.......... 
    0x0030: 1bc3 a8d9        .... 
22:52:41.621086 IP localhost.45564 > localhost.53685: Flags [.], ack 1, win 9186, options [nop,nop,TS val 465807577 ecr 465807577], length 0 
    0x0000: 4500 0034 53bf 4000 4006 0000 7f00 0001 [email protected]@....... 
    0x0010: 7f00 0001 b1fc d1b5 512f 39df e1bc 0a69 ........Q/9....i 
    0x0020: 8010 23e2 fe28 0000 0101 080a 1bc3 a8d9 ..#..(.......... 
    0x0030: 1bc3 a8d9 

У меня довольно большая библиотека с некоторым сетевым кодом с помощью Libevent. Долгое время это работало, но недавно с некоторым новым кодом, который использует сетевой код, он редко работает, потому что соединения не могут быть сделаны. Кроме того, событие тайм-аута соединения не запускается, несмотря на то, что соединение никогда не завершается (что ему нужно). Я попытался разделить код, чтобы привести пример этого не работает. Следующий код не работает на OSX Mountain Lion (по крайней мере):

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <event2/event.h> 
#include <stdbool.h> 
#include <string.h> 
#include <errno.h> 

#ifdef SO_NOSIGPIPE 
#define CB_NOSIGPIPE true 
#else 
#define CB_NOSIGPIPE false 
#define SO_NOSIGPIPE 0 
#endif 

void loopErr(void * vself); 
void loopErr(void * vself){ 
    printf("Loop error\n"); 
    exit(EXIT_FAILURE); 
} 

void timeout(void * vself, void * foo); 
void timeout(void * vself, void * foo){ 
    printf("Timeout\n"); 
    exit(EXIT_FAILURE); 
} 

void didConnect(void * vself, void * foo); 
void didConnect(void * vself, void * foo){ 
    printf("Did connect\n"); 
} 

bool CBSocketAccept(int sock, int * connectionSock); 
bool CBSocketAccept(int sock, int * connectionSock){ 
    *connectionSock = accept(sock, NULL, NULL); 
    if (*connectionSock == -1) 
     return false; 
    // Make socket non-blocking 
    evutil_make_socket_nonblocking(*connectionSock); 
    // Stop SIGPIPE 
    int i = 1; 
    if (CB_NOSIGPIPE) 
     setsockopt(*connectionSock, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)); 
    return true; 
} 

void acceptConn(void * vself, int socket); 
void acceptConn(void * vself, int socket){ 
    int connSock; 
    if (! CBSocketAccept(socket, &connSock)){ 
     printf("Unable to accept a connection.\n"); 
     exit(EXIT_FAILURE); 
    } 
    printf("Did accept\n"); 
} 

typedef struct{ 
    struct event_base * base; 
    void (*onError)(void *); 
    void (*onTimeOut)(void *, void *); /**< Callback for timeouts */ 
    void * communicator; 
    pthread_t loopThread; /**< The thread for the event loop. */ 
    void (*userCallback)(void *); 
    void * userArg; 
}CBEventLoop; 

union CBOnEvent{ 
    void (*i)(void *, int); 
    void (*ptr)(void *, void *); 
}; 

typedef struct{ 
    CBEventLoop * loop; /**< For getting timeout CBLogError */ 
    struct event * event; /**< libevent event. */ 
    union CBOnEvent onEvent; 
    void * peer; 
}CBEvent; 

void * CBStartEventLoop(void * vloop); 
void * CBStartEventLoop(void * vloop){ 
    CBEventLoop * loop = vloop; 
    // Start event loop 
    printf("Starting network event loop.\n"); 
    if(event_base_dispatch(loop->base) == -1){ 
     // Error 
     loop->onError(loop->communicator); 
     return NULL; 
    } 
    // Break from loop. Free everything. 
    event_base_free(loop->base); 
    free(loop); 
    return NULL; 
} 
void event_base_add_virtual(struct event_base *); 
bool CBNewEventLoop(CBEventLoop ** loop, void (*onError)(void *), void (*onDidTimeout)(void *, void *), void * communicator); 
bool CBNewEventLoop(CBEventLoop ** loop, void (*onError)(void *), void (*onDidTimeout)(void *, void *), void * communicator){ 
    struct event_base * base = event_base_new(); 
    // Create dummy event to maintain loop. 
    event_base_add_virtual(base); 
    // Create arguments for the loop 
    *loop = malloc(sizeof(**loop)); 
    (*loop)->base = base; 
    (*loop)->onError = onError; 
    (*loop)->onTimeOut = onDidTimeout; 
    (*loop)->communicator = communicator; 
    // Create thread 
    pthread_create(&(*loop)->loopThread, NULL, CBStartEventLoop, *loop); 
    return loop; 
} 

bool CBSocketBind(int * sock, bool IPv6, uint16_t port); 
bool CBSocketBind(int * sock, bool IPv6, uint16_t port){ 
    struct addrinfo hints, *res, *ptr; 
    // Set hints for the computer's addresses. 
    memset(&hints, 0, sizeof(hints)); 
    hints.ai_flags = AI_PASSIVE; 
    hints.ai_family = IPv6 ? AF_INET6 : AF_INET; 
    hints.ai_socktype = SOCK_STREAM; 
    // Get host for listening 
    char portStr[6]; 
    sprintf(portStr, "%u", port); 
    if (getaddrinfo(NULL, portStr, &hints, &res)) 
     return false; 
    // Attempt to bind to one of the addresses. 
    for(ptr = res; ptr != NULL; ptr = ptr->ai_next) { 
     if ((*sock = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == -1) 
      continue; 
     // Prevent EADDRINUSE 
     int opt = 1; 
     setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 
     if (bind(*sock, ptr->ai_addr, ptr->ai_addrlen) == -1) { 
      printf("Bind gave the error %s for address on port %u.\n", strerror(errno), port); 
      evutil_closesocket(*sock); 
      continue; 
     } 
     break; // Success. 
    } 
    freeaddrinfo(res); 
    if (ptr == NULL) // Failure 
     return false; 
    // Prevent SIGPIPE 
    int i = 1; 
    setsockopt(*sock, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)); 
    // Make socket non-blocking 
    evutil_make_socket_nonblocking(*sock); 
    return true; 
} 

void CBCanAccept(evutil_socket_t sock, short eventNum, void * arg); 
void CBCanAccept(evutil_socket_t sock, short eventNum, void * arg){ 
    CBEvent * event = arg; 
    event->onEvent.i(event->loop->communicator, sock); 
} 

bool CBSocketCanAcceptEvent(CBEvent ** event, CBEventLoop * loop, int sock, void (*onCanAccept)(void *, int)); 
bool CBSocketCanAcceptEvent(CBEvent ** event, CBEventLoop * loop, int sock, void (*onCanAccept)(void *, int)){ 
    *event = malloc(sizeof(**event)); 
    (*event)->loop = loop; 
    (*event)->onEvent.i = onCanAccept; 
    (*event)->event = event_new((*event)->loop->base, sock, EV_READ|EV_PERSIST, CBCanAccept, *event); 
    return true; 
} 

bool CBSocketAddEvent(CBEvent * event, uint32_t timeout); 
bool CBSocketAddEvent(CBEvent * event, uint32_t timeout){ 
    int res; 
    if (timeout) { 
     uint32_t secs = timeout/1000; 
     struct timeval time = {secs, (timeout - secs*1000) * 1000}; 
     res = event_add(event->event, &time); 
    }else 
     res = event_add(event->event, NULL); 
    return ! res; 
} 

bool CBNewSocket(int * sock, bool IPv6); 
bool CBNewSocket(int * sock, bool IPv6){ 
    // You need to use PF_INET for IPv4 mapped IPv6 addresses despite using the IPv6 format. 
    *sock = socket(IPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, 0); 
    if (*sock == -1) 
     return false; 
    // Stop SIGPIPE annoying us. 
    int i = 1; 
    if (CB_NOSIGPIPE) 
     setsockopt(*sock, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof(i)); 
    // Make address reusable 
    setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); 
    // Make socket non-blocking 
    evutil_make_socket_nonblocking(*sock); 
    return true; 
} 

bool CBSocketConnect(int sock, uint8_t * IP, bool IPv6, uint16_t port); 
bool CBSocketConnect(int sock, uint8_t * IP, bool IPv6, uint16_t port){ 
    // Create sockaddr_in6 information for a IPv6 address 
    int res; 
    if (IPv6) { 
     struct sockaddr_in6 address; 
     memset(&address, 0, sizeof(address)); // Clear structure. 
     address.sin6_family = AF_INET6; 
     memcpy(&address.sin6_addr, IP, 16); // Move IP address into place. 
     address.sin6_port = htons(port); // Port number to network order 
     res = connect(sock, (struct sockaddr *)&address, sizeof(address)); 
    }else{ 
     struct sockaddr_in address; 
     memset(&address, 0, sizeof(address)); // Clear structure. 
     address.sin_family = AF_INET; 
     memcpy(&address.sin_addr, IP + 12, 4); // Move IP address into place. Last 4 bytes for IPv4. 
     address.sin_port = htons(port); // Port number to network order 
     res = connect(sock, (struct sockaddr *)&address, sizeof(address)); 
    } 
    if (res < 0 && errno == EINPROGRESS) 
     return true; 
    return false; 
} 

void CBDidConnect(evutil_socket_t socketID, short eventNum, void * arg); 
void CBDidConnect(evutil_socket_t socketID, short eventNum, void * arg){ 
    CBEvent * event = arg; 
    if (eventNum & EV_TIMEOUT) { 
     // Timeout for the connection 
     event->loop->onTimeOut(event->loop->communicator, event->peer); 
    }else{ 
     int optval = -1; 
     socklen_t optlen = sizeof(optval); 
     getsockopt(socketID, SOL_SOCKET, SO_ERROR, &optval, &optlen); 
     if (optval){ 
      // Act as timeout 
      printf("Connection error: %s\n", strerror(optval)); 
      event->loop->onTimeOut(event->loop->communicator, event->peer); 
     }else 
      // Connection successful 
      event->onEvent.ptr(event->loop->communicator, event->peer); 
    } 
} 

bool CBSocketDidConnectEvent(CBEvent ** event, CBEventLoop * loop, int sock, void (*onDidConnect)(void *, void *), void * peer); 
bool CBSocketDidConnectEvent(CBEvent ** event, CBEventLoop * loop, int sock, void (*onDidConnect)(void *, void *), void * peer){ 
    *event = malloc(sizeof(**event)); 
    (*event)->loop = loop; 
    (*event)->onEvent.ptr = onDidConnect; 
    (*event)->peer = peer; 
    (*event)->event = event_new((*event)->loop->base, sock, EV_TIMEOUT|EV_WRITE, CBDidConnect, *event); 
    return true; 
} 

int main(int argc, const char * argv[]){ 
    int listeningSocket, connSocket; 
    CBEvent *acceptEvent, *connectEvent; 
    CBEventLoop *listeningEventLoop, *connEventLoop; 
    if (!CBNewEventLoop(&listeningEventLoop, loopErr, timeout, NULL) 
     || !CBNewEventLoop(&connEventLoop, loopErr, timeout, NULL)){ 
     printf("Unable to start event loops."); 
     return EXIT_FAILURE; 
    } 
    if (!CBSocketBind(&listeningSocket, false, 45564)){ 
     printf("Unable to bind a socket for listening"); 
     return EXIT_FAILURE; 
    } 
    if (!CBSocketCanAcceptEvent(&acceptEvent, listeningEventLoop, listeningSocket, acceptConn)) { 
     printf("Unable to create an accept event"); 
     return EXIT_FAILURE; 
    } 
    if(!CBSocketAddEvent(acceptEvent, 0)){ 
     printf("Unable to add an accept event\n"); 
     return EXIT_FAILURE; 
    } 
    if (listen(listeningSocket, 1) == -1){ 
     printf("Unable to start listening\n"); 
     return EXIT_FAILURE; 
    } 
    // Make socket 
    if (!CBNewSocket(&connSocket, false)){ 
     printf("Socket create fail\n"); 
     return EXIT_FAILURE; 
    } 
    // Add event for connection 
    if (!CBSocketDidConnectEvent(&connectEvent, connEventLoop, connSocket, didConnect, NULL)) { 
     printf("Couldn't create did connect event\n"); 
     return EXIT_FAILURE; 
    } 
    if (!CBSocketAddEvent(connectEvent, 1000)) { 
     printf("Couldn't add connect event\n"); 
     return EXIT_FAILURE; 
    } 
    // Connect 
    if (!CBSocketConnect(connSocket, (uint8_t []){0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 127, 0, 0, 1}, false, 45564)){ 
     printf("Couldn't connect\n"); 
     return EXIT_FAILURE; 
    } 
    pthread_exit(NULL); 
    return 0; 
} 

Когда я запускаю это я получаю:

Starting network event loop. 
Starting network event loop. 

И ничего другого. Никакое соединение, чтобы принять, без тайм-аута, ничего. Однако я могу подтвердить, что это работает на Linux Mint 13. Я также попробовал другой номер порта, но не повезло.

Редактировать: Похоже, что у меня возникает аналогичная проблема с libev. Я просто попробовал переопределить код с libev, и у меня такая же проблема. Однако я не пытался изменить маленький пример выше на libev.

ответ

3

С этим кодом связано несколько проблем.

Во-первых, CBNewEventLoop всегда возвращает true, потому что он возвращает (bool)loop (который является адресом одной из переменных стека в основном). Кроме того, единственная ошибка, которую вы, похоже, обнаружите, вернув (bool)(*loop), будет сбоем malloc, но если malloc не удастся, вам нужно проверить его результат до записи до *loop.

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

И, наконец, довольно редко требуется больше одного цикла событий за раз. У вас должен быть один цикл событий для процесса и использовать его для прослушивания и подключения.

+0

Спасибо за ответ. Ваша первая точка верна, но это не имеет отношения к этой проблеме. Ваш второй пункт, однако, находится на месте. Я забыл использовать 'evthread_use_pthreads();' Мне нужно вызвать это, чтобы функции libevent стали потокобезопасными и, таким образом, позволили мне установить соединение в основном потоке. Я мог бы, конечно, сделать это в сетевом потоке. Причина, по которой у меня есть два цикла событий, заключается в том, что это тест, а не фактическое приложение. –

+1

В отличие от конечной точки Бена, при запуске вашего (серверного) приложения на многоядерных машинах необходимо использовать несколько циклов событий для получения максимально возможной производительности. Попробуйте запустить одиночный цикл событий на 8-ядерном компьютере по сравнению с восемью циклами событий, вы увидите огромную разницу. – Corehacker

+0

Хорошая точка @ Корейер; Я привык к миру Python, где потоки на самом деле не являются решением, и вы должны иметь один цикл событий для каждого процесса, но на C вы можете использовать потоки вместо процессов и получать одинаковый результат. –

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