十六、select函数

void FD_ZERO(fd_set *set); ---清空一个文件描述符集合。fd_set rset;FD_ZERO(&rset);void FD_SET(int fd, fd_set *set); ---将待监听的文件描述符,添加到监听集合中。FD_SET(3, &rset);FD_SET(5, &rset);FD_SET(6, &rset);void FD_CLR(int fd, fd_set *set); ---将一个文件描述符从监听集合中移除。FD_CLR(4. &rset);int FD_ISSET(int fd, fd set *set); ---判断一个文件描述符是否在监听集合中。返回值:在1,不在0FD_ISSET(4, &rset);int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);nfds:监听的所有文件描述符中,最大文件描述符+1readfds:读文件描述符监听集合。 传入、传出参数 writefds:写文件描述符监听集合。 传入、传出参数 NULLexceptfds:异常文件描述符监听集合 传入、传出参数 NULLtimeout:>0:设置监听超时时长。NULL:阻塞监听0:非阻塞监听,轮询返回值:>0:所有监听集合(3个)中,满足对应事件的总数。0:没有满足监听条件的文件描述符-1:errno
十七、select实现多路IO

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>// 封装的错误处理函数
#include"wrap.h"#define SERV_PORT 6666int main(int argc, char* argv[]) {int listenfd, connfd;char buf[BUFSIZ];struct sockaddr_in clie_addr, serv_addr;socklen_t clie_addr_len;listenfd = Socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));Listen(listenfd, 128);fd_set rset, allset; //定义读集合,备份集合allsetint ret, maxfd = 0, n, i, j;maxfd = listenfd; //最大文件描述符FD_ZERO(&allset); //清空监听集合FD_SET(listenfd, &allset); //将待监听fd添加到监听集合中while (1) {rset = allset; //备份ret = select(maxfd + 1, &rset, NULL, NULL, NULL); //使用select监听if (ret < 0) {perr_exit("select error");}//listenfd满足监听的读事件if (FD_ISSET(listenfd, &rset)) { clie_addr_len = sizeof(clie_addr);//建立链接,不会阻塞connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);FD_SET(connfd, &allset); //将新产生的fd,添加到监听集合中,监听数据读事件if (maxfd < connfd) {maxfd = connfd; //修改maxfd}if (ret == 1) {continue; //说明select只返回一个,并且是listenfd,后续执行无须执行}}//处理满足读事件的fdfor (i = listenfd + 1; i <= maxfd; i++) { //找到满足读事件的那个fdif (FD_ISSET(i, &rset)) { n = Read(i, buf, sizeof(buf));//检测到客户端已经关闭链接if (n == 0) { Close(i);FD_CLR(i, &allset); //将关闭的fd,移除出监听集合}else if (n == -1) {perr_exit("read error");}for (j = 0; j < n; j++) {buf[j] = toupper(buf[j]);}write(i, buf, n);write(STDOUT_FILENO, buf, n);}}}Close(listenfd);return 0;
}
十八、select优缺点
缺点:监听上限受文件描述符限制。最大1024轮询fd效率太慢,一个个问,有的可能用不到也被询优点:跨平台。Win、Linux、MacOS、Unix解决:通过自定义数组放入需要被轮询的fd,避免不必要的轮询
// 用client数组解决
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>#include"wrap.h"#define SERV_PORT 6666int main(int argc, char* argv[]) {int i, j, n, maxi;/*自定义数组client,防止遍历1024个文件描述符 FD_SETSIZE默认为1024*/int nready, client[FD_SETSIZE];int maxfd, listenfd, connfd, sockfd;/* #define INET_ADDRSTRLEN 16 */char buf[BUFSIZ], str[INET_ADDRSTRLEN];struct sockaddr_in clie_addr, serv_addr;socklen_t clie_addr_len;fd_set rset, allset; /*set读事件文件描述符集合allset用来暂存*/listenfd = Socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));Listen(listenfd, 128);maxfd = listenfd; /*起初listenfd即为最大文件描述符*/maxi = -1; /*将来用作client[]的下标,初始值指向0个元素之前下标位置*/for (i = 0; i < FD_SETSIZE; i++) {client[i] = -1; /*用-1初始化client[]*/}FD_ZERO(&allset);FD_SET(listenfd, &allset); /*构造select监控文件描述符集*/while (1) {rset = allset; /*每次循环时都重新设置select监控信号集*/nready = select(maxfd + 1, &rset, NULL, NULL, NULL);if (nready < 0) {perr_exit("select error");}/*说明有新的客户端连接请求*/if (FD_ISSET(listenfd, &rset)) {clie_addr_len = sizeof(clie_addr);//建立连接,不会阻塞connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),ntohs(clie_addr.sin_port));for (i = 0; i < FD_SETSIZE; i++) {/*找client[]中没有使用的位置*/if (client[i] < 0) {client[i] = connfd; /*保存accept返回的文件描述符到client[]里*/break;}}/*达到select能监控的文件个数上限1024*/if (i == FD_SETSIZE) {fputs("too many clients\n", stderr);exit(1);}FD_SET(connfd, &allset); /*向监控文件描述符集合allset添加新的文件描述符connfd*/if (connfd > maxfd) {maxfd = connfd; /*select第一个参数需要*/}if (i > maxi) {maxi = i; /*保证maxi存的总是client[]最后一个元素下标*/}if (--nready == 0) {continue;}}/*检测哪个clients有数据就绪*/for (i = 0; i <= maxi; i++) {if ((sockfd = client[i]) < 0) {continue;}if (FD_ISSET(sockfd, &rset)) {/*当client关闭连接时,服务器端也关闭对应连接*/if ((n = Read(sockfd, buf, sizeof(buf))) == 0) {Close(sockfd);FD_CLR(sockfd, &allset); /*解除select对此文件描述符的监控*/client[i] = -1;}else if (n > 0) {for (j = 0; j < n; j++) {buf[j] = toupper(buf[j]);}Write(sockfd, buf, n);Write(STDOUT_FILENO, buf, n);if (--nready == 0) {break;}}}}}Close(listenfd);return 0;
}
十九、poll函数
int poll(struct pollfd *fds, nfds_t nfds, int timeout);fds:监听的文件描述符[数组]struct pollfd {int fd; // 待监听的文件描述符short events; // 待监听的文件描述符对应的监听事件// 取值:POLLIN、POLLOUT、POLLERRshort revents; // 传入时,给0。如果满足对应事件的话,// 返回非0-->POLLIN、POLLOUT、POLLERR};nfds:监听数组的,实际有效监听个数。timeout:>0: 超时时长。单位:毫秒。-1: 阻塞等待0: 不阻塞返回值:返回满足对应监听事件的文件描述符总个数。优点:自带数组结构。可以将监听事件集合和返回事件集合分离。拓展监听上限。可以超出1024限制。
缺点:不能跨平台。在Linux上无法直接定位满足监听事件的文件描述符,要轮询。read函数返回值:>0:实际读到的字节数=0:socket中,表示对端关闭。close()-1: 如果errno=EINTR 被异常终端。需要重启。如果errno=EAGIN或EWOULDBLOCK以非阻塞方式读数据,但是没有数据。需要再次读。如果errno==ECONNRESET,说明连接被重置,需要c1ose(),移除监听队列。
二十、poll实现服务器

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
#include<errno.h>
#include<ctype.h>#include"wrap.h"#define MAXLINE 80
#define SERV_PORT 8000
#define OPEN_MAX 1024int main(int argc, char* argv[]) {int i, j, maxi, listenfd, connfd, sockfd;int nready; /*接收poll返回值,记录满足监听事件的fd个数*/ssize_t n;char_buf[MAXLINE], str[INET_ADDRSTRLEN];socklen_t clilen;struct pollfd client[OPEN_MAX];struct sockaddr_in cliaddr, servaddr;listenfd = Socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));Listen(listenfd, 128);client[o].fd = listenfd; /*要监听的第一个文件描述符存入client[0]*/client[o].events = POLLIN; /*listenfd监听普通读事件*/for (i = 1; i < OPEN_MAX; i++) {client[i].fd = -1; /*用-1初始化client[]里剩下元素0也是文件描述符,不能用*/}maxi = 0; /*client[]数组有效元素中最大元素下标*/while (1) {nready = poll(client, maxi + 1, -1); /*阻塞监听是否有客户端链接请求*//*listenfd有读事件就绪*/if (client[0].revents & POLLIN) {clilen = sizeof(cliaddr);/*接收客户端请求Accept不会阻塞*/connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));for (i = 1; i < OPEN_MAX; i++) {if (client[i].fd < 0) {client[i].fd = connfd; /*找到client[]中空闲的位置,存放accept返回的connfd*/break;}}/*达到了最大客户端数*/if (i == OPEN_MAX) {perr_exit("too many clients");}client[i].events = POLLIN; /*设置刚刚返回的connfd,监控读事件*/if (i > maxi) {maxi = i; /*更新client[]中最大元素下标*/}if (--nready <= 0) {continue; /*没有更多就绪事件时,继续回到pol1阻塞*/}}/*前面的if没满足,说明没有listenfd满足,检测client[]看是那个connfd就绪*/for (i = 1; i <= maxi; i++) {if ((sockfd = client[i].fd) < 0) {continue;}if (client[i].revents & POLLIN) {if ((n = Read(sockfd, buf, MAXLINE)) < 0) {/*收到RST标志*/if (errno == ECONNRESET) {printf("client[%d] aborted connectionn", i);Close(sockfd);/*poll中不监控该文件描述符,直接置为-1即可,不用像select中那样移除*/client[i].fd = -1;}else {perr_exit("read error");}}else if (n == 0) {/*说明客户端先关闭链接*/printf("client[%d] closed connection\n", i);Close(sockfd);client[i].fd = -1;}else {for (j = 0; j < n; j++) {buf[j] = toupper(buf[j]);}Writen(sockfd, buf, n);}if (--nready <= 0) {break;}}}}return 0;
}
二十一、设置文件描述符
突破1024文件描述符限制·cat/proc/sys/fs/file-max-->当前计算机所能打开的最大文件个数。受硬件影响。ulimit -a --> 当前用户下的进程,默认打开文件描述符个数。缺省为1024修改:打开sudo vi/etc/security/limits.conf,写入:* soft nofile 65536 -->设置默认值,可以直接借助命令修改。* hard nofile 10000 -->命令修改上限。【注销用户,使其生效】
二十二、epoll函数
epoll:
创建一棵监听红黑树
int epoll_create(int size):size:创建的红黑树的监听节点数量。(仅供内核参考。)返回值:指向新创建的红黑树的根节点的fd。失败:-1 errno操作监听红黑树
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);epfd: epoll_create函数的返回值。epfdop: 对该监听红黑数所做的操作。EPOLL_CTL_ADD添加fd到监听红黑树EPOLL_CTL_MOD修改fd在监听红黑树上的监听事件。EPOLL_CTL_DEL将一个fd从监听红黑树上摘下(取消监听)fd: 待监听的fdevent: 本质struct epoll_event 结构体地址events: EPOLLIN/EPOLLOUT/EPOLLERRdata: 联合体int fd; 对应监听事件的fdvoid *ptr;uint32_t u32;uint64_t u64;返回值:成功:0 失败:-1 errno阻塞监听
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);epfd: epoll_create函数的返回值。epfdevents:传出参数,【数组】,满足监听条件的哪些fd结构体。maxevents:数组元素的总个数。1024struct epoll_event evnets[1024];timeout:-1: 阻塞0: 不阻塞>0: 超时时间(毫秒)返回值:>0:满足监听的总个数。可以用作循环上限。0:没有rd满足监听事件I-1:失败。errno

二十三、epoll函数实现多路IO
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<errno.h>
#include<ctype.h>#include "wrap.h"#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 5000int main(int argc, char* argv[]) {int i, listenfd, connfd, sockfd;int n, num = 0;ssize_t nready, efd, res;char_buf[MAXLINE], str[INET_ADDRSTRLEN];socklen_t clilen;struct sockaddr_in cliaddr, servaddr;listenfd = Socket(AF_INET, SOCK_STREAM, 0);int opt = 1;setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(SERV_PORT);Bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));Listen(listenfd, 20);efd = epoll_create(OPEN_MAX); //创建epoll模型,efd指向红黑树根节点if (efd = -1) {perr_exit("epoll_create error");}struct epoll_event tep, ep[OPEN_MAX]; //tep:epoll_ctl参数 ep[]:epoll_wait参数tep.events = EPOLLIN;tep.data.fd = listenfd; //指定1fd的监听时间为"读"//将lfd及对应的结构体设置到树上,efd可找到该树res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);if (res == -1) {perr_exit("epoll_ctl error");}while (1) {/*epoll为server阻塞监听事件,ep为struct epoll_event类型数组,OPEN_MAX为数组容量,-1表示永久阻塞*/nready = epoll_wait(efd, ep, OPEN_MAX, -1);if (nready == -1) {perr_exit("epoll_wait error");}for (i = 0; i < nready; i++) {if (!(ep[i].events & EPOLLIN)) {continue; //如果不是"读"事件,继续循环}//判断满足事件的fd是不是lfdif (ep[i].data.fd == listenfd) {clilen = sizeof(cliaddr);connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &clilen); //接受连接printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));printf("cfd %d---client %d\n", connfd, ++num);tep.events = EPOLLIN;tep.data.fd = connfd;res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); //加入红黑树if (res = -1) {perr_exit("epoll_ctl error");}}else {//不是lfdsockfd = ep[i].data.fd;n = Read(sockfd, buf, MAXLINE);//读到0,说明客户端关闭链接if (n == 0) {res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //将该文件描述符从红黑树摘除if (res = -1) {perr_exit("epoll_ctl error");}Close(sockfd); //关闭与该客户端的链接printf("client[%d] closed connection(n", sockfd);}else if (n < 0) {//出错perror("read n < 0 error: ");res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //摘除节点Close(sockfd);}else {//实际读到了字节数for (i = 0; i < n; i++) {buf[i] = toupper(buf[i]); //转大写,写回给客户端}Write(STDOUT_FILENO, buf, n);Writen(sockfd, buf, n);}}}}Close(listenfd);return 0;
}
二十四、ET和LT模式
epoll事件模型:
ET模式:
边沿触发--缓冲区剩余未读尽的数据不会导致epoll_wait返回。新的事件(读写)满足,才会触发。LT模式:
水平触发-—默认采用模式。缓冲区剩余未读尽的数据会导致epoll_wait返回。
#include<stdio.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<errno.h>
#include<unistd.h>#define MAXLINE 10int main(int argc, char* argv[]) {int efd, i;int pfd[2];pid_t pid;char buf[MAXLINE], ch = 'a';pipe(pfd);pid = fork();//子进程写if (pid == 0) {close(pfd[0]);while (1) {//aaaa\nfor (i = 0; i < MAXLINE / 2; i++) {buf[i] = ch;}buf[i - 1] = '\n';ch++;//bbbb\nfor (; i < MAXLINE; i++) {buf[i] = ch;}buf[i - 1] = '\n';ch++;//aaaa\nbbbb\nwrite(pfd[1], buf, sizeof(buf)); //写10个数据sleep(5);}close(pfd[1]);}else if (pid > 0) {//父进程读struct epoll_event event;struct epoll_event resevent[10]; //epoll_wait就绪返回eventint res, len;close(pfd[1]);efd = epoll_create(10);event.events = EPOLLIN | EPOLLET; //ET边沿触发//event.events = EPOLLIN; //LT水平触发(默认)event.data.fd = pfd[0];epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);while(1) {res = epoll_wait(efd, resevent, 10, -1);printf("res %d\n", res);if (resevent[0].data.fd == pfd[0]) {len = read(pfd[0], buf, MAXLINE / 2); //读5个数据write(STDOUT_FILENO, buf, len);}}close(pfd[0]);close(efd);}else {perror("fork");exit(-1);}return 0;
}//如下图:如果bbbb\n会对epoll_wait()触发就是LT模式,否则是ET模式

二十五、epoll的ET非阻塞模式
ET(edge-triggered):ET是高速工作方式,只支持no-block socket。
应用于预览部分内容,而不需要全部了解struct epoll_event event;
event.events= EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
int flg = fcntl(cfd, F_GETFL);
flg |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flg); 非阻塞后面的数据读不到就返回了,需要全部读到就用忙轮询epoll优缺点:
优点:高效。突破1024文件描述符。
缺点:不能跨平台。Linux。
二十六、线程池



二十七、TCP和UDP
TCP:
面向连接的,可靠数据包传输。对于不稳定的网络层,采取完全弥补的通信方式。丢包重传。
优点:数据流量稳定、速度稳定、顺序
缺点:传输速度慢。相率低。开销大。
使用场景:数据的完整型要求较高,不追求效率。大数据传输、文件传输。UDP:
无连接的,不可靠的数据报传递。对于不稳定的网络层,采取完全不弥补的通信方式。
优点:传输速度块。相率高。开销小。
缺点:数据流量、速度、顺序不稳定。
使用场景:对时效性要求较高场合。稳定性其次。(视频通话)
二十八、recvfrom和sendto函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);sockfd:套接字buf:缓冲区地址len:缓冲区大小flags: 0src_addr:(struct sockaddr*) &addr传出。对端地址结构addrlen:传入传出。返回值:成功接收数据字节数。失败:-1errn。0:对端关闭。ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);sockfd:套接字buf:存储数据的缓冲区len:数据长度flags:0dest_addr:(struct sockaddr*) &addr传入。目标地址结构addrlen:地址结构长度。返回值:成功写出数据字节数。失败-1,errno
二十九、UDP的CS模型实现
// 服务器端
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>#define SERV_PORT 9527int main(void) {int sockfd;sockfd = socket(AF_INET, SOCK_DGRAM, 0);char buf[1024], clie_IP[BUFSIZ];int ret, i;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));clie_addr_len = sizeof(clie_addr);while (1) {ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&clie_addr, &clie_addr_len);printf("client 's IP:%s port:%d\n",inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),ntohs(clie_addr.sin_port));for (i = 0; i < ret; i++) {buf[i] = toupper(buf[i]);}sendto(sockfd, buf, ret, 0, (struct sockaddr*)&clie_addr, clie_addr_len);}close(sockfd);return 0;
}
// 客户端
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>#define SERV_PORT 9527
#define SERV_IP "127.0.0.1"int main(void) {int sockfd;sockfd = socket(AF_INET, SOCK_DGRAM, 0);char buf[BUFSIZ];int ret;struct sockaddr_in serv_addr;bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);while (1) {fgets(buf, sizeof(buf), stdin);sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));// 因为定义了宏,明确了服务器的地址就直接NULLret = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, 0);write(STDOUT_FILENO, buf, ret);}close(sockfd);return 0;
}
三十、本地套接字





// 服务器代码
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<strings.h>
#include<string.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<sys/un.h>
#include<stddef.h>#include "wrap.h"#define SERV_ADDR "serv.socket"int main(void) {int lfd, cfd, len, size, i;struct sockaddr_un servaddr, cliaddr;char buf[4096];lfd = Socket(AF_UNIX, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sun_family = AF_UNIX;strcpy(servaddr.sun_path, SERV_ADDR);// offsetof是用后面参数的首地址减前面参数的首地址len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); /* servaddr total len */unlink(SERV_ADDR); /*确保bind之前serv.sock文件不存在,bind会创建该文件*/Bind(lfd, (struct sockaddr*)&servaddr, len); /*参3不能是sizeof(servaddr)*/Listen(lfd, 20);printf("Accept...\n");while (1) {len = sizeof(cliaddr); // AF_UNIX大小+108Bcfd = Accept(lfd, (struct sockaddr*)&cliaddr, (socklen_t*)&len);len -= offsetof(struct sockaddr_un, sun_path); // 得到文件名的长度cliaddr.sun_path[len] = '\0'; /*确保打印时,没有乱码出现*/printf("client bind filename %s\n", cliaddr.sun_path);while ((size = read(cfd, buf, sizeof(buf))) > 0) {for (i = 0; i < size; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, size);}close(cfd);}close(lfd);return 0;
}// 客户端服务器通过文件通信
// 客户端
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<strings.h>
#include<string.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<sys/un.h>
#include<stddef.h>#include "wrap.h"#define SERV_ADDR "serv.socket"
#define CLIE_ADDR "clie.socket"int main(void) {int cfd, len;struct sockaddr_un servaddr, cliaddr;char buf[4096];cfd = Socket(AF_UNIX, SOCK_STREAM, 0);bzero(&cliaddr, sizeof(cliaddr));cliaddr.sun_family = AF_UNIX;strcpy(cliaddr.sun_path, CLIE_ADDR);/*计算客户端地址结构有效长度*/len = offsetof(struct sockaddr_un, sun_path) + strlen(cliaddr.sun_path);unlink(CLIE_ADDR);Bind(cfd, (struct sockaddr*)&cliaddr, len); /*客户端也需要bind,不能依赖自动绑定*/bzero(&servaddr, sizeof(servaddr)); /*构造server地址 */servaddr.sun_family = AF_UNIX;strcpy(servaddr.sun_path, SERV_ADDR);/*计算服务器端地址结构有效长度*/len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path);Connect(cfd, (struct sockaddr*)&servaddr, len);while (fgets(buf, sizeof(buf), stdin) != NULL) {write(cfd, buf, strlen(buf));len = read(cfd, buf, sizeof(buf));write(STDOUT_FILENO, buf, len);}close(cfd);return 0;
}