套接字可以用于网络通信,也可以用于本机内的进程通信。由于本机内进程的IP地址都相同,因此只需要进程号来确定通信的双方。Linux环境下使用socketpair函数创造一对未命名的、相互连接的UNIX域套接字。
socketpair 函数
功能:创建一个全双工的流管道
原型 int socketpair(int domain, int type, int protocol, int sv[2]);
参数
domain: 协议家族
type: 套接字类型
protocol: 协议类型
sv: 返回套接字对
返回值:成功返回0;失败返回-1
实际上socketpair 函数跟pipe 函数是类似的,也只能在同个主机上具有亲缘关系的进程间通信,但pipe 创建的匿名管道是半双工的,而socketpair 可以认为是创建一个全双工的管道。
可以使用socketpair 创建返回的套接字对进行父子进程通信
setsockopt设置函数。
DEMO程序:
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#define SOCKET_BUFFER_SIZE (32768U)
void *function_thread1 (void *arg)
{
int fd = (int)arg;
char buf[500];
int len;
int cnt = 0;
while (1)
{
/* 向 main线程发出: Hello, main thread */
len = sprintf(buf, "Hello, main thread, cnt = %d", cnt++);
write(fd, buf, len);
/* 读取数据(main线程发回的数据) */
len = read(fd, buf, 500);
buf[len] = '\0';
printf("%s\n", buf);
sleep(5);
}
return NULL;
}
int main(int argc, char **argv)
{
int sockets[2];
/*
int socketpair(int domain, int type, int protocol, int sv[2]);
socketpair函数需要四个参数:
domain-套接口的域
type-套接口类型
protocol-使用的协议
sv[2]-指向存储文件描述符的指针
类型参数声明了我们希望创建哪种类型的套接口,socketpair函数的选择如下:
SOCK_STREAM
SOCK_DGRAM
对于socketpair函数,protocol参数必须提供为0。
参数sv[2]是接收代表两个套接口的整数数组。每一个文件描述符代表一个套接口,并且与另一个并没有区别。
如果函数成功,将会返回0值。否则将会返回-1表明创建失败,并且errno来表明特定的错误号。
socketpair可以用于多进程间全双工通讯。
1.调用socketpair,成功后便有两个socket文件描述符,一个socket就像是一个pipe。
在两个socket中指定一个给父进程使用,另外一个给子进程使用。不用在意指定哪一个socket给父进程使用,
随便挑一个就可以了。
2.调用fork,成功后就创建了子进程
2.1 fork返回0,那就是子进程,关闭父进程的socket,保留子进程的socket
2.2 fork返回非0,那就是父进程,关闭子进程的socket,保留父进程的socket
为什么要关闭socket,现在还没有搞清楚。
3.现在有父子两个进程,每个进程都有一个socket描述符用以代表同一个pipe的两端。如果父进程调用write,
那么子进程就调用read,反之亦然。实现父子进程双向通信。
*/
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets);//创建两个文件句柄
int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
/* 创建线程1 */
pthread_t threadID;
pthread_create(&threadID, NULL, function_thread1, (void *)sockets[1]);//把一个文件句柄传给线程AA
char buf[500];
int len;
int cnt = 0;
int fd = sockets[0];
while(1)
{
/* 读数据: 读取线程1发出的数据 */
len = read(fd, buf, 500);
buf[len] = '\0';
printf("%s\n", buf);
/* main thread向thread1 发出: Hello, thread1 */
len = sprintf(buf, "Hello, thread1, cnt = %d", cnt++);
write(fd, buf, len);
}
}
socketpair双向通信在Android输入系统有什么用了?InputReader负责通过EventHub读取输入事件,InputDispatcher负责分发事件。InputChannel就是用于InputDispatcher把事件分发给具体应用程序用的。
InputChannel
的创建是在 ViewRootImpl
中setView
方法中。也就是说InputDispatcher通过InputChannel 把事件发给了ViewRootImpl。这其中的InputChannel和ViewRootImpl就使用到了
socketpair通信机制。相关代码:
status_t InputChannel::openInputChannelPair(const String8& name,
sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
//创建一对
int sockets[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
status_t result = -errno;
ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
name.string(), errno);
outServerChannel.clear();
outClientChannel.clear();
return result;
}
int bufferSize = SOCKET_BUFFER_SIZE;
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
String8 serverChannelName = name;
serverChannelName.append(" (server)");
/*Server的名字,name的值是从WMS传过来的*/
outServerChannel = new InputChannel(serverChannelName, sockets[0]);
String8 clientChannelName = name;
clientChannelName.append(" (client)");
/*clien端使用*/
outClientChannel = new InputChannel(clientChannelName, sockets[1]);
return OK;
}
之后的收发操作:
status_t InputChannel::sendMessage(const InputMessage* msg) {
size_t msgLength = msg->size();
ssize_t nWrite;
do {
nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
...........................................
}
status_t InputChannel::receiveMessage(InputMessage* msg) {
ssize_t nRead;
do {
nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
} while (nRead == -1 && errno == EINTR);
........................
}
也就是说c++层的InputDispatcher最终是通过socketpair通信方式把 event事件发给java层的app程序的,而不是jni,binder的方式。