1. 高性能I/O
(1)通常,recv函数没有数据可用时会阻塞等待。同样,当socket发送缓冲区没有足够多空间来发送消息时,函数send会阻塞。
(2)当socket在非阻塞模式下,这些函数不会阻塞,如果发送/接收缓冲区没有数据时,调用会失败并设置errno为EWOULDBLOCK或EAGAIN。
(3)可以调用fcntl函数实现非阻塞式I/O或调用select实现I/O多路复用以提高使用I/O而出现的效率问题。
2. 非阻塞I/O模型: fcntl函数
【编程实验】echo服务器(非阻塞IO方式实现)
(1)主线程创建一个服务于所有客户端的子线程。
(2)主线程调用accept与客户端建立连接,并将新的socket设置为非阻塞方式。然后将这个新的socket放入数组中。
(3)利用一个子线程遍历(轮询)数组中各个socket,并调用read/write(非阻塞式)与客户端进行通信。
//vector_fd.h
#ifndef __VECTOR_H__ #define __VECTOR_H__ #include <pthread.h> //用于存放sock的动态数组(线程安全!) typedef struct{ int *fd; int counter; //元素个数 int max_counter;//最多存数个数,会动态增长 pthread_mutex_t mutex; }VectorFD, *PVectorFD; //动态数组相关的操作函数 extern VectorFD* create_vector_fd(void); extern void destroy_vector_fd(VectorFD* vfd); extern int get_fd(VectorFD* vfd, int index); extern void remove_fd(VectorFD* vfd, int fd); extern void add_fd(VectorFD* vfd, int fd); #endif
//vector_fd.c //动态数组操作函数
#include "vector_fd.h" #include <memory.h> #include <malloc.h> #include <assert.h> //查找指定fd在数组中的索引值 static int indexof(VectorFD* vfd, int fd) { int ret = -1; int i=0; for(; i<vfd->counter; i++){ if(vfd->fd[i] == fd){ ret = i; break; } } return ret; } //数组空间的动态增长 static void encapacity(VectorFD* vfd) { if(vfd->counter >=vfd->max_counter){ int* fds = (int*)calloc(vfd->counter + 5, sizeof(int)); assert(fds != NULL); memcpy(fds, vfd->fd, sizeof(int) * vfd->counter); free(vfd->fd); vfd->fd = fds; vfd->max_counter += 5; } } //动态数组相关的操作 VectorFD* create_vector_fd(void) { VectorFD* vfd = (VectorFD*)calloc(1, sizeof(VectorFD)); assert(vfd != NULL); //分配存放fd的数组空间 vfd->fd = (int*)calloc(5, sizeof(int)); assert(vfd->fd != NULL); vfd->counter = 0; vfd->max_counter = 0; //对互斥锁进行初始化 pthread_mutex_init(&vfd->mutex, NULL); return vfd; } void destroy_vector_fd(VectorFD* vfd) { assert(vfd != NULL); //销毁互斥锁 pthread_mutex_destroy(&vfd->mutex); free(vfd->fd); free(vfd); } int get_fd(VectorFD* vfd, int index) { int ret = 0; assert(vfd != NULL); pthread_mutex_lock(&vfd->mutex); if((0 <= index) && (index < vfd->counter)){ ret = vfd->fd[index]; } pthread_mutex_unlock(&vfd->mutex); return ret; } void remove_fd(VectorFD* vfd, int fd) { assert(vfd != NULL); pthread_mutex_lock(&vfd->mutex); int index = indexof(vfd, fd); if(index >= 0){ int i = index; for(; i<vfd->counter-1; i++){ vfd->fd[i] = vfd->fd[i+1]; } vfd->counter--; } pthread_mutex_unlock(&vfd->mutex); } void add_fd(VectorFD* vfd, int fd) { assert(vfd != NULL); encapacity(vfd); vfd->fd[vfd->counter++] = fd; }
//echo_tcp_server_fcntl.c
#include <netdb.h> #include <sys/socket.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> #include <time.h> #include <pthread.h> #include <signal.h> #include <errno.h> #include "vector_fd.h" #include <fcntl.h> /*基于非阻塞IO的高并发服务器编程 测试:telnet 127.0.0.1 xxxx http://xxx.xxx.xxx.xxx:端口号 注意:演示时可关闭服务器的防火墙,防火墙口被过滤 #service iptables status 查看防火墙 #service iptables stop 关闭防火墙 */ VectorFD* vfd; int sockfd; int bStop = 0; void sig_handler(int signo) { if(signo == SIGINT){ bStop = 1; printf("server close\n"); exit(1); } } void out_addr(struct sockaddr_in* clientAddr) { char ip[16]; memset(ip, 0, sizeof(ip)); int port = ntohs(clientAddr->sin_port); inet_ntop(AF_INET, &clientAddr->sin_addr.s_addr, ip, sizeof(ip)); printf("%s(%d) connnected!\n", ip, port); } /*服务程序 * fd对应于某个连接的客户端,和某一个连接的客户端进行双向通信(非阻塞方式) */ void do_service(int fd) { /*服务端和客户端进行读写操作(双向通信)*/ char buff[512]; memset(buff, 0, sizeof(buff)); size_t size = read(fd, buff, sizeof(buff)); //读取客户端发送过来的消息 //由于采用非阻塞方式,若读不到数据直接返回了,直接服务于下一个客户端 //因此不需要判断size小于0的情况。 if(size == 0){ //客户端己关闭连接 char info[] = "client close\n"; write(STDOUT_FILENO, info, sizeof(info)); //将fd从动态数组中删除 remove_fd(vfd, fd); close(fd); }else if(size > 0){ write(STDOUT_FILENO, buff, sizeof(buff));//显示客户端发送的消息 //写回客户端(回显功能) if(write(fd, buff, sizeof(buff)) != size){ if(errno == EPIPE){ //如果客户端己被关闭(相当于管道的读端关闭),会产生SIGPIPE信号 //并将errno设置为EPIPE perror("write error"); remove_fd(vfd, fd); close(fd); } } } } //线程函数 void* th_fn(void* arg) { int i= 0; //轮询动态数组中的socket描述符 while(!bStop){ for(i=0; i<vfd->counter; i++){ do_service(get_fd(vfd, i)); } } return (void*)0; } int main(int argc, char* argv[]) { if(argc < 2){ printf("usage: %s port\n", argv[0]); exit(1); } //按ctrl-c时中止服务端程序 if(signal(SIGINT, sig_handler) == SIG_ERR){ perror("signal sigint error"); exit(1); } /*步骤1:创建socket(套接字) *注:socket创建在内核中,是一个结构体 *AF_INET:IPv4 *SOCK_STREAM:tcp协议 */ sockfd = socket(AF_INET, SOCK_STREAM, 0); /*步骤2:将sock和地址(包括ip、port)进行绑定*/ struct sockaddr_in servAddr; //使用专用地址结构体 memset(&servAddr, 0, sizeof(servAddr)); //往地址中填入ip、port和Internet地址族类型 servAddr.sin_family = AF_INET;//IPv4 servAddr.sin_port = htons(atoi(argv[1])); //port servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) <0 ){ perror("bind error"); exit(1); } /*步骤3:调用listen函数启动监听 * 通知系统去接受来自客户端的连接请求 */ if(listen(sockfd, 10) < 0){ //队列中最多允许10个连接请求 perror("listen error"); exit(1); } //创建放置套接字描述符的动态数组 vfd = create_vector_fd(); //设置线程的分离属性 pthread_t th; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //启动子线程 int err; if((err = pthread_create(&th, &attr, th_fn, (void*)0)) != 0){ perror("pthread create error"); exit(1); } pthread_attr_destroy(&attr); /*(1)主线程获得客户端连接,将新的socket描述符放置到动态数组中 *(2)子线程负责遍历动态数组中socket描述符并和对应的客户端进行 * 双向通信(采用非阻塞方式读写) */ struct sockaddr_in clientAddr; socklen_t len = sizeof(clientAddr); while(!bStop){ /*步骤4:调用accept函数,从请求队列中获取一个连接 * 并返回新的socket描述符 * */ int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &len); if(fd < 0){ perror("accept error"); continue; } //将读写修改为非阻塞方式 int val; fcntl(fd, F_GETFL, &val); val |= O_NONBLOCK; //非阻塞式 fcntl(fd, F_SETFL, val); //输出客户端信息 out_addr(&clientAddr); //将返回的新socket描述符加入到动态数组中 add_fd(vfd, fd); } close(sockfd); destroy_vector_fd(vfd); return 0; } /*输出结果 * [root@localhost 15.AdvNet]# gcc -o bin/echo_tcp_client src/echo_tcp_client.c * [root@localhost 15.AdvNet]# bin/echo_tcp_server_fcntl 8888 * 127.0.0.1(40694) connnected! * abcdefaadeafcdafacdaegadeageadfacadegadaddeagdafddeagd^Cserver close */
//echo_tcp_client.c
#include <netdb.h> #include <sys/socket.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> int main(int argc, char* argv[]) { if(argc < 3){ printf("usage: %s ip port\n", argv[0]); exit(1); } /*步骤1: 创建socket(套接字)*/ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0){ perror("socket error"); } //往servAddr中填入ip、port和地址族类型 struct sockaddr_in servAddr; memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_port = htons(atoi(argv[2])); //将ip地址转换成网络字节序后填入servAdd中 inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr); /*步骤2: 客户端调用connect函数连接到服务器端*/ if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){ perror("connect error"); exit(1); } /*步骤3: 调用自定义的协议处理函数和服务端进行双向通信*/ char buff[512]; size_t size; char* prompt = ">"; while(1){ memset(buff, 0, sizeof(buff)); write(STDOUT_FILENO, prompt, 1); size = read(STDIN_FILENO, buff, sizeof(buff)); if(size < 0) continue; buff[size-1] = '\0'; //将键盘输入的内容发送到服务端 if(write(sockfd, buff, sizeof(buff)) < 0){ perror("write error"); continue; }else{ memset(buff, 0, sizeof(buff)); //读取来自服务端的消息 if(read(sockfd, buff, sizeof(buff)) < 0){ perror("read error"); continue; }else{ printf("%s\n", buff); } } } /*关闭套接字*/ close(sockfd); } /*输出结果 *[root@localhost 15.AdvNet]# bin/echo_tcp_client 127.0.0.1 8888 >abcdef abcdef >aade aade >afcdaf afcdaf >acdaeg acdaeg >^C */