注:博主只关注编程实现的方面以及linux部分,部分网络原理讲解和windows实现部分跳过
第 15 章 套接字和标准I/O
15.1 标准I/O函数的优点
- c15p247:标准 IO 函数和系统函数文件复制程序的性能对比
标准 IO 函数stdcpy.c
#include <stdio.h> #define BUF_SIZE 3 int main() { FILE * fp1; FILE * fp2; char buf[BUF_SIZE]; fp1 = fopen("news.txt", "r"); fp2 = fopen("cpy1.txt", "w"); while (fgets(buf, BUF_SIZE, fp1) != NULL) { fputs(buf, fp2); } fclose(fp1); fclose(fp2); return 0; }
系统函数syscpy.c
#include <stdio.h> #include <fcntl.h> #define BUF_SIZE 3 int main() { int fd1, fd2; int len; char buf[BUF_SIZE]; fd1 = open("news.txt", O_RDONLY); fd2 = open("cpy.txt", O_WRONLY|O_CREAT|O_TRUNC); while ((len = read(fd1, buf, sizeof(buf))) > 0) { write(fd2, buf, len); } close(fd1); close(fd2); return 0; }
编译运行
比对的时候发现问题,标准 IO 复制后的文件和原文件不对
shiqi@pc:~/network/ch15$ ll -h -rw-r--r-- 1 shiqi shiqi 330M Aug 8 20:04 news.txt shiqi@pc:~/network/ch15$ gcc syscpy.c -o syscpy shiqi@pc:~/Devel/network/ch15$ time ./syscpy real 3m2.397s user 0m55.517s sys 2m6.619s shiqi@pc:~/network/ch15$ gcc stdcpy.c -o stdcpy shiqi@pc:~/network/ch15$ time ./stdcpy real 0m6.822s user 0m5.014s sys 0m0.208s shiqi@pc:~/network/ch15$ ll -h total 986M -rw-rw-r-- 1 shiqi shiqi 327M Aug 8 20:11 cpy1.txt -rwxrwxrwx 1 shiqi shiqi 330M Aug 8 20:08 cpy.txt* -rw-r--r-- 1 shiqi shiqi 330M Aug 8 20:04 news.txt
15.2 使用标准I/O函数
c15p249:将创建套接字返回的文件描述符转换为标准 IO 函数中使用的 FILE 结构体指针的 fdopen 函数原型
#include <stdio.h> FILE * fdopen(int fildes, const char * mode); // fildes: 需要转换的文件描述符 // mode: 将要创建的 FILE 结构体指针的模式(mode)信息 // 成功时返回转换的 FILE 结构体指针,失败时返回 NULL
c15p250:fdopen 的函数使用示例 desto.c
#include <stdio.h> #include <fcntl.h> int main() { FILE * fp; int fd = open("data.dat", O_WRONLY|O_CREAT|O_TRUNC); if (fd == -1) { fputs("file open error", stdout); return -1; } fp = fdopen(fd, "w"); fputs("Network C programming\n", fp); fclose(fp); return 0; }
shiqi@pc:~/network/ch15$ gcc desto.c -o desto shiqi@pc:~/network/ch15$ ./desto shiqi@pc:~/network/ch15$ chmod 755 data.dat shiqi@pc:~/network/ch15$ cat data.dat Network C programming
c15p250:将创建的标准 IO 函数中使用的 FILE 结构体指针转换为套接字返回的文件描述符的 fileno 函数原型
#include <stdio.h> int fileno(FILE * stream); // 成功时返回转换的 FILE 结构体指针,失败时返回 NULL
c15p251:fileno 的程序示例 todes.c
#include <stdio.h> #include <fcntl.h> int main() { FILE * fp; int fd = open("data.dat", O_WRONLY|O_CREAT|O_TRUNC); if (fd == -1) { fputs("file open error", stdout); return -1; } printf("First file descriptor: %d\n", fd); fp = fdopen(fd, "w"); fputs("TCP/IP SOCKET PROGRAMMING\n", fp); printf("Second file descriptor: %d\n", fileno(fp)); fclose(fp); return 0; }
shiqi@pc:~/network/ch15$ gcc todes.c -o todes shiqi@pc:~/network/ch15$ ./todes First file descriptor: 3 Second file descriptor: 3
15.3 基于套接字的标准 I/O函数使用
c15p256:基于标准 IO 函数分离流的目的?
- 为了将 FILE 指针按读模式和写模式加以区分
- 可通过区分读写模式降低实现难度
- 通过区分 IO 缓冲提高缓冲性能
c15p252:基于标准 IO 函数的回声服务端和客户端程序示例
服务端 echo_stdserv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int serv_sock, clnt_sock; char message[BUF_SIZE]; int str_len, i; struct sockaddr_in serv_adr, clnt_adr; socklen_t clnt_adr_sz; FILE * readfp; FILE * writefp; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { error_handling("socket() error"); } memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) { error_handling("bind() error"); } if (listen(serv_sock, 5) == -1) { error_handling("listen() error"); } clnt_adr_sz = sizeof(clnt_adr); for (i = 0; i < 5; ++i) { clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz); if (clnt_sock == -1) { error_handling("accept() error"); } else { printf("Connected client %d \n", i+1); } readfp = fdopen(clnt_sock, "r"); writefp = fdopen(clnt_sock, "w"); while (!feof(readfp)) { fgets(message, BUF_SIZE, readfp); fputs(message, writefp); fflush(writefp); } fclose(readfp); fclose(writefp); } close(serv_sock); return 0; }
客户端 echo_client
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char * argv[]) { int sock; char message[BUF_SIZE]; int str_len; struct sockaddr_in serv_adr; FILE * readfp; FILE * writefp; if (argc != 3) { printf("Usage : %s <IP> <port>\n", argv[0]); exit(1); } sock = socket(PF_INET, SOCK_STREAM, 0); if (sock == -1) { error_handling("socket() error"); } memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = inet_addr(argv[1]); serv_adr.sin_port = htons(atoi(argv[2])); if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) { error_handling("connect() error!"); } else { puts("Connceted........"); } readfp = fdopen(sock, "r"); writefp = fdopen(sock, "w"); while (1) { fputs("Input message(Q to quit): ", stdout); fgets(message, BUF_SIZE, stdin); if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) { break; } fputs(message, writefp); fflush(writefp); fgets(message, BUF_SIZE, readfp); printf("Message from server: %s", message); } close(sock); return 0; }
编译运行
shiqi@pc:~/network/ch15$ gcc echo_stdserv.c -o serv shiqi@pc:~/network/ch15$ ./serv 9190 Connected client 1 ^C
shiqi@pc:~/network/ch15$ gcc echo_client.c -o clnt shiqi@pc:~/network/ch15$ ./clnt 127.0.0.1 9190 Connceted........ Input message(Q to quit): uz Message from server: uz Input message(Q to quit): pdu Message from server: pdu Input message(Q to quit): q
第 16 章 关于I/O流分离的其他内容
16.1 分离I/O流
16.2 文件描述符的复制和半关闭
c16p261:文件描述符的复制函数原型 dup 和 dup2
#include <unistd.h> int dup(int fildes); int dup2(int fildes, int fildes2); // fildes: 需要复制的文件描述符 // fildes2: 明确指定的文件描述符整数值 // 成功时返回复制的文件描述符,失败时返回-1
c16p262:dup的程序示例 dup.c
#include <stdio.h> #include <unistd.h> int main() { int cfd1, cfd2; char str1[] = "Hi~ \n"; char str2[] = "It's nice day~ \n"; cfd1 = dup(1); cfd2 = dup2(cfd1, 7); printf("fd1=%d, fd2=%d\n", cfd1, cfd2); write(cfd1, str1, sizeof(str1)); write(cfd2, str2, sizeof(str2)); close(cfd1); close(cfd2); write(1, str1, sizeof(str1)); close(1); write(1, str1, sizeof(str2)); return 0; }
shiqi@pc:~/network/ch16$ gcc dup.c -o dup shiqi@pc:~/network/ch16$ ./dup fd1=3, fd2=7 Hi~ It's nice day~ Hi~
c16p263:复制文件描述符后「流」分离的服务端客户端程序示例 sep_serv.c 和 sep_clnt.c,可通过服务器端的半关闭状态接收客户端最后发送的字符串
没做到流分离的服务端 sep_serv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 int main(int argc, char * argv[]) { int serv_sock, clnt_sock; FILE * readfp; FILE * writefp; struct sockaddr_in serv_adr, clnt_adr; socklen_t clnt_adr_sz; char buf[BUF_SIZE] = {0,}; serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)); listen(serv_sock, 5); clnt_adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz); readfp = fdopen(clnt_sock, "r"); writefp = fdopen(clnt_sock, "w"); fputs("FROM SERVER: Hi~ client?\n", writefp); fputs("I love all of the world\n", writefp); fputs("You are awesome!\n", writefp); fflush(writefp); fclose(writefp); fgets(buf, sizeof(buf), readfp); fputs(buf, stdout); fclose(readfp); return 0; }
流分离的服务端 sep_serv2.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 int main(int argc, char * argv[]) { int serv_sock, clnt_sock; FILE * readfp; FILE * writefp; struct sockaddr_in serv_adr, clnt_adr; socklen_t clnt_adr_sz; char buf[BUF_SIZE] = {0,}; serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)); listen(serv_sock, 5); clnt_adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz); readfp = fdopen(clnt_sock, "r"); // 注意此处的 dup writefp = fdopen(dup(clnt_sock), "w"); fputs("FROM SERVER: Hi~ client?\n", writefp); fputs("I love all of the world\n", writefp); fputs("You are awesome!\n", writefp); fflush(writefp); // 服务端进入半关闭状态,并向客户端发送 EOF shutdown(fileno(writefp), SHUT_WR); fclose(writefp); fgets(buf, sizeof(buf), readfp); fputs(buf, stdout); fclose(readfp); return 0; }
客户端 sep_clnt.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 1024 int main(int argc, char * argv[]) { int sock; char buf[BUF_SIZE]; struct sockaddr_in serv_addr; FILE * readfp; FILE * writefp; sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); readfp = fdopen(sock, "r"); writefp = fdopen(sock, "w"); while (1) { if (fgets(buf, sizeof(buf), readfp) == NULL) { break; } fputs(buf, stdout); fflush(stdout); } fputs("FROM CLIENT: Thank you!\n", writefp); fflush(writefp); fclose(writefp); fclose(readfp); return 0; }
编译运行
shiqi@pc:~/network/ch16$ gcc sep_serv.c -o serv shiqi@pc:~/network/ch16$ ./serv 9190 shiqi@pc:~/network/ch16$ gcc sep_clnt.c -o clnt shiqi@pc:~/network/ch16$ ./clnt 127.0.0.1 9190 FROM SERVER: Hi~ client? I love all of the world You are awesome!
shiqi@pc:~/network/ch16$ gcc sep_serv2.c -o serv shiqi@pc:~/network/ch16$ ./serv 9190 FROM CLIENT: Thank you! shiqi@pc:~/network/ch16$ ./clnt 127.0.0.1 9190 FROM SERVER: Hi~ client? I love all of the world You are awesome!
第 17 章 优于 select 的 epoll
17.1 epoll 理解及应用
c17p265:基于 select 的 I/O 复用技术速度慢的原因
- 调用 select 函数后常见的针对所有文件描述符的循环语句
- 每次调用 select 函数时都需要向该函数传递监视对象信息
c17p266:epoll的优点
- 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句
- 调用对应于select函数的epoll_wait函数时无需每次传递监视对象信息
Q:select 和 epoll 的区别
c17p267:epoll的3个函数
- epoll_create: 创建保存epoll文件描述符的空间
- epoll_ctl: 向空间注册并注销文件描述符
- epoll_wait: 与select函数类似,等待文件描述符发生变化
- epoll 由操作系统负责保存监视对象文件描述符,因此需要向操作系统请求创建保存文件描述符的空间,此时用的函数就是 epoll_create
- 为了添加和删除监视对象文件描述符,通过 epoll_ctl 函数请求操作系统完成
- epoll调用epoll_wait函数等待文件描述符的变化
c17p267:epoll中将发生变化的文件描述符单独集中到一起的epoll_event结构体
struct epoll_event { __uint32_t events; epoll_data_t data; }; typedef union epoll_data { void * ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;
声明epoll_event结构体数组后,传递给epoll_wait 函数时,发生变化的文件描述符信息将被填入数组
c17p268:epoll_create 的函数原型
#include <sys/epoll.h> int epoll_create(int size); // size: epoll实例的大小 // 成功时返回 epoll 的文件描述符,失败时返回 -1
调用 epoll_create 函数时创建的文件描述符保存空间称为「epoll 例程」,通过参数 size 传递的值决定 epoll 例程的大小,但该值只是向操作系统提出的建议。换言之,size 并不用来决定 epoll 的大小,而仅供操作系统参考
Linux 2.6.8 之后的内核将完全忽略传入 epoll_create 函数的 size 函数,因此内核会根据情况调整 epoll 例程大小
epoll_create 函数创建的资源与套接字相同,也由操作系统管理。因此,该函数和创建套接字的情况相同,也会返回文件描述符,也就是说返回的文件描述符主要用于区分 epoll 例程。需要终止时,与其他文件描述符相同,也要调用 close 函数
c17p268:epoll_ctl 的函数原型
生成例程后,应在其内部注册监视对象文件描述符,此时使用 epoll_ctl 函数
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // epfd: 用于注册监视对象的epoll例程的文件描述符 // op: 用于指定监视对象的添加、删除或更改等操作 // fd: 需要注册的监视对象文件描述符 // event: 监视对象的事件类型 // 成功时返回 0 ,失败时返回 -1
用法举例:
epoll_ctl(A,EPOLL_CTL_ADD,B,C);
:epoll 例程 A 中注册文件描述符 B ,主要目的是为了监视参数 C 中的事件epoll_ctl(A,EPOLL_CTL_DEL,B,NULL);
:从 epoll 例程 A 中删除文件描述符 B
第二个参数op:
- EPOLL_CTL_ADD:将文件描述符注册到epoll例程
- EPOLL_CTL_DEL:从epoll例程中删除文件描述符
- EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况
epoll_event结构体在epoll_ctl函数中的应用
struct epoll_event event; ... event.events = EPOLLIN; event.data.fd = sockfd; epoll_ctl(opfd, EPOLL_CTL_ADD, sockfd, &event); ...
上述代码将sockfd注册到epoll例程epfd中,并在需要读取数据的情况下产生相应事件。
Q:epoll_event成员events中可保存的常量和所指的事件类型
- EPOLLIN: 需要读取数据的情况
- EPOLLOUT: 输出缓冲为空,可以立即发送数据的情况
- EPOLLPRI: 收到OOB数据的情况
- EPOLLRDHUP: 断开连接或半关闭的情况,在边缘触发方式下非常有用
- EPOLLERR: 发生错误的情况
- EPOLLET: 以边缘触发的方式得到事件通知
- EPOLLONESHOT: 发生一次事件后,相应文件描述符不再收到事件通知。因此需向epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件
- 可通过位或运算同时传递多个上述参数
c17p270:epoll_wait 的函数原型
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); // epfd: 表示事件发生监视范围的epoll例程的文件描述符 // events: 保存发生事件的文件描述符集合的结构体地址值 // maxevents: 第二个参数中可以保存的最大事件数 // timeout: 以1/1000秒为单位的等待时间,传递-1时, // 一直等待直到发生事件 // 成功时返回发生事件的文件描述符,失败时返回 -1
调用方式:注意第二个参数所指缓冲需要动态分配
int event_cnt; struct epoll_event *op_events; ... // EPOLL_SIZE是宏常量 ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); ... event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); ...
调用函数后,返回发生事件的文件描述符数,同时第二个参数指向的缓冲中保存发生事件的文件描述符集合
Q:epoll回声服务器端条件触发示例echo_epollserv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h> #define BUF_SIZE 100 #define EPOLL_SIZE 50 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char *argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; socklen_t adr_sz; int str_len, i; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) { error_handling("bind() error"); } if (listen(serv_sock, 5) == -1) { error_handling("listen() error"); } epfd = epoll_create(EPOLL_SIZE); ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); event.events = EPOLLIN; event.data.fd = serv_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); while (1) { event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); if (event_cnt == -1) { puts("epoll_wait() error"); break; } for (i = 0; i < event_cnt; ++i) { if (ep_events[i].data.fd == serv_sock) { adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz); event.events = EPOLLIN; event.data.fd = clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); printf("connected client: %d \n", clnt_sock); } else { str_len = read(ep_events[i].data.fd, buf, BUF_SIZE); if (str_len == 0) { epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); close(ep_events[i].data.fd); printf("closed client: %d \n", ep_events[i].data.fd); } else { write(ep_events[i].data.fd, buf, str_len); } } } } close(serv_sock); close(epfd); return 0; }
shiqi@inspiron:~/network$ gcc echo_epollserv.c -o echo_epollserv shiqi@inspiron:~/network$ ./echo_epollserv 9190 connected client: 5 connected client: 6
shiqi@inspiron:~/network$ ./eclient2 127.0.0.1 9190 Connceted........ Input message(Q to quit): xp Message from server: xp Input message(Q to quit): q
17.2 条件触发和边缘触发
c17p274:条件触发(Level Trigger)和边缘触发(Edge Trigger)的特性
- 条件触发方式中,只要输入缓冲有数据就会一直通知该事件;只要输入缓冲中还剩有数据,就将以事件方式再次注册
- 边缘触发中输入缓冲收到数据时仅注册 1 次该事件。即使输入缓冲中还留有数据,也不会再进行注册
c17p274:条件触发和边缘触发的程序示例比较 echo_EPLTserv.c
条件触发每次收到客户端数据时,都会注册该事件,并因此多次调用 epoll_wait 函数,切换为边缘触发后,仅注册一次事件
服务端 echo_EPLTserv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h> // 减少缓冲大小以阻止服务器端一次性读取接收的数据 #define BUF_SIZE 4 #define EPOLL_SIZE 50 void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char *argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; socklen_t adr_sz; int str_len, i; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) { error_handling("bind() error"); } if (listen(serv_sock, 5) == -1) { error_handling("listen() error"); } epfd = epoll_create(EPOLL_SIZE); ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); event.events = EPOLLIN; event.data.fd = serv_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); while (1) { event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); if (event_cnt == -1) { puts("epoll_wait() error"); break; } for (i = 0; i < event_cnt; ++i) { if (ep_events[i].data.fd == serv_sock) { adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz); event.events = EPOLLIN; //event.events = EPOLLIN | EPOLLET; event.data.fd = clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); printf("connected client: %d \n", clnt_sock); } else { str_len = read(ep_events[i].data.fd, buf, BUF_SIZE); if (str_len == 0) { epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); close(ep_events[i].data.fd); printf("closed client: %d \n", ep_events[i].data.fd); } else { write(ep_events[i].data.fd, buf, str_len); } } } } close(serv_sock); close(epfd); return 0; }
编译测试
47@pc:~/network/ch17$ gcc echo_EPLTserv.c -o serv 47@pc:~/network/ch17$ ./serv 9190 return epoll_wait connected client: 5 return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait closed client: 5 47@pc:~/network/ch04$ ./eclient 127.0.0.1 9190 Connceted........ Input message(Q to quit): eeeeeeeee Message from server: eeeeeeeee Input message(Q to quit): 12345678 Message from server: 12345678Input message(Q to quit): 1234 Message from server: Input message(Q to quit): 1 Message from server: 1234 Input message(Q to quit): 222 Message from server: 1 Input message(Q to quit): q
把代码 71 行取消注释,注释 70 行后
47@pc:~/network/ch17$ gcc echo_EPTLserv.c -o serv 47@pc:~/network/ch17$ ./serv 9190 return epoll_wait connected client: 5 return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait return epoll_wait 47@pc:~/network/ch04$ ./eclient 127.0.0.1 9190 Connceted........ Input message(Q to quit): 12345678 Message from server: 1234Input message(Q to quit): 999999 Message from server: 5678Input message(Q to quit): 9999999999esh Message from server: 999Input message(Q to quit): 67890 Message from server: 999 Input message(Q to quit): q
c17p278:更改或读取文件属性的 fcntl 函数原型
#include <fcntl.h> int fcntl(int fieldes, int cmd, ...); // filedes: 属性更改目标的文件描述符 // cmd: 表示函数调用的目的 // 成功时返回 cmd 参数相关值,失败时返回-1
fcntl 具有可变参数的形式,如果向第二个参数传递 F_GETFL,可以获得第一个参数所指的文件描述符属性(int 型)。反之,如果传递 F_SETTL,可以更改文件描述符属性
c17p278:将文件(套接字)改为非阻塞模式的代码
int flag = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag|O_NONBLOCK);
通过第一条语句获取之前设置的属性信息,通过第二条语句在此基础上添加非阻塞 O_ NONBLOCK 标志。调用 read & write 函数时,无论是否存在数据,都会形成非阻塞文件(套接字)
c17p279:实现边缘触发的回声服务器的程序示例 echo_EPETserv.c
边缘触发接收数据仅注册 1 次事件,所以一旦发生输入相关事件就应该读取输入缓冲中的全部数据。其终止条件是验证输入缓冲是否为空:read 函数返回 -1,变量 errno 中的值为 EAGAIN时,说明没有数据可读
echo_EPETserv.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> #define BUF_SIZE 4 #define EPOLL_SIZE 50 void setnonblockingmode(int fd) { int flag = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag|O_NONBLOCK); } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } int main(int argc, char *argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; socklen_t adr_sz; int str_len, i; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if (argc != 2) { printf("Usage : %s <port>\n", argv[0]); exit(1); } serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) { error_handling("bind() error"); } if (listen(serv_sock, 5) == -1) { error_handling("listen() error"); } epfd = epoll_create(EPOLL_SIZE); ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); event.events = EPOLLIN; event.data.fd = serv_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); while (1) { event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); if (event_cnt == -1) { puts("epoll_wait() error"); break; } puts("return epoll_wait"); for (i = 0; i < event_cnt; ++i) { if (ep_events[i].data.fd == serv_sock) { adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz); setnonblockingmode(clnt_sock); event.events = EPOLLIN | EPOLLET; event.data.fd = clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); printf("connected client: %d \n", clnt_sock); } else { while (1) { str_len = read(ep_events[i].data.fd, buf, BUF_SIZE); if (str_len == 0) { epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); close(ep_events[i].data.fd); printf("closed client: %d \n", ep_events[i].data.fd); break; } else if (str_len < 0) { if (errno == EAGAIN) { break; } } else { write(ep_events[i].data.fd, buf, str_len); } } } } } close(serv_sock); close(epfd); return 0; }
编译运行
47@pc:~/Devel/network/ch17$ gcc echo_EPETserv.c -o serv 47@pc:~/Devel/network/ch17$ ./serv 9190 return epoll_wait connected client: 5 return epoll_wait return epoll_wait return epoll_wait closed client: 5 47@pc:~/Devel/network/ch04$ ./eclient 127.0.0.1 9190 Connceted........ Input message(Q to quit): 123456789 Message from server: 123456789 Input message(Q to quit): 987ehem Message from server: 987ehem Input message(Q to quit): q
c17p282:边缘触发相比条件触发的优点是什么?
- 可以分离接收数据和处理数据的时间点