Socket编程实践(13) --UNIX域协议
UNIX域協(xié)議
? ?UNIX域套接字與TCP相比,?在同一臺(tái)主機(jī)上,?UNIX域套接字更有效率,?幾乎是TCP的兩倍(由于UNIX域套接字不需要經(jīng)過(guò)網(wǎng)絡(luò)協(xié)議棧,不需要打包/拆包,計(jì)算校驗(yàn)和,維護(hù)序號(hào)和應(yīng)答等,只是將應(yīng)用層數(shù)據(jù)從一個(gè)進(jìn)程拷貝到另一個(gè)進(jìn)程,?而且UNIX域協(xié)議機(jī)制本質(zhì)上就是可靠的通訊,?而網(wǎng)絡(luò)協(xié)議是為不可靠的通訊設(shè)計(jì)的).
? ?UNIX域套接字可以在同一臺(tái)主機(jī)上各進(jìn)程之間傳遞文件描述符;
? ?UNIX域套接字與傳統(tǒng)套接字的區(qū)別是用路徑名來(lái)表示協(xié)議族的描述;
? ?UNIX域套接字也提供面向流和面向數(shù)據(jù)包兩種API接口,類似于TCP和UDP,但是面向消息的UNIX套接字也是可靠的,消息既不會(huì)丟失也不會(huì)順序錯(cuò)亂。
? ?使用UNIX域套接字的過(guò)程和網(wǎng)絡(luò)socket十分相似,?也要先調(diào)用socket創(chuàng)建一個(gè)socket文件描述符,?family指定為AF_UNIX,?type可以選擇SOCK_DGRAM/SOCK_STREAM;
?
UNIX域套接字地址結(jié)構(gòu):
#define UNIX_PATH_MAX 108 struct sockaddr_un {sa_family_t sun_family; /* AF_UNIX */char sun_path[UNIX_PATH_MAX]; /* pathname */ };基于UNIX域套接字的echo-server/client程序
/**Server端**/ void echoServer(int sockfd); int main() {signal(SIGCHLD, sigHandlerForSigChild);int listenfd = socket(AF_UNIX, SOCK_STREAM, 0);if (listenfd == -1)err_exit("socket error");char pathname[] = "/tmp/test_for_unix";unlink(pathname);struct sockaddr_un servAddr;servAddr.sun_family = AF_UNIX;strcpy(servAddr.sun_path, pathname);if (bind(listenfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("bind error");if (listen(listenfd, 128) == -1)err_exit("listen error");while (true){int connfd = accept(listenfd, NULL, NULL);if (connfd == -1)err_exit("accept error");pid_t pid = fork();if (pid == -1)err_exit("fork error");else if (pid > 0)close(connfd);else if (pid == 0){close(listenfd);echoServer(connfd);close(connfd);exit(EXIT_SUCCESS);}} } void echoServer(int sockfd) {char buf[BUFSIZ];while (true){memset(buf, 0, sizeof(buf));int recvBytes = read(sockfd, buf, sizeof(buf));if (recvBytes < 0){if (errno == EINTR)continue;elseerr_exit("read socket error");}else if (recvBytes == 0){cout << "client connect closed..." << endl;break;}cout << buf ;if (write(sockfd, buf, recvBytes) == -1)err_exit("write socket error");} } /**Client端代碼**/ void echoClient(int sockfd); int main() {int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);if (sockfd == -1)err_exit("socket error");char pathname[] = "/tmp/test_for_unix";struct sockaddr_un servAddr;servAddr.sun_family = AF_UNIX;strcpy(servAddr.sun_path, pathname);if (connect(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("connect error");echoClient(sockfd); } void echoClient(int sockfd) {char buf[BUFSIZ] = {0};while (fgets(buf, sizeof(buf), stdin) != NULL){if (write(sockfd, buf, strlen(buf)) == -1)err_exit("write socket error");memset(buf, 0, sizeof(buf));int recvBytes = read(sockfd, buf, sizeof(buf));if (recvBytes == -1){if (errno == EINTR)continue;elseerr_exit("read socket error");}cout << buf ;memset(buf, 0, sizeof(buf));} }UNIX域套接字編程注意點(diǎn)
? ?1.bind成功將會(huì)創(chuàng)建一個(gè)文件,權(quán)限為0777?&?~umask
? ?2.sun_path最好用一個(gè)/tmp目錄下的文件的絕對(duì)路徑,?而且server端在指定該文件之前首先要unlink一下;
? ?3.UNIX域協(xié)議支持流式套接口(需要處理粘包問(wèn)題)與報(bào)式套接口(基于數(shù)據(jù)報(bào))
? ?4.UNIX域流式套接字connect發(fā)現(xiàn)監(jiān)聽(tīng)隊(duì)列滿時(shí),會(huì)立刻返回一個(gè)ECONNREFUSED,這和TCP不同,如果監(jiān)聽(tīng)隊(duì)列滿,會(huì)忽略到來(lái)的SYN,這導(dǎo)致對(duì)方重傳SYN。
?
傳遞文件描述符
socketpair
#include <sys/types.h> #include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sv[2]);創(chuàng)建一個(gè)全雙工的流管道
參數(shù):
? ?domain:?協(xié)議家族,?可以使用AF_UNIX(AF_LOCAL)UNIX域協(xié)議,?而且在Linux上,?該函數(shù)也就只支持這一種協(xié)議;
? ?type:?套接字類型,?可以使用SOCK_STREAM
? ?protocol:?協(xié)議類型,?一般填充為0;
? ?sv:?返回的套接字對(duì);
socketpair?函數(shù)跟pipe?函數(shù)是類似:?只能在具有親緣關(guān)系的進(jìn)程間通信,但pipe?創(chuàng)建的匿名管道是半雙工的,而socketpair?可以認(rèn)為是創(chuàng)建一個(gè)全雙工的管道。
可以使用socketpair?創(chuàng)建返回的套接字對(duì)進(jìn)行父子進(jìn)程通信,?如下例:
int main() {int sockfds[2];if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) == -1)err_exit("socketpair error");pid_t pid = fork();if (pid == -1)err_exit("fork error");// 父進(jìn)程, 只負(fù)責(zé)數(shù)據(jù)的打印else if (pid > 0){close(sockfds[1]);int iVal = 0;while (true){cout << "value = " << iVal << endl;write(sockfds[0], &iVal, sizeof(iVal));read(sockfds[0], &iVal, sizeof(iVal));sleep(1);}}// 子進(jìn)程, 只負(fù)責(zé)數(shù)據(jù)的更改(+1)else if (pid == 0){close(sockfds[0]);int iVal = 0;while (read(sockfds[1], &iVal, sizeof(iVal)) > 0){++ iVal;write(sockfds[1], &iVal, sizeof(iVal));}} }sendmsg/recvmsg
#include <sys/types.h> #include <sys/socket.h> ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);它們與sendto/send?和?recvfrom/recv?函數(shù)類似,只不過(guò)可以傳輸更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),不僅可以傳輸一般數(shù)據(jù),還可以傳輸額外的數(shù)據(jù),如文件描述符。
//msghdr結(jié)構(gòu)體 struct msghdr {void *msg_name; /* optional address */socklen_t msg_namelen; /* size of address */struct iovec *msg_iov; /* scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */ }; struct iovec /* Scatter/gather array items */ {void *iov_base; /* Starting address */size_t iov_len; /* Number of bytes to transfer */ };msghdr結(jié)構(gòu)體成員解釋:
? ?1)msg_name?:即對(duì)等方的地址指針,不關(guān)心時(shí)設(shè)為NULL即可;
? ?2)msg_namelen:地址長(zhǎng)度,不關(guān)心時(shí)設(shè)置為0即可;
? ?3)msg_iov:是結(jié)構(gòu)體iovec?的指針,?指向需要發(fā)送的普通數(shù)據(jù),?見(jiàn)下圖。???
? ?? ?成員iov_base?可以認(rèn)為是傳輸正常數(shù)據(jù)時(shí)的buf;
? ?? ?成員iov_len?是buf?的大小;
? ?4)msg_iovlen:當(dāng)有n個(gè)iovec?結(jié)構(gòu)體時(shí),此值為n;
? ?5)msg_control:是一個(gè)指向cmsghdr?結(jié)構(gòu)體的指針(見(jiàn)下圖),?當(dāng)需要發(fā)送輔助數(shù)據(jù)(如控制信息/文件描述符)時(shí),?需要設(shè)置該字段,?當(dāng)發(fā)送正常數(shù)據(jù)時(shí),?就不需要關(guān)心該字段,?并且msg_controllen可以置為0;
? ?6)msg_controllen:cmsghdr?結(jié)構(gòu)體可能不止一個(gè)(見(jiàn)下圖):
? ?7)flags:?不用關(guān)心;
為了對(duì)齊,可能存在一些填充字節(jié)(見(jiàn)下圖),跟系統(tǒng)的實(shí)現(xiàn)有關(guān),但我們不必關(guān)心,可以通過(guò)一些函數(shù)宏來(lái)獲取相關(guān)的值,如下:
#include <sys/socket.h> struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh); //獲取輔助數(shù)據(jù)的第一條消息 struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg); //獲取輔助數(shù)據(jù)的下一條信息 size_t CMSG_ALIGN(size_t length); size_t CMSG_SPACE(size_t length); size_t CMSG_LEN(size_t length); //length使用的是的(實(shí)際)數(shù)據(jù)的長(zhǎng)度, 見(jiàn)下圖(兩條填充數(shù)據(jù)的中間部分) unsigned char *CMSG_DATA(struct cmsghdr *cmsg);進(jìn)程間傳遞文件描述符
/**示例: 封裝兩個(gè)函數(shù)send_fd/recv_fd用于在進(jìn)程間傳遞文件描述符**/ int send_fd(int sockfd, int sendfd) {// 填充 name 字段struct msghdr msg;msg.msg_name = NULL;msg.msg_namelen = 0;// 填充 iov 字段struct iovec iov;char sendchar = '\0';iov.iov_base = &sendchar;iov.iov_len = 1;msg.msg_iov = &iov;msg.msg_iovlen = 1;// 填充 cmsg 字段struct cmsghdr cmsg;cmsg.cmsg_len = CMSG_LEN(sizeof(int));cmsg.cmsg_level = SOL_SOCKET;cmsg.cmsg_type = SCM_RIGHTS;*(int *)CMSG_DATA(&cmsg) = sendfd;msg.msg_control = &cmsg;msg.msg_controllen = CMSG_LEN(sizeof(int));// 發(fā)送if (sendmsg(sockfd, &msg, 0) == -1)return -1;return 0; } int recv_fd(int sockfd) {// 填充 name 字段struct msghdr msg;msg.msg_name = NULL;msg.msg_namelen = 0;// 填充 iov 字段struct iovec iov;char recvchar;iov.iov_base = &recvchar;iov.iov_len = 1;msg.msg_iov = &iov;msg.msg_iovlen = 1;// 填充 cmsg 字段struct cmsghdr cmsg;msg.msg_control = &cmsg;msg.msg_controllen = CMSG_LEN(sizeof(int));// 接收if (recvmsg(sockfd, &msg, 0) == -1)return -1;return *(int *)CMSG_DATA(&cmsg); } int main() {int sockfds[2];if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) == -1)err_exit("socketpair error");pid_t pid = fork();if (pid == -1)err_exit("fork error");// 子進(jìn)程以只讀方式打開(kāi)文件, 將文件描述符發(fā)送給子進(jìn)程else if (pid == 0){close(sockfds[1]);int fd = open("read.txt", O_RDONLY);if (fd == -1)err_exit("open error");cout << "In child, fd = " << fd << endl;send_fd(sockfds[0], fd);}// 父進(jìn)程從文件描述符中讀取數(shù)據(jù)else if (pid > 0){close(sockfds[0]);int fd = recv_fd(sockfds[1]);if (fd == -1)err_exit("recv_fd error");cout << "In parent, fd = " << fd << endl;char buf[BUFSIZ] = {0};int readBytes = read(fd, buf, sizeof(buf));if (readBytes == -1)err_exit("read fd error");cout << buf;} }分析:
? ?我們知道,父進(jìn)程在fork?之前打開(kāi)的文件描述符,子進(jìn)程是可以共享的,但是子進(jìn)程打開(kāi)的文件描述符,父進(jìn)程是不能共享的,上述程序就是舉例在子進(jìn)程中打開(kāi)了一個(gè)文件描述符,然后通過(guò)send_fd?函數(shù)將文件描述符傳遞給父進(jìn)程,父進(jìn)程可以通過(guò)recv_fd?函數(shù)接收到這個(gè)文件描述符。先建立一個(gè)文件read.txt?后輸入幾個(gè)字符,然后運(yùn)行程序;
?
注意:
? ?(1)只有UNIX域協(xié)議才能在本機(jī)進(jìn)程間傳遞文件描述符;
? ?(2)進(jìn)程間傳遞文件描述符并不是傳遞文件描述符的值(其實(shí)send_fd/recv_fd的兩個(gè)值也是不同的),?而是要在接收進(jìn)程中創(chuàng)建一個(gè)新的文件描述符,?并且該文件描述符和發(fā)送進(jìn)程中被傳遞的文件描述符指向內(nèi)核中相同的文件表項(xiàng).
總結(jié)
以上是生活随笔為你收集整理的Socket编程实践(13) --UNIX域协议的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: android 一些常用的功能方法代码块
- 下一篇: css语言基础--css的选择符语法