可在网络不好的环境下运行的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;
}
浙公网安备 33010602011771号