版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nia305/article/details/80735437
对于惊群问题,我们可以使用一个主线程来接受连接,并且把这个连接套接字传递到子进程里面,让子进程来处理这个连接。这种方法需要进程间通信:通过Unix套接字来在进程之间传递套接字。【注意不能使用Unix套接字***直接***传递描述符到子进程,因为虽然父进程和子进程获得的文件描述符相同,但是子进程不一定打开了这个描述符的文件,或者说这两个描述符指向不同的文件,所以必须使用recvmsg/sendmsg这两个函数来传递】
#include "ipcunix.h"
extern int sendFD(int fd, int fdSend) {
struct iovec iov[1];
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
char buf[128];
const char* str = "Hello World.\n";
memcpy(buf, str, strlen(str));
iov[0].iov_base = buf;
iov[0].iov_len = 128;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
struct cmsghdr* cmsg = malloc(sizeof(struct cmsghdr));
if (fdSend < 0) {
return -1;
} else {
memset(cmsg, 0, sizeof(cmsg));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CONTROLLEN;
msg.msg_control = cmsg;
msg.msg_controllen = CONTROLLEN;
*(int*)CMSG_DATA(cmsg) = fdSend;
}
int numSend;
if ((numSend = sendmsg(fd, &msg, 0)) < 0) {
perror("Send Msg Error");
return -1;
}
free(cmsg);
return 0;
}
extern int recvFD(int fd, ssize_t (*userfunc)(int, const void*, size_t)) {
int newFD, nr;
char* ptr;
char buf[128];
struct iovec iov[1];
struct msghdr msg;
struct cmsghdr* cmsg = malloc(sizeof(struct cmsghdr));
memset(cmsg, 0, sizeof(struct cmsghdr));
memset(buf, 0, 128);
while (1) {
iov[0].iov_base = buf;
iov[0].iov_len = 128;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_control = cmsg;
msg.msg_controllen = CONTROLLEN;
if ((nr = recvmsg(fd, &msg, 0)) < 0) {
perror("Recv Msg Error");
return -1;
} else if (nr == 0) {
printf("Connection closed by peer.\n");
return -1;
} else {
newFD = *(int*)CMSG_DATA(cmsg);
return newFD;
}
}
}
上面的代码需要注意:
1.cmsghdr结构体需要分配在堆上,如果直接放在栈上,cmsghdr在栈上的地址跟变量声明的顺序不一样[我也不知道为什么]。
2.cmsghdr的type成员在send时候的取值是固定的,必须是SCM_RIGHTS,这样内核就会帮我们把newFD这个文件指针指向父进程打开的连接。
服务器代码里面,需要添加的代码只是对于新连接进来时候的处理:采用轮询的方法来选择不同的子进程来处理连接:
numThread = cpu_num_core - 1;
int clientFD = accept(listenFD, (struct sockaddr*)&cli, &len);
sendFD(processes[(currentProcess++) % numThread].connectFD, clientFD);
close(clientFD);
上面的代码需要注意的是最后的close:因为通过Unix套接字把描述符传递给了子进程,父进程这边的描述符必须关闭,要不然对这个描述符的引用是2,子进程如果关闭这个连接,不是真正的关闭,只是引用-1,可能会出现Bad File Descriptor的错误。