Linux网络编程——网络高级编程

2019-07-12 18:44发布

      在实际情况中,人们往往遇到多个客户端连接服务端的情况。由于connect()、recv()和send()等都是阻塞性函数,如果资源没有准备好,则调用该函数的进程将进入睡眠状态,这样就无法处理I/O多路复用的情况。以下给出两个解决办法: 1.fcntl() (1)fcntl()回顾: fcntl()函数语法要点 所需头文件 #include #include #include 函数原型 int fcntl(int fd,int cmd,struct flock *lock) 函数传入值 fd:文件描述符 cmd F_DUPFD:复制文件描述符 F_GETFD:获得fd的close-on-exec标志,若标志未设置,则文件经过exec()函数之后仍保持打开状态 F_SETFD:设置close-on-exec标志,该标志由参数arg的FD_CLOEXEC位决定 F_GETFL:得到open设置的标志 F_SETFL:改变open设置的标志 F_GETLK:根据lock参数值,决定是否上文件锁 F_SETLK:设置lock参数值的文件锁 F_SETLKW:这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。在无法获取锁时,会进入睡眠状态;如果可以获取锁或者捕捉到信号则会返回 lock:结构为flock,设置记录锁的具体状态 函数返回值 0:成功 -1:出错 lock的结构如下所示: struct flock { short l_type; //F_RDLCK:读取锁(共享锁) //F_WRLCK:写入锁(排斥锁) //F_UNLCK:解锁 off_t l_start; //相对位移量(字节) short l_whence; //相对位移量的起点 //SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小 //SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量 //SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小 off_t l_len; //加锁区域的长度 pid_t l_pid; } (2)函数fcntl()针对socket编程提供了如下的编程特性: (1)非阻塞I/O:可将cmd设置为F_SETFL,将lock设置为O_NONBLOCK。 (2)异步I/O:可将cmd设置为F_SETFL,将lock设置为O_ASYNC。 下面是用fcntl()将套接字设置为非阻塞I/O的实例代码: /*net_fcntl.c*/ #include #include #include #include #include #include #include #include #include #define PORT 1234 //端口号 #define BUFFER_SIZE 1024 #define MAX_QUE_CONN_NM 5 int main() { struct sockaddr_in server_sockaddr,client_sockaddr; /* Structure describing an Internet (IP) socket address. */ int sin_size,recvbytes,flags; int sockfd,client_fd;//sockfd=文件描述符 char buf[BUFFER_SIZE]; /*建立socket连接*/ if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//AF_INET=IPv4,SOCK_STREAM=TCP { perror("socket"); exit(1); } printf("Socket id = %d ",sockfd); /*设置sockaddr_in结构体中相关参数*/ server_sockaddr.sin_family = AF_INET; //sin_family=地址族,AF_INET=IPv4 server_sockaddr.sin_port = htons(PORT);//sin_port=端口号,htons()主机字节序>转化 server_sockaddr.sin_addr.s_addr = INADDR_ANY;//s_addr=IP地址 bzero(&(server_sockaddr.sin_zero),8);//填充0以保持与struct sockaddr同样大小 int i=1;//允许重复使用本地地址与套接字进行绑定 if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) < 0)//用于任意类型、任意状态套接口的设置选项值 { perror("setsockopt"); exit(1); } /*sockfd = 标识一个套接口的描述字 level = 选项定义的层次:支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6 optname = 需设置的选项 optval = 指针,指向存放选项待设置的新值的缓冲区 optlen = optval缓冲区长度*/ /*绑定函数bind()*/ if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)//server_sockaddr = 本地地址 { //sizeof(struct sockaddr) = 地址长度 perror("bind"); exit(1); } printf("Bind success! "); /*调用listen()函数,创建未处理请求的队列*/ if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5 { perror("listen"); exit(1); } printf("Listening... "); flags = fcntl(sockfd,F_GETFL); if(flags < 0 || fcntl(sockfd,F_SETFL,flags|O_NONBLOCK) < 0) { perror("fcntl"); exit(1); } while(1) { sin_size = sizeof(struct sockaddr_in); /*调用accept()函数,等待客户端的连接*/ if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) == -1)//client_sockaddr = 客户端地址 { //地址长度 perror("accept"); exit(1); } /*调用recv()函数接收客户端的请求*/ if((recvbytes = recv(client_fd,buf,BUFFER_SIZE,0)) == -1)//返回接收 的字节数 { perror("recv"); exit(1); } printf("Received a message:%s ",buf); } close(sockfd); exit(0); } 2.select() (1)select()回顾 select()函数语法要点 所需头文件 #include #include #include 函数原型 int select(int numfds,fd_set *readfds,fd_set *writefds,fd_set *exeptfds,struct timeval *timeout) 函数传入值 numfds:该参数值为需要监视的文件描述法的最大值加1 readfds:由select()监视的读文件描述符集合 writefds:由select()监视的写文件描述符集合 exeptfds:由select()监视的异常处理文件描述符集合 timeout NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止 具体值:struct timeval 类型的指针,若等待了timeout时间还没有检测到任何文件描述符准备好,就立即返回 0:从不等待,测试所有指定的描述符并立即返回 函数返回值 大于0:成功,返回准备好的文件描述符的数量 0:超时 -1:出错 对文件描述符进行处理的宏函数: select()文件描述符处理函数 FD_ZERO(fd_set *set) 清除一个文件描述符集合 FD_SET(int fd,fd_set *set) 将一个文件描述符加入文件描述符集合 FD_CLR(int fd,fd_set *set) 将一个文件描述法从文件描述符集中清除 FD_ISSET(int fd,fd_set *set) 如果文件描述法fd为fd_set集合中的一个元素,则返回非零值,可以用于调用select()之后测试文件描述符集合中的文件描述符是否有变化 PS:在使用select()之前,首先使用FD_ZERO()和FD_SET()来初始化文件描述符集合;         在使用select()时,循环使用FD_ISSET()来测试描述符集合;         在执行完相关描述符的操作后,使用FD_CLR()来清除描述符集合; select()函数中的timeout是一个struct timeval类型的指针,结构体如下: struct timeval { long tv_sec; /* 秒 */ long tv_unsec; /* 微妙 */ } (2)下面是使用select()函数的服务器端源代码: /*net_select.c*/ #include #include #include #include #include #include #include #include #define PORT 4321 //端口号 #define BUFFER_SIZE 1024 #define MAX_QUE_CONN_NM 5 #define MAX_SOCK_FD FD_SETSIZE int main() { struct sockaddr_in server_sockaddr,client_sockaddr; /* Structure describing an Internet (IP) socket address. */ int sin_size,count; fd_set inset,tmp_inset; int sockfd,client_fd,fd;//sockfd=文件描述符 char buf[BUFFER_SIZE]; /*建立socket连接*/ if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)//AF_INET=IPv4,SOCK_STREAM=TCP { perror("socket"); exit(1); } //printf("Socket id = %d ",sockfd); /*设置sockaddr_in结构体中相关参数*/ server_sockaddr.sin_family = AF_INET; //sin_family=地址族,AF_INET=IPv4 server_sockaddr.sin_port = htons(PORT);//sin_port=端口号,htons()主机字节序>转化 server_sockaddr.sin_addr.s_addr = INADDR_ANY;//s_addr=IP地址 bzero(&(server_sockaddr.sin_zero),8);//填充0以保持与struct sockaddr同样大小 int i=1;//允许重复使用本地地址与套接字进行绑定 if((setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))) < 0)//用于任意 类型、任意状态套接口的设置选项值 { perror("setsockopt"); exit(1); } /*sockfd = 标识一个套接口的描述字 level = 选项定义的层次:支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6 optname = 需设置的选项 optval = 指针,指向存放选项待设置的新值的缓冲区 optlen = optval缓冲区长度*/ /*绑定函数bind()*/ if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr)) == -1)//server_sockaddr = 本地地址 { //sizeof(struct sockaddr) = 地址长度 perror("bind"); exit(1); } printf("Bind success! "); /*调用listen()函数,创建未处理请求的队列*/ if(listen(sockfd,MAX_QUE_CONN_NM) == -1)//MAX_QUE_CONN_NM=请求队列中允许的>最大请求数,大多数系统默认值为5 { perror("listen"); exit(1); } printf("Listening... "); /*将调用socket()函数的描述符作为文件描述符*/ FD_ZERO(&inset);//将inset清零使集合中不含任何fd FD_SET(sockfd,&inset);//将fd加入inset集合 while(1) { tmp_inset = inset; sin_size = sizeof(struct sockaddr_in); memset(buf,0,sizeof(buf)); /*调用select()函数*/ if(!(select(MAX_SOCK_FD,&tmp_inset,NULL,NULL,NULL)) > 0) { perror("select"); } for(fd=0;fd 0) { if(fd == sockfd) { /*服务端接收客户端的连接请求*/ /*调用accept()函数,等待客户端的连接*/ if((client_fd = accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size)) == -1)//client_sockaddr = 客户端地址 { //地址长度 perror("accept"); exit(1); } FD_SET(client_fd,&inset); printf("New connection from %d(socket) ",client_fd); } else { /*调用recv()函数接收客户端的请求*/ if((count = recv(fd,buf,BUFFER_SIZE,0)) > 0)//返回接收的字节数 { printf("Received a message from %d:%s ",fd,buf); } else { close(fd); FD_CLR(fd,&inset); printf("Client %d(socket) has left ",fd); } } } } } close(sockfd); exit(0); }