既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
一、为什么要使用非阻塞I/O之select
初学socket的人可能不爱用select写程序,而习惯诸如connect、accept、recv/recvfrom这样的阻塞程序。
当让服务器同时为多个客户端提供一问一答服务时,很多程序员采用多线程/进程模型来解决。但是若同时响应成百上千的连接请求,无论是多进程还是多线程都会严重占据系统资源降低系统对外响应的效率。(“线程池”旨在降低创建和销毁线程的频率,“连接池”旨在尽量重用已有连接,二者都需要考虑面临的响应规模,即池的大小是有限的)。
高级程序员使用select就可以完成非阻塞方式工作的程序,它能够监视被监测文件描述符的变化情况。
使用select的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多CPU资源,同时能为多客户端提供服务。当然select也有缺点如下:
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- 每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- select支持的文件描述符数量太小了,默认是1024
后面学习poll、epoll就是解决这个问题的,这个后面会了解到。
二、slect函数原型
int select(int maxfdp,fd_set \*readfds,fd_set \*writefds,fd_set \*errorfds,struct timeval \*timeout);
具体解释select的参数:
- maxfdp是一个整数值,集合中所有文件描述符的范围,即所有文件描述符的最大值加1。
- fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0;若发生错误返回负值。
- fd_set *writefds是指向fd_set结构的指针,主要关心文件的写变化,即是否可写。
- fd_set *errorfds用来监视文件错误异常
返回值:
正值表示准备就绪的描述符数, 0表示等待超时,负值表示select出错
三、使用select函数循环读取键盘输入
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
int main()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY |O_NONBLOCK);
assert(keyboard>0);
while(1)
{
timeout.tv_sec = 5;
timeout.tv_usec = 0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret = select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(ret == -1)
perror("select error\n");
else if (ret) {
if(FD_ISSET(keyboard,&readfd)) {
i = read(keyboard,&c,1);
if('\n'== c)
continue;
printf("The input is %c\n",c);
if('q'==c)
break;
}
}
else if (ret ==0)
printf("time out\n");
}
return 0;
}
只要发现键盘输入字符,程序就输出对应字符。若超过5s不输入,打印time out。
四、使用select函数提高服务器处理能力
服务器端:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#define DEFAULT\_PORT 6666
int main( int argc, char ** argv){
int serverfd,acceptfd; /\* 监听socket: serverfd,数据传输socket: acceptfd \*/
struct sockaddr_in my_addr; /\* 本机地址信息 \*/
struct sockaddr_in their_addr; /\* 客户地址信息 \*/
unsigned int sin_size, myport=6666, lisnum=10;
if ((serverfd = socket(AF_INET , SOCK_STREAM, 0)) == -1) {
perror("socket" );
return -1;
}
printf("socket ok \n");
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(DEFAULT_PORT);
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero), 0);
if (bind(serverfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr )) == -1) {
perror("bind" );
return -2;
}
printf("bind ok \n");
if (listen(serverfd, lisnum) == -1) {
perror("listen" );
return -3;
}
printf("listen ok \n");
fd_set client_fdset; /\*监控文件描述符集合\*/
int maxsock; /\*监控文件描述符中最大的文件号\*/
struct timeval tv; /\*超时返回时间\*/
int client_sockfd[5]; /\*存放活动的sockfd\*/
bzero((void*)client_sockfd,sizeof(client_sockfd));
int conn_amount = 0; /\*用来记录描述符数量\*/
maxsock = serverfd;
char buffer[1024];
int ret=0;
/\*不断的查看是否有新的client连接;已连接的client是否有发送消息过来\*/
while(1){
/\*初始化文件描述符号到集合\*/
FD_ZERO(&client_fdset);
/\*加入服务器描述符\*/
FD_SET(serverfd,&client_fdset);
/\*设置超时时间\*/
tv.tv_sec = 30; /\*30秒\*/
tv.tv_usec = 0;
/\*把活动的句柄加入到文件描述符中\*/
for(int i = 0; i < 5; ++i){
/\*程序中Listen中参数设为5,故i必须小于5\*/
if(client_sockfd[i] != 0){
FD_SET(client_sockfd[i], &client_fdset);
}
}
/\*printf("put sockfd in fdset!\n");\*/
/\*select函数,根据返回值判断程序是否有异常\*/
ret = select(maxsock+1, &client_fdset, NULL, NULL, &tv);
if(ret < 0){
perror("select error!\n");
break;
} else if(ret == 0){
printf("timeout!\n");
continue;
}
/\*轮询各个(已连接上的client的)文件描述符有无可读(接收)数据,有就输出,没有或者异常时,关闭相应的client连接,并在集合里清理掉\*/
for(int i = 0; i < conn_amount; ++i){
/\*FD\_ISSET检查client\_sockfd是否可读写,>0可读写\*/
if(FD_ISSET(client_sockfd[i], &client_fdset)){
printf("start recv from client[%d]:\n",i);
ret = recv(client_sockfd[i], buffer, 1024, 0);
if(ret <= 0){
printf("client[%d] close\n", i);
close(client_sockfd[i]);
FD_CLR(client_sockfd[i], &client_fdset);
client_sockfd[i] = 0;
}
else{
printf("recv from client[%d] :%s\n", i, buffer);
}
}
}
/\*检查是否有新的连接,如果有,接收连接加入到client\_sockfd中\*/
if(FD_ISSET(serverfd, &client_fdset))


**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**
4506)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**