對於Linux下的伺服器程式設計(2)
阿新 • • 發佈:2018-11-26
對於驚群問題,我們可以使用一個主執行緒來接受連線,並且把這個連線套接字傳遞到子程序裡面,讓子程序來處理這個連線。這種方法需要程序間通訊:通過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的錯誤。