Unlimited Blade Works

Unaware of loss.Nor aware of gain.

导航

可在网络不好的环境下运行的ENet示例程序

  本文是ENet的示例程序。因为仅是作为示例所以程序并没有做支持多请求处理。

  一开始按照教程写程序,在网络好的情况下(本机和局域网),可以成功完成传输,但是网络差时就不行了。

  用top监视时,发现程序消耗内存增长极快,但使用valgrind检查发现并无内存泄漏。于是怀疑ENet的可靠传输机制是当一个packet没发出时,就将这个packet保存在内存中,成功发出后才会被释放掉。当client被destroy时,这些包如果还没发出,就会一起被释放掉,所以文件没传完就结束了。于是考虑是不是可以让一个packet发完后再发下一个。(因为程序里几乎没有其它的处理过程,如果有的话,或许执行一遍后packet早就成功发出了--未测试,猜测而已)。

查看ENetPacket的定义

 

typedef struct _ENetPacket
{
   size_t                               referenceCount;  
   enet_uint32                     flags;           
   enet_uint8 *                    data;            
   size_t                               dataLength;      
   ENetPacketFreeCallback  freeCallback;    
} ENetPacket;

  发现有一个当packet被free时的回调函数:

ENetPacketFreeCallback  freeCallback;

  于是用这个回调函数来检测。

 

 

  采用这个方法后,内存果然不再增长了,但是传输时间长后,client端(发送端)会报超时错误。发送失败。

  于是想到两种方法,断点续传或将超时设为无限。这里为方便采用第二种。

  于是查看enet的超时检查函数在protocol.c里发现

 

static int
enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event)
{
    ENetOutgoingCommand * outgoingCommand;
    ENetListIterator currentCommand, insertPosition;

    currentCommand = enet_list_begin (& peer -> sentReliableCommands);
    insertPosition = enet_list_begin (& peer -> outgoingReliableCommands);

    while (currentCommand != enet_list_end (& peer -> sentReliableCommands))
    {
       outgoingCommand = (ENetOutgoingCommand *) currentCommand;

       currentCommand = enet_list_next (currentCommand);

       if (ENET_TIME_DIFFERENCE (host -> serviceTime, outgoingCommand -> sentTime) < outgoingCommand -> roundTripTimeout)
         continue;

       if (peer -> earliestTimeout == 0 ||
           ENET_TIME_LESS (outgoingCommand -> sentTime, peer -> earliestTimeout))
         peer -> earliestTimeout = outgoingCommand -> sentTime;

       if (peer -> earliestTimeout != 0 &&
             (ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= ENET_PEER_TIMEOUT_MAXIMUM ||
               (outgoingCommand -> roundTripTimeout >= outgoingCommand -> roundTripTimeoutLimit &&
                 ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= ENET_PEER_TIMEOUT_MINIMUM)))
       {
          enet_protocol_notify_disconnect (host, peer, event);

          return 1;
       }

       if (outgoingCommand -> packet != NULL)
         peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength;
          
       ++ peer -> packetsLost;

       outgoingCommand -> roundTripTimeout *= 2;

       enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));

       if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) &&
           ! enet_list_empty (& peer -> sentReliableCommands))
       {
          outgoingCommand = (ENetOutgoingCommand *) currentCommand;

          peer -> nextTimeout = outgoingCommand -> sentTime + outgoingCommand -> roundTripTimeout;
       }
    }
    
    return 0;
}

  其中有一句 

outgoingCommand -> roundTripTimeout *= 2;

 

  把这句注释掉后,重新编译安装enet。

  之后再运行就可正常传输了。

  下面是程序说明:

安装enet的方法很简单按照源码安装三步曲即可 

 

./configure --prefix=安装路径
make
make install

  现在从ENet官网上下载的源码有两个版本enet-1.2.2和enet-1.3.0。1.2.2和1.3.0的说明文档和教程可以分别在源码包的docs目录下找到。

  两者的接口有些许变化。例如enet_host_create()和enet_host_connect()。

  程序里对通过判断版本号,对两者都支持。

  下面代码在gcc4.4.3下可以编译通过。编译时要加-lenet。

  程序里用了不少全局变量,这个不是好的编程习惯,这里仅作示例,为了方便而已。

  单文件编译命令示例(假设enet装在/usr/local/enetlib1.3.0/下)

 

$ gcc -o ENetDemo ENetDemo.c -I /usr/local/enetlib1.3.0/include/ -L /usr/local/enetlib1.3.0/lib/ -lenet

  以下为程序源码。程序的使用说明参看main函数前的注释。

/**
 * @file ENetDemo.c
 * @brief A demo of ENet. Clinet just sends one file. And server just handles one file.
 * @author allen
 * @date 2010-12-9
 * @other For convenient I used many global variables. And I know it's not good.
 */

#define _LARGE_FILES
#undef  _FILE_OFFSET_BITS   
#define _FILE_OFFSET_BITS   64  
#include <enet/enet.h>
#include <stdio.h>
#include<string.h>
#include<stdlib.h>

char * file_name;
int packet_size;
int per_wait_time;
int my_flag = 1;
/**
 * @brief this is a callback function. I want to use it check whether the packet was sent.
 */
ENetPacketFreeCallback packetCallback(ENetPacket * pac) {
    my_flag = 0;
};
/**
 * @brief the enetServer main funtion
 * @param port the port enet server bind to.
 */
void enetServer(int port) {
    ENetAddress address;
    ENetHost * server;

    /* Bind the server to the default localhost.     */
    /* A specific host address can be specified by   */
    /* enet_address_set_host (& address, "x.x.x.x"); */

    address.host = ENET_HOST_ANY;
    /* Bind the server to port 1234. */
    address.port = port;
#if ENET_VERSION ==  ENET_VERSION_CREATE(1,2,2)
    server = enet_host_create(& address /* the address to bind the server host to */,
            32 /* allow up to 32 clients and/or outgoing connections */,
            0 /* assume any amount of incoming bandwidth */,
            0 /* assume any amount of outgoing bandwidth */);
#elif ENET_VERSION ==  ENET_VERSION_CREATE(1,3,0)
    server = enet_host_create(&address/* the address to bind the server host to */,
            32 /* allow up to 32 clients and/or outgoing connections*/,
            2 /*allow up to 2 channels to be used, 0 and 1 */,
            0 /*assume any amount of incoming bandwidth*/,
            0 /* assume any amount of outgoing bandwidth */);
#endif
    if (server == NULL) {
        fprintf(stderr,
                "An error occurred while trying to create an ENet server host.\n");
        exit(EXIT_FAILURE);
    }

    ENetEvent event;
    char * filename = file_name;
    FILE *fp;
    puts(file_name);
    unsigned int sumsize = 0;
    /* Wait up to 5000 milliseconds for an event. */
    while (enet_host_service(server, & event, 5000) >= 0) {

        /*  printf("Type %d\n", event.type);*/
        switch (event.type) {
            case ENET_EVENT_TYPE_CONNECT:
                printf("A new client connected from %x:%u.\n",
                        event.peer -> address.host,
                        event.peer -> address.port);

                /* Store any relevant client information here. */
                /*  event.peer -> data = "Client information";*/
                static unsigned int num = 0;
                ENetAddress remote = event.peer->address; /*remote address*/
                char ip[256];
                static char peerInfo[256];
                enet_address_get_host_ip(&remote, ip, 256);
                num++;
                printf("ip:%s has connected. No.%u\n", ip, num);
                sprintf(peerInfo, "ip:%s No.%u connection.", ip, num);
                event.peer->data = (void*) peerInfo;
                break;

            case ENET_EVENT_TYPE_RECEIVE:
                printf("%s\n", (char*) (event.peer->data));
                if ((fp = fopen(filename, "ab")) == NULL) {
                    fprintf(stderr, "enetServer: file open error");
                    enet_packet_destroy(event.packet);
                    enet_host_destroy(server);
                    return;
                }

                size_t writeBytes = fwrite(event.packet -> data, sizeof (char), event.packet -> dataLength, fp);
                if (writeBytes != event.packet -> dataLength) {
                    fprintf(stderr, "enetServer: write to file error");
                }
                sumsize += writeBytes;
                printf("%d\n", writeBytes);
                fclose(fp);
                /* Clean up the packet now that we're done using it. */
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                printf("%s disconected.\n", (char*) (event.peer -> data));
                /* Reset the peer's client information. */
                event.peer -> data = NULL;
                printf("get%d\n", sumsize);
                enet_host_destroy(server);
                return;
        }
    }
    enet_host_destroy(server);
}

/**
 * @brief this function used to send one file by enet.
 * @param fileName the filename
 * @param client the enethost
 * @param peer the target peer to send
 * @return 0 means success. 1 means failure.
 */

int sendFile(char * fileName, ENetHost * client, ENetPeer *peer) {
    const int failureCode = 1;
    const int successCode = 0;
    ENetEvent event;
    char * datas;
    int dataSize = packet_size;
    unsigned int sum = 0;
    FILE *fp;
    if ((fp = fopen(fileName, "rb")) == NULL) {
        fprintf(stderr, "sendFile: file read error");
        return failureCode;
    }

    datas = (char*) malloc(dataSize);
    if (datas == NULL) {
        fprintf(stderr, "sendFile: memory malloc error");
        fclose(fp);
        return failureCode;
    }

    size_t readBytes = 0;
    ENetPacket *packet;
    while (!feof(fp)) {
        readBytes = fread(datas, sizeof (char), dataSize, fp);
        packet = enet_packet_create(datas, readBytes, ENET_PACKET_FLAG_RELIABLE); /*create reliable packet*/
        if (packet == NULL) {
            fprintf(stderr, "sendFile:: packet error");
            fclose(fp);
            free(datas);
            return failureCode;
        }
        printf("sendFile: packet:%d\n", packet -> dataLength);

        int errorcode = 0;

        packet->freeCallback = (ENetPacketFreeCallback) packetCallback;
        if ((errorcode = enet_peer_send(peer, 0, packet)) != 0) {
            fprintf(stderr, "sendFile: enet_peer_send errorcode %d\n", errorcode);
            fclose(fp);
            free(datas);
            fprintf(stderr, "sendFile: sumsize%u\n", sum);
            return failureCode;
        }
        printf("sendFile: enet_peer_send errorcode %d\n", errorcode);

        do {
            errorcode = enet_host_service(client, &event, per_wait_time);
            if (errorcode > 0) {
                fprintf(stderr, "enetClient: eventtype:%d\n", event.type);
            }
        } while (my_flag != 0);
        my_flag = 1;
        printf(" service errorcode %d\n", errorcode);
        sum += readBytes;
        printf("sumSize%u\n", sum);
    }
    fclose(fp);
    free(datas);
    printf("total file sumsize%d\n", sum);
    return successCode;
}
/**
 * @brief the enetClient main funtion
 * @param hostname target IP or hostname
 * @param port target port
 */
void enetClient(char * hostname, int port) {
    ENetHost * client;
#if ENET_VERSION ==  ENET_VERSION_CREATE(1,2,2)
    client = enet_host_create(NULL /* create a client host */,
            1 /* only allow 1 outgoing connection */,
            57600 / 8 /* 56K modem with 56 Kbps downstream bandwidth */,
            14400 / 8 /* 56K modem with 14 Kbps upstream bandwidth */);
#elif ENET_VERSION ==  ENET_VERSION_CREATE(1,3,0)
    client = enet_host_create(NULL /* create a client host */,
            1 /*only allow 1 outgoing connection */,
            2 /*allow up to 2 channels to be used, 0 and 1  */,
            57600 / 8 /*56K modem with 56 Kbps downstream bandwidth */,
            14400 / 8 /* 56K modem with 14 Kbps upstream bandwidth */);
#endif

    if (client == NULL) {
        fprintf(stderr,
                "enetClient: An error occurred while trying to create an ENet client host.\n");
        /*exit(EXIT_FAILURE);*/
        return;
    }

    ENetAddress address;
    ENetEvent event;
    ENetPeer *peer;

    /* Connect to host. */
    enet_address_set_host(& address, hostname);
    address.port = port;


#if ENET_VERSION ==  ENET_VERSION_CREATE(1,2,2)
    /* Initiate the connection, allocating the two channels 0 and 1. */
    peer = enet_host_connect(client, & address, 2);
#elif ENET_VERSION ==  ENET_VERSION_CREATE(1,3,0)
    /* Initiate the connection, allocating the two channels 0 and 1. */
    peer = enet_host_connect(client, & address, 2, 0);
#endif
    if (peer == NULL) {
        fprintf(stderr,
                "enetClient: No available peers for initiating an ENet connection.\n");
        /*exit(EXIT_FAILURE);*/
        return;
    }

    /* Wait up to 10 seconds for the connection attempt to succeed. */
    if (enet_host_service(client, &event, 10000) > 0 &&
            event.type == ENET_EVENT_TYPE_CONNECT) {
        printf("enetClient: Connection %s:%d succeeded.", hostname, port);
        /*send a file*/
        int errCode = sendFile(file_name, client, peer);
        if (errCode != 0) {
            puts("send error\n");
        }
        /*send file end*/
    } else {
        /* Either the 10 seconds are up or a disconnect event was */
        /* received. Reset the peer in the event the 5 seconds   */
        /* had run out without any significant event.            */
        enet_peer_reset(peer);
        printf("enetClient: Connection %s:%d failed.", hostname, port);
    }

    enet_peer_disconnect(peer, 0);
    /* Allow up to 3 seconds for the disconnect to succeed and drop any packets received packets. */
    while (enet_host_service(client, & event, 3000) > 0) {
        switch (event.type) {
            case ENET_EVENT_TYPE_RECEIVE:
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                puts("Disconnection succeeded.");
                enet_host_destroy(client);
                return;
        }
    }
    /* We've arrived here, so the disconnect attempt didn't */
    /* succeed yet.  Force the connection down.             */
    enet_peer_reset(peer);
    puts("Disconnection force succeeded.");
    enet_host_destroy(client);
}
/**
 * @brief as server : ENetDemo s port filename
 *                    port means the port server to bind.
 *                    filename is the file server recieved save as.
 *        as client : ENetDemo c IP port filename packetsize perwaittime(ms)
 *                    IP is the target server IP(hostname)
 *                    port the target server listened.
 *                    filename the file client send
 *                    packetsize the client send per time.
 *                    perwaittime (please see the enet_host_service() )
 */
int main(int argc, char ** argv) {
#if ENET_VERSION ==  ENET_VERSION_CREATE(1,2,2)
    /* Initiate the connection, allocating the two channels 0 and 1. */
    printf("Enet Version 1.2.2\n");
#elif ENET_VERSION ==  ENET_VERSION_CREATE(1,3,0)
    /* Initiate the connection, allocating the two channels 0 and 1. */
    printf("Enet Version 1.3.0\n");
#endif
    if (argc < 4) {
        fprintf(stderr, "Wrong Format. ENetDemo [c IP port filename packetsize perwaittime(ms)] [ s port filename] \n");
        return EXIT_FAILURE;
    }

    int err = 0;
    if ((err = enet_initialize()) != 0) {
        fprintf(stderr, "An error occurred while initializing ENet %d.\n", err);
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);
    if (strcmp(argv[1], "s") == 0) {
        puts("SERVER");
        file_name = argv[3];
        puts(file_name);
        enetServer(atoi(argv[2]));
    }
    if (strcmp(argv[1], "c") == 0) {
        puts("CLIENT");
        if (argc < 7) {
            fprintf(stderr, "Wrong Format. ENetDemo [c IP port filename packetsize perwaittime(ms)] [ s port filename] \n");
            return EXIT_FAILURE;
        }
        file_name = argv[4];
        printf("send file %s\n", file_name);
        packet_size = atoi(argv[5]);
        per_wait_time = atoi(argv[6]);
        enetClient(argv[2], atoi(argv[3]));
    }
    int endPause;
    puts("enter any key to end\n");
    scanf("%d", &endPause);
    return 0;

}

posted on 2010-12-10 11:00  飞鸿无月  阅读(1888)  评论(4编辑  收藏  举报