可在网络不好的环境下运行的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; }