上次在做版本升级时遇到一个这样的需求:服务器需要向客户端发送版本文件,采用的是TCP + pthread来实现,其中,发送结果需要返回给主线程。这让我想起了子进程的操作退出后会给主进程发送一个信号,而主进程会在收到信号后调用WaitPid来获取返回值。而在线程里面退出就没有类似的信号发送出来,刚开始感觉无从下手,后面浏览了libevent代码后便有有了灵感,使用eventfd可以解决这个问题。
/* event fd, use for thread communicate */
//首先创建一个eventfd
ThreadNotifyFD = eventfd(0, 0);
//加入异步读操作
gevent.thread_ev = event_new(gevent.base, ThreadNotifyFD, EV_READ|EV_PERSIST,
do_thread_notify, (void*)base);
event_add(gevent.thread_ev, NULL);
void do_thread_notify(evutil_socket_t fd, short event, void *arg)
{
pthread_t thread_id;
struct thread_args *threadarg;
ev_uint64_t msg;
ev_ssize_t r;
r = read(fd, (void*) &msg, sizeof(msg));
if (r < 0 && errno != EAGAIN) {
log_debug("do thread notify read failed: %s\n", strerror(errno));
trackExit(EXIT_READ);
return;
}
//收到子线程的返回值
if (pthread_join(msg, (void*)&threadarg)!=0 || !threadarg) {
log_debug("pthread join status failed: %s, %x\n
", strerror(errno), threadarg);
trackExit(EXIT_THREAD);
return;
}
log_debug("arg.peer: %s, writelen: %d, status: %d\n",
inet_ntoa(threadarg->addr), threadarg->write_len, threadarg->status);
if (threadarg->status == 1) {
mark_peer_flags(threadarg->addr, BIN_SENDED);
}
free(threadarg);
return;
}
void do_filetranst(evutil_socket_t fd, short event, void *arg)
{
struct sockaddr_in peeraddr;
pthread_t thread_id;
int socklen;
int s;
fd = accept(fd, (struct sockaddr*)&peeraddr, &socklen);
if (fd < 0) {
log_debug("accept failed: %s\n", strerror(errno));
return;
}
if (!IamDrWithBin || !find_peer(peeraddr.sin_addr)) {
log_debug("bin not ready or accept unknown peer: %s, %d\n
", inet_ntoa(peeraddr.sin_addr), IamDrWithBin);
return ;
}
log_debug("accept non-dr %s\n", inet_ntoa(peeraddr.sin_addr));
//客户端连接来了之后分配一个子线程进程处理
s = pthread_create(&thread_id, NULL, (void *)filetranst_thread, (void *)fd);
if (s != 0) {
log_debug("pthread create failed: %s\n", strerror(errno));
trackExit(EXIT_THREAD);
}
return;
}
void *filetranst_thread(void *arg)
{
struct sockaddr_in peeraddr;
struct thread_args *args;
unsigned int peerlen = sizeof(struct sockaddr_in);
struct stat file_info;
int datafd, data_size;
int fd = (int)arg;
int rc;
ev_uint64_t msg = pthread_self();
rc = getpeername (fd, (struct sockaddr *)&peeraddr, &peerlen);
if (rc < 0) {
log_debug("getpeername failed: %s\n", strerror(errno));
pthread_detach(pthread_self());/* 自生自灭 */
return;
}
log_debug("start to transmit file to peer:%s,
port:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
datafd = open("/tmp/firmware.img", O_RDONLY);
if (datafd < 0 || fstat(datafd, &file_info) < 0) {
log_debug("open file firmware.img failed: %s\n", strerror(errno));
trackExit(EXIT_OPEN);
goto fin;
}
log_debug("file size: %u\n", file_info.st_size);
rc = sendfile(fd, datafd, NULL, file_info.st_size);
if (rc < 0) {
log_debug("sendfile failed: %s\n", strerror(errno));
}
else
log_debug("send %u bytes finished\n", rc);
fin:
close(datafd);
close(fd);
args = (struct thread_args *)malloc(sizeof(struct thread_args));
if (!arg) {
log_debug("malloc args failed\n");
trackExit(EXIT_MEMORY);
} else {
args->addr = peeraddr.sin_addr;
args->write_len = rc;
args->status = (rc == file_info.st_size);
}
do {
//子线程操作完毕后就把相应的结果打包结构体返回,
//并通知eventfd,在主线程内会调用do_thread_notify执行pthread_join返回结果
rc = write(ThreadNotifyFD, (void*) &msg, sizeof(msg));
} while (rc < 0 && errno == EAGAIN);
pthread_exit((void*)args);
}