本「The Linux Programming Interface」には、次のような関数 : readn があります。
ssize_t readn(int fd, void *buffer, size_t n)
{
ssize_t numRead; /* # of bytes fetched by last read() */
size_t totRead; /* Total # of bytes read so far */
char *buf;
buf = buffer; /* No pointer arithmetic on "void *" */
for (totRead = 0; totRead < n; ) {
numRead = read(fd, buf, n - totRead);
if (numRead == 0) /* EOF */
return totRead; /* May be 0 if this is first read() */
if (numRead == -1) {
if (errno == EINTR)
continue; /* Interrupted --> restart read() */
else
return -1; /* Some other error */
}
totRead += numRead;
buf += numRead;
}
return totRead; /* Must be 'n' bytes if we get here */
}
ソケット クライアントは、最初に : 0123 を送信して、メッセージが 123 バイトであることをソケット サーバーに伝えます。次のように readn を使用します。
while(1){
iRead = readn(fd,buffer,4) ;
if(iRead <= 0)
break ;
buffer[4]=0x00;
iMsglen = atoi(buffer) ;
iRead = readn(fd,buffer,iMsglen) ;
if(iRead <= 0)
break ;
//do something for buffer
} //while
ソケット クライアントが最初に "0123" をソケット サーバーに送信するが、"01" と "23" という 2 つのパッケージで配信される場合、readn 関数 readn(fd,buffer,4) は "01" と " 23" を "0123" に変更して 4 を返すと、アプリケーションは、その後に続くメッセージの長さが 123 バイトであることを認識します!! readn は非常に便利な機能です...
後で libevent 2.0 tutorail をググったところ、次の機能を持つエコー サーバーがあります。
void read_cb(struct bufferevent *bev, void *arg)
{
#define MAX_LINE 256
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
line[n] = '\0';
printf("fd=%u, read line: %s\n", fd, line);
bufferevent_write(bev, line, n);
}
}
関数 read_cb は、上記の readn のように各ファイル記述子のバッファを管理する代わりに、ソケット fd 受信メッセージ イベントが発生した場合に呼び出されます。それを行うより簡単な方法はありますか? つまり、次のような関数名があります。
bufferevent_readn(bev, line, 4)
私の例のネットワークパッケージ「01」がサーバーに送信され、「23」に続いて、bufferevent_readn の 3 番目のパラメーターが 4 であるため、受信した「01」は、4 バイトすべてを受信するまで、今回はイベントを無視する可能性があります。クライアントは、libevent の API で readn 関数を構築するように、イベントを発生させます !!!
任意の提案、コメントをお待ちしております !!
編集 :
libevent のドキュメントを詳しく調べてみると、libevent は本当にすごいと思います!!
http://www.wangafu.net/~nickm/libevent-book/Ref6_bufferevent.html
次のソースに示すように、適切な仕事をする関数がいくつかあります。
bufferevent_setwatermark と evbuffer_get_length と evbuffer_drain はまさに私が必要としているものです...素晴らしい仕事をありがとう!!!
struct info {
const char *name;
size_t total_drained;
};
void read_callback(struct bufferevent *bev, void *ctx)
{
struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(input);
if (len) {
inf->total_drained += len;
evbuffer_drain(input, len);
printf("Drained %lu bytes from %s\n",
(unsigned long) len, inf->name);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx)
{
struct info *inf = ctx;
struct evbuffer *input = bufferevent_get_input(bev);
int finished = 0;
if (events & BEV_EVENT_EOF) {
size_t len = evbuffer_get_length(input);
printf("Got a close from %s. We drained %lu bytes from it, "
"and have %lu left.\n", inf->name,
(unsigned long)inf->total_drained, (unsigned long)len);
finished = 1;
}
if (events & BEV_EVENT_ERROR) {
printf("Got an error from %s: %s\n",
inf->name, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
finished = 1;
}
if (finished) {
free(ctx);
bufferevent_free(bev);
}
}
struct bufferevent *setup_bufferevent(void)
{
struct bufferevent *b1 = NULL;
struct info *info1;
info1 = malloc(sizeof(struct info));
info1->name = "buffer 1";
info1->total_drained = 0;
/* ... Here we should set up the bufferevent and make sure it gets
connected... */
/* Trigger the read callback only whenever there is at least 128 bytes
of data in the buffer. */
bufferevent_setwatermark(b1, EV_READ, 128, 0);
bufferevent_setcb(b1, read_callback, NULL, event_callback, info1);
bufferevent_enable(b1, EV_READ); /* Start reading. */
return b1;
}
編集2:
http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html
evbuffer_pullup 関数を使用すると、バッファ内のデータを覗くことができます!!
int parse_socks4(struct evbuffer *buf, ev_uint16_t *port, ev_uint32_t *addr)
{
/* Let's parse the start of a SOCKS4 request! The format is easy:
* 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
* destip. */
unsigned char *mem;
mem = evbuffer_pullup(buf, 8);
if (mem == NULL) {
/* Not enough data in the buffer */
return 0;
} else if (mem[0] != 4 || mem[1] != 1) {
/* Unrecognized protocol or command */
return -1;
} else {
memcpy(port, mem+2, 2);
memcpy(addr, mem+4, 4);
*port = ntohs(*port);
*addr = ntohl(*addr);
/* Actually remove the data from the buffer now that we know we
like it. */
evbuffer_drain(buf, 8);
return 1;
}
}
編集3:
このウェブページには、私が必要とする正確なサンプルがあり、この投稿に最適なサンプルです!!!!
int get_record(struct evbuffer *buf, size_t *size_out, char **record_out)
{
/* Let's assume that we're speaking some protocol where records
contain a 4-byte size field in network order, followed by that
number of bytes. We will return 1 and set the 'out' fields if we
have a whole record, return 0 if the record isn't here yet, and
-1 on error. */
size_t buffer_len = evbuffer_get_length(buf);
ev_uint32_t record_len;
char *record;
if (buffer_len < 4)
return 0; /* The size field hasn't arrived. */
/* We use evbuffer_copyout here so that the size field will stay on
the buffer for now. */
evbuffer_copyout(buf, &record_len, 4);
/* Convert len_buf into host order. */
record_len = ntohl(record_len);
if (buffer_len < record_len + 4)
return 0; /* The record hasn't arrived */
/* Okay, _now_ we can remove the record. */
record = malloc(record_len);
if (record == NULL)
return -1;
evbuffer_drain(buf, 4);
evbuffer_remove(buf, record, record_len);
*record_out = record;
*size_out = record_len;
return 1;
}