1. 程式人生 > >對於Linux下的伺服器程式設計(2)

對於Linux下的伺服器程式設計(2)

對於驚群問題,我們可以使用一個主執行緒來接受連線,並且把這個連線套接字傳遞到子程序裡面,讓子程序來處理這個連線。這種方法需要程序間通訊:通過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的錯誤。