文件I/O方式比较
1.阻塞式文件I/O
进程从调用函数开始,直到返回这段时间都处于阻塞状态。
2.非阻塞式文件I/O
如果当前没有数据可操作,将不阻塞当前进程,而是立即返回一个错误信息。需要反复尝试。
3.多路复用I/O
仍然是阻塞方式等待,但是可以同时等待多个文件描述符。
4.信号驱动I/O
异步方式,等到数据准备好后通知处理进程,不需要重复询问,效率高。
I/O阻塞与非阻塞操作
阻塞方式:默认情况下read/write和 把flag设为0的recv/send
非阻塞方式:如果没有数据,立刻返回-1表示失败,并修改系统全局变量errno的值为EAGAIN,表示数据未准备好。
通过设置recv的MSG_DONTWAIT标志可以实现。如果设置socket的文件描述符的属性为非阻塞,将导致后续所有针对该文件描述符的操作都为非阻塞。
例子:
服务器端:接收非阻塞,发送阻塞。 可以连续发送多条。如果对方发送的很多数据过来,也会一次性接收。
客户端:发送、接收都阻塞。这个例子里面,接收端一定是收一条、发一条这样交替着的。如果服务器发送了多条,则会分开接收到。
奇怪:这个例子里面,接收端也绑定了自己的地址,而之前的例子里却没有绑定。两者都实现了通信,为什么呢?
服务器代码:
#include<sys/types.h> #include<sys/socket.h> #include<stdio.h> #include<string.h> #include<netinet/in.h> #include<arpa/inet.h> #include<unistd.h> #include<stdlib.h> #include<errno.h> #define BUFSIZE 128 int main(int argc, char *argv[]) { int server_sockfd, client_sockfd; int server_len, client_len; struct sockaddr_in server_address; struct sockaddr_in client_address; int i, byte; char char_send[BUFSIZE]; //创建socket对象 阻塞 server_sockfd = socket(AF_INET, SOCK_STREAM, 0); server_address.sin_family = AF_INET; //从argv[1]提取IP地址 if(inet_aton(argv[1],(struct in_addr*)&server_address.sin_addr.s_addr) == 0) { perror(argv[1]); exit(EXIT_FAILURE); } server_address.sin_port = htons(7838); //使用特定端口 server_len = sizeof(server_address); //绑定IP信息 bind(server_sockfd, (struct sockaddr *)&server_address, server_len); //监听网络 listen(server_sockfd, 5); printf("server waiting for connect\n"); client_len = sizeof(client_address); //等待连接 client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_address,(socklen_t *)&client_len); for(i = 0; i < 5; i++) { memset(char_send, ‘\0‘, BUFSIZE); printf("input message to send:"); fgets(char_send, BUFSIZE, stdin); //阻塞在终端,接收用户输入数据 //发送 if((byte = send(client_sockfd, char_send, strlen(char_send), 0)) == -1) { perror("send"); exit(EXIT_FAILURE); } memset(char_send, ‘\0‘, BUFSIZE); //非阻塞接收 byte = recv(client_sockfd, char_send, BUFSIZE, MSG_DONTWAIT); if(byte > 0) { printf("get %d message:%s", byte, char_send); byte = 0; } else if(byte < 0) { if(errno == EAGAIN) { errno = 0; continue; } else { perror("recv"); exit(EXIT_FAILURE); } } } //关闭socket对象 shutdown(client_sockfd, 2); shutdown(server_sockfd, 2); }
客户端代码:
#include<stdio.h> #include<string.h> #include<errno.h> #include<sys/socket.h> #include<resolv.h> #include<stdlib.h> #include<netinet/in.h> #include<arpa/inet.h> #include<unistd.h> #include<fcntl.h> #define MAXBUF 128 int main(int argc, char **argv) { int sockfd, ret, i; struct sockaddr_in dest, mine; char buffer[MAXBUF + 1]; //创建socket对象 if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket"); exit(EXIT_FAILURE); } bzero(&dest, sizeof(dest)); dest.sin_family = AF_INET; dest.sin_port = htons(7838); //服务器的特定端口,与服务器端设置一致 //获取服务器IP地址,由argv[1]指定 if(inet_aton(argv[1], (struct in_addr *)&dest.sin_addr.s_addr) == 0) { perror(argv[1]); exit(EXIT_FAILURE); } bzero(&mine, sizeof(mine)); mine.sin_family = AF_INET; mine.sin_port = htons(7839); //本地端口 //本地IP地址,由argv[2]指定 if(inet_aton(argv[2], (struct in_addr *)&mine.sin_addr.s_addr) == 0) { perror(argv[2]); exit(EXIT_FAILURE); } //绑定自己的IP地址信息 if(bind(sockfd, (struct sockaddr *)&mine, sizeof(struct sockaddr)) == -1) { perror("bind"); exit(EXIT_FAILURE); } //发起连接 if(connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) != 0) { perror("Connect"); exit(EXIT_FAILURE); } //设置sockfd描述符为非阻塞 if(fcntl(sockfd, F_SETFL, O_NONBLOCK) == -1) { perror("fcntl"); exit(EXIT_FAILURE); } while(1) { bzero(buffer, MAXBUF + 1); //接收 ret = recv(sockfd, buffer, MAXBUF, 0); //因为设置socket非阻塞,故此操作非阻塞 if(ret > 0) { printf("get %d message:%s", ret, buffer); ret = 0; } else if(ret < 0) { if(errno == EAGAIN) { errno = 0; continue; } else { perror("recv"); exit(EXIT_FAILURE); } } memset(buffer, ‘\0‘, MAXBUF + 1); printf("input message to send:"); fgets(buffer, MAXBUF, stdin); //在接收到数据后阻塞在终端,向对方发 if((ret = send(sockfd, buffer, strlen(buffer), 0)) == -1) //发送数据 { perror("send"); exit(EXIT_FAILURE); } } close(sockfd); return 0; }
我在同一台虚拟机的不同终端做实验
服务器端结果:
客户端结果:
时间: 2024-12-19 21:53:29