Windows套接字(UDP) - 客户端TIMEOUT问题

时间:2015-11-11 17:31:44

标签: c windows sockets udp timeout

我有一个服务器和一个客户端(代码发布在下面) 我正在使用客户端程序中的 SO_RCVTIMEO 超时选项测试 setsockopt 功能,它似乎正在编译中。 过程是:服务器绑定到套接字以进行读取 - 客户端向其发送消息 - 服务器显示消息并将其重新发送给发送它的客户端 - 最后,客户端显示从服务器收到的消息
因此,要在X毫秒数之后检查超时是否实际失败(仅当客户端等待从服务器发回消息时才使用超时),我在服务器之前插入了一个Sleep(miliseconds)函数。将消息重新发送到客户端,以平衡客户端和服务器之间的这种延迟。
因此,当使用Sleep(0)进行测试并且在0处超时时,一切都完美同步,但是如果我将值提升到两端的10 ms,结果应该与0相同,但实际上,一切都是按计划工作,除非客户端等待服务器的响应,它超时,尽管两端的延迟完全相同。
作为最后一个例子:如果服务器上的睡眠是10毫秒,并且客户端上的超时是11,甚至15以留下更多空间,那么会发生什么,服务器接收消息,等待10毫秒,重新发送它,客户端是等待另外5ms完成15ms,实际上超时。更糟糕的是,服务器在完成整个过程后恢复了它的初始等待响应循环,不知何故给我一个错误的recvfrom。
请原谅这篇庞大的文字,但如果不遗漏一些细节,就很难总结所有这些内容。

Client.c(忽略广播,不使用)

#include <winsock.h>
#include <stdio.h>

#define SERV_HOST_ADDR "127.0.0.1"
#define SERV_UDP_PORT  6000

#define PORT_MAX 7000
#define PORT_MIN 1000

#define NUM_IT  10

#define BUFFERSIZE     4096

void Abort(char *msg);

/*________________________________ main _______________________________________
*/

int main(int argc, char *argv[])
{

    SOCKET sockfd;
    int msg_len, iResult, nbytes, addrlength, local_port;
    struct sockaddr_in serv_addr, local;
    char buffer[BUFFERSIZE];
    WSADATA wsaData;
    //broadcast
    int broadcast, broadcast_loop;
    //timeout
    DWORD timeout;// = TIMEOUT;

    /*========================= TESTA A SINTAXE =========================*/

    if (argc != 9){
        fprintf(stderr, "(%d argumentos) Sintaxe: %s -msg \"msg\" -ip ip -port port -timeout microsegundos\n (ordem arbitraria)\n", argc, argv[0]);
        exit(EXIT_FAILURE);
    }

    //tratar os argumentos//
    int pos_msg, pos_port, pos_ip, pos_timeout, i;
    for (i = 0; i < argc; i++)
    {
        if (strcmp(argv[i], "-msg") == 0)
            pos_msg = i + 1;
        if (strcmp(argv[i], "-ip") == 0)
            pos_ip = i + 1;
        if (strcmp(argv[i], "-port") == 0)
            pos_port = i + 1;
        if (strcmp(argv[i], "-timeout") == 0)
            pos_timeout = i + 1;
    }

    timeout = atol((char*)(LPCTSTR)argv[pos_timeout]); //converter timeout STRING para DWORD

    if (strcmp(argv[pos_ip], "255.255.255.255") == 0)
    {
        broadcast_loop = 1;
        broadcast = 1;
    }
    else
    {
        broadcast_loop = 0;
        broadcast = 0;
    }

    /*=============== INICIA OS WINSOCKS ==============*/

    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        getchar();
        exit(1);
    }

    /*=============== CRIA SOCKET PARA ENVIO/RECEPCAO DE DATAGRAMAS ==============*/

    sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sockfd == INVALID_SOCKET)
        Abort("Impossibilidade de criar socket");

    /*================ CONFIGURA O SOCKET PARA TIMEOUT DE RECEPCAO ==============*/

    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));

    //this call is what allows broadcast packets to be sent:
    if (broadcast_loop == 1)
    {
        if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (char *)&broadcast, sizeof broadcast) == -1)
        {
            perror("setsockopt (SO_BROADCAST)");
            exit(1);
        }
    }


    /*================= PREENCHE ENDERECO DO SERVIDOR ====================*/

    memset((char*)&serv_addr, 0, sizeof(serv_addr)); /*Coloca a zero todos os bytes*/
    serv_addr.sin_family = AF_INET; /*Address Family: Internet*/
    //serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR); /*IP no formato "dotted decimal" => 32 bits*/
    serv_addr.sin_addr.s_addr = inet_addr(argv[pos_ip]);
    //serv_addr.sin_port = htons(SERV_UDP_PORT); /*Host TO Netowork Short*/
    serv_addr.sin_port = htons(atoi(argv[pos_port]));

    /*====================== ENVIA MENSAGEM AO SERVIDOR ==================*/

    msg_len = strlen(argv[pos_msg]);

    if (broadcast_loop == 1)
    {
        //loop sem esperar pelo reenvio
        for (int i = 0; i < NUM_IT; i++)
        {
            for (int pt = PORT_MIN; pt <= PORT_MAX; pt++)
            {
                serv_addr.sin_port = htons(pt);
                if (sendto(sockfd, argv[pos_msg], msg_len, 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
                    Abort("SO nao conseguiu aceitar o datagram");
                printf(" -Enviada mensagem %d (port: %d)\n", i, pt);

                //ir buscar a porta gerada automaticamente pare este socket e mostrá-la
                addrlength = sizeof(local);
                if (getsockname(sockfd, (struct sockaddr *)&local, &addrlength) == 0 && local.sin_family == AF_INET && addrlength == sizeof(local))
                {
                    local_port = ntohs(local.sin_port);
                }

                printf("<CLI1>Mensagem enviada (port: %d) ... aguardando pelo seu reenvio.\n", local_port);
            }

            //////// ESPERAR QUE MESMA MENSAGEM SEJA REENVIADA PELO SERVIDOR //////
            ////nbytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
            ////memset((char*)&serv_addr, 0, sizeof(serv_addr));
            //addrlength = sizeof(serv_addr);
            //nbytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&serv_addr, &addrlength);

            //if (nbytes == SOCKET_ERROR)
            //  Abort("Erro na recepcao da mensagem");

            //buffer[nbytes] = '\0'; /*Termina a cadeia de caracteres recebidos com '\0'*/

            //printf("\n<CLI1>Mensagem recebida {%s}\n", buffer);

            //if (strcmp(inet_ntoa(serv_addr.sin_addr), SERV_HOST_ADDR) == 0 && ntohs(serv_addr.sin_port) == SERV_UDP_PORT)
            //  printf(" -Mensagem enviada pelo servidor -> %s:%d\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
            //else
            //  printf(" -Mensagem NAO enviada pelo servidor -> %s:%d\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));

        }
    }
    else
    {
        if (sendto(sockfd, argv[pos_msg], msg_len, 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
            Abort("SO nao conseguiu aceitar o datagram");

        //ir buscar a porta gerada automaticamente pare este socket e mostrá-la
        addrlength = sizeof(local);
        if (getsockname(sockfd, (struct sockaddr *)&local, &addrlength) == 0 && local.sin_family == AF_INET && addrlength == sizeof(local))
        {
            local_port = ntohs(local.sin_port);
        }

        printf("<CLI1>Mensagem enviada (port: %d) ... aguardando pelo seu reenvio (timeout).\n", local_port);

        ////// ESPERAR QUE MESMA MENSAGEM SEJA REENVIADA PELO SERVIDOR //////
        //nbytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
        //memset((char*)&serv_addr, 0, sizeof(serv_addr));
        addrlength = sizeof(serv_addr);
        nbytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&serv_addr, &addrlength);

        if (nbytes == SOCKET_ERROR)
        if (WSAGetLastError() != WSAETIMEDOUT)
            Abort("Erro na recepcao da mensagem");
        else
            Abort("Timeout de recepcao");


        buffer[nbytes] = '\0'; /*Termina a cadeia de caracteres recebidos com '\0'*/

        printf("\n<CLI1>Mensagem recebida {%s}\n", buffer);

        if (strcmp(inet_ntoa(serv_addr.sin_addr), SERV_HOST_ADDR) == 0 && ntohs(serv_addr.sin_port) == SERV_UDP_PORT)
            printf(" -Mensagem enviada pelo servidor -> %s:%d\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));
        else
            printf(" -Mensagem NAO enviada pelo servidor -> %s:%d\n", inet_ntoa(serv_addr.sin_addr), ntohs(serv_addr.sin_port));

    }

    /*========================= FECHA O SOCKET ===========================*/

    closesocket(sockfd);

    printf("\n");
    system("Pause");
    exit(EXIT_SUCCESS);
}

/*________________________________ Abort________________________________________
Mostra uma mensagem de erro e o código associado ao ultimo erro com Winsocks.
Termina a aplicacao com "exit status" a 1 (constante EXIT_FAILURE)
________________________________________________________________________________*/

void Abort(char *msg)
{

    fprintf(stderr, "<CLI1>Erro fatal: <%s> (%d)\n", msg, WSAGetLastError());
    exit(EXIT_FAILURE);

}


和Server.c

#include <stdio.h>
#include <winsock.h>
#include <Windows.h> //Sleep()

#define SERV_UDP_PORT 6000
#define BUFFERSIZE     4096

void Abort(char *msg);

/*________________________________ main ________________________________________
*/

int main(int argc, char *argv[])
{
    SOCKET sockfd;
    int iResult, nbytes, msg_len, len;
    struct sockaddr_in serv_addr, cli_addr;
    char buffer[BUFFERSIZE];
    WSADATA wsaData;

    /*========================= TESTA A SINTAXE =========================*/

    if (argc != 5){
        fprintf(stderr, "(%d argumentos) Sintaxe: %s -port port -delay delay\n", argc, argv[0]);
        exit(EXIT_FAILURE);
    }

    //tratar os argumentos//
    int pos_port, pos_delay, i;
    for (i = 0; i < argc; i++)
    {
        if (strcmp(argv[i], "-port") == 0)
            pos_port = i + 1;
        if (strcmp(argv[i], "-delay") == 0)
            pos_delay = i + 1;
    }

    /*=============== INICIA OS WINSOCKS ==============*/

    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        getchar();
        exit(1);
    }

    /*============ CRIA O SOCKET PARA RECEPCAO/ENVIO DE DATAGRAMAS UDP ============*/

    if ((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
        Abort("Impossibilidade de abrir socket");

    /*=============== ASSOCIA O SOCKET AO  ENDERECO DE ESCUTA ===============*/

    /*Define que pretende receber datagramas vindos de qualquer interface de
    rede, no porto pretendido*/

    memset((char*)&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET; /*Address Family: Internet*/
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  /*Host TO Network Long*/
    serv_addr.sin_port = htons(atoi(argv[pos_port]));  /*Host TO Network Short*/

    /*Associa o socket ao porto pretendido*/

    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
        Abort("Impossibilidade de registar-se para escuta");


    /*================ PASSA A ATENDER CLIENTES INTERACTIVAMENTE =============*/

    while (1){

        fprintf(stderr, "<SER1>Esperando datagram...\n");

        len = sizeof(cli_addr);
        //recieve from, tem agora de saber de quem recebeu, para reenviar
        nbytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&cli_addr, &len);

        if (nbytes == SOCKET_ERROR)
            Abort("Erro na recepcao de datagrams");

        buffer[nbytes] = '\0'; /*Termina a cadeia de caracteres recebidos com '\0'*/
        printf("<SER1>Mensagem recebida {%s} de %s:%d\n", buffer, inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));

        ////// REENVIAR A MENSAGEM AO CLIENTE //////

        msg_len = strlen(buffer);
        Sleep(atoi(argv[pos_delay])); //sleep de 'delay' milisegundos
        if (sendto(sockfd, buffer, msg_len, 0, (struct sockaddr*)&cli_addr, sizeof(cli_addr)) == SOCKET_ERROR)
            Abort("SO nao conseguiu aceitar o datagram");

        printf("<SER1>Mensagem reenviada.\n\n");

    }
    system("Pause");
}

/*________________________________ Abort________________________________________
Mostra uma mensagem de erro e o código associado ao ultimo erro com Winsocks.
Termina a aplicacao com "exit status" a 1 (constante EXIT_FAILURE)
________________________________________________________________________________*/

void Abort(char *msg)
{

    fprintf(stderr, "<SER1>Erro fatal: <%s> (%d)\n", msg, WSAGetLastError());
    printf("\n");
    system("Pause");
    exit(EXIT_FAILURE);

}


如果有人能够理解所有这些帖子并提供一些有用的答案,我将非常感激。

0 个答案:

没有答案