在libevent(二)中最后我提到了socketpair,不同于系统调用socketpair,libevent自己使用本地通信实例(AF_UNIX)实现了socketpair的功能,我不知道二者实现方式是不是一直,但原理上应该是一致的。
我把evsignal_init函数中的evutil_socketpair提取出来并做测试,代码如下:
源码地址:https://github.com/jeremy505/multi-thread-communication
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <memory.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
int evutil_socketpair(int family, int type, int protocol, int fd[2])
{
/* This code is originally from Tor. Used with permission. */
/* This socketpair does not work when localhost is down. So
* it's really not the same thing at all. But it's close enough
* for now, and really, when localhost is down sometimes, we
* have other problems too.
*/
int listener = -1;
int connector = -1;
int acceptor = -1;
struct sockaddr_in listen_addr;
struct sockaddr_in connect_addr;
int size;
int saved_errno = -1;
if (protocol
#ifdef AF_UNIX
|| family != AF_UNIX
#endif
) {
return -1;
}
if (!fd) {
return -1;
}
listener = socket(AF_INET, type, 0);
if (listener < 0)
return -1;
memset(&listen_addr, 0, sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
listen_addr.sin_port = 0; /* kernel chooses port. */
if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr)) == -1)
goto tidy_up_and_fail;
printf("listen_addr.sin_addr.s_addr=0x%x string=%s--listen_addr.sin_port=%d\n",
listen_addr.sin_addr.s_addr, (char*)inet_ntoa(listen_addr.sin_addr),
listen_addr.sin_port);
if (listen(listener, 1) == -1)
goto tidy_up_and_fail;
connector = socket(AF_INET, type, 0);
if (connector < 0)
goto tidy_up_and_fail;
/* We want to find out the port number to connect to. */
size = sizeof(connect_addr);
if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1)
goto tidy_up_and_fail;
printf("connect_addr.sin_addr.s_addr=0x%x string=%s--connect_addr.sin_port=%d\n",
connect_addr.sin_addr.s_addr, (char*)inet_ntoa(connect_addr.sin_addr),
connect_addr.sin_port);
if (size != sizeof (connect_addr))
goto abort_tidy_up_and_fail;
if (connect(connector, (struct sockaddr *) &connect_addr,
sizeof(connect_addr)) == -1)
goto tidy_up_and_fail;
size = sizeof(listen_addr);
acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);
if (acceptor < 0)
goto tidy_up_and_fail;
if (size != sizeof(listen_addr))
goto abort_tidy_up_and_fail;
/* Now check we are talking to ourself by matching port and host on the
two sockets. */
printf("listen_addr.sin_addr.s_addr=0x%x string=%s--listen_addr.sin_port=%d\n",
listen_addr.sin_addr.s_addr, (char*)inet_ntoa(listen_addr.sin_addr),
listen_addr.sin_port);
if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1)
goto tidy_up_and_fail;
printf("connect_addr.sin_addr.s_addr=0x%x string=%s--connect_addr.sin_port=%d\n",
connect_addr.sin_addr.s_addr, (char*)inet_ntoa(connect_addr.sin_addr),
connect_addr.sin_port);
if (size != sizeof (connect_addr)
|| listen_addr.sin_family != connect_addr.sin_family
|| listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr
|| listen_addr.sin_port != connect_addr.sin_port)
goto abort_tidy_up_and_fail;
fd[0] = connector;
fd[1] = acceptor;
return 0;
abort_tidy_up_and_fail:
tidy_up_and_fail:
return -1;
}
void* work_thread(void* argc)
{
int* fd = (int*)argc;
while(1)
{
char c[50];
int n = recv(fd[1], &c, sizeof(c), 0);
printf("recv%dbyte=%s\n", n, c);
// sleep(1);
char b[]="Goodbye!";
n = send(fd[1], &b, sizeof(b), 0);
printf("send%dbyte=%s\n", n, b);
}
}
int main(int argc, char* argv[])
{
int socket_pair[2];
pthread_t tid;
if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair) == -1)
{
perror("create socketpair failed\n");
return -1;
}
/**//使用socketpair系统调用结果一致
if(socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair) == -1)
{
perror("create socketpair failed\n");
return -1;
}
**/
pthread_create(&tid, NULL,
work_thread, &socket_pair[0]);
while(1)
{
char c[] = "Hello,thread!";
int n = send(socket_pair[0], &c, sizeof(c), 0);
printf("++send%dbyte=%s\n", n, c);
sleep(1);
n = recv(socket_pair[0], &c, sizeof(c), 0);
printf("++recv%dbyte=%s\n", n, c);
}
pthread_join(tid, NULL);
close(socket_pair[0]);
close(socket_pair[1]);
return 0;
}
以上代码实现了线程之间的全双工通信
evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair)
是libevent源码实现方式,
用系统调用socketpair(AF_UNIX, SOCK_STREAM, 0, socket_pair)
结果一致。
输出见如下:
使用evutil_socketpair(包含调试信息)
使用系统调用socketpair:
可以分析 打印信息,socketpair实现了线程之间的双向通信,使用pipe只能单向,如果需要双向通信,需要创建两组pipe来实现,所以我觉得如果需要双向通信可以优先选择使用socketpair,实现原理可以研究evutil_socketpair,同样,可以将sockertpair应用于进程之间。其实进程和线程之间的区别仅仅在于线程之间共享数据,而进程则是各自都有一份数据,互不影响,很多东西二者都是适用的,另外线程又被称为轻进程。