嵌入式 Linux网络编程(四)——Select机制

嵌入式 Linux网络编程(四)——Select机制

一、select工作机制

poll和select,都是基于内核函数sys_poll实现的,不同在于在linux中select是从BSD Unix系统继承而来,poll则是从SYSTEM V Unix系统继承而来,因此两种方式相差不大。poll函数没有最大文件描述符数量的限制。poll和 select与一样,大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,开销随着文件描述符数量的增加而线性增大。

select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源 可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。

1、select的睡眠过程

支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作。当应用程序通过设备驱动访问设备时(默认为BLOCK操作),若设备当前没有数据可读或写,则将用户进程插入到设备驱动对应的读/写等待队列让其睡眠一段时间,等到有数据可读/写时再将用户进程唤醒。
        select巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。
        select会循环遍历所监测的fd_set(一组文件描述符(fd)的集合)内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后,如果没有一个资源可用(即没有一个文件可供操作),则select让进程睡眠,一直等到有资源可用为止,进程被唤醒(或者timeout)继续往下执行。

2、select的唤醒

select会循环遍历它所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的 用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。

唤醒进程的过程通常是在所监测文件的设备驱动内实现的,驱动程序维护了针对自身资源读写的等待队列。当设备驱动发现自身资源变为可读写并且有进程睡眠在自身资源的等待队列上时,就会唤醒自身资源等待队列上的进程。

3、select函数

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

ndfs:select监视的文件句柄数,一般设为要监视各文件中的最大文件描述符值加1。

readfds:文件描述符集合监视文件集中的任何文件是否有数据可读,当select函数返回的时候,readfds将清除其中不可读的文件描述符,只留下可读的文件描述符。

writefds:文件描述符集合监视文件集中的任何文件是否有数据可写,当select函数返回的时候,writefds将清除其中不可写的文件描述符,只留下可写的文件描述符。

exceptfds:文件集将监视文件集中的任何文件是否发生错误,可用于其他的用途,例如,监视带外数据OOB,带外数据使用MSG_OOB标志发送到套接字上。当select函数返回的时候,exceptfds将清除其中的其他文件描述符,只留下标记有OOB数据的文件描述符。

timeout:本次select()的超时结束时间。这个参数至关重要,它可以使select处于三种状态:

(1)若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;

(2)若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;

(3)timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

struct timeval{

long tv_sec;   /*秒 */

long tv_usec;  /*微秒 */

}

返回值为正值表示监视的文件集中有文件描述符符合要求,为零值表示select监视超时,为负值表示发生了错误,错误值由errno指定。

文件描述符集合的操作:

void FD_CLR(int fd, fd_set *set);

int  FD_ISSET(int fd, fd_set *set);

void FD_SET(int fd, fd_set *set);

void FD_ZERO(fd_set *set);

select并发服务器模型:

socket(...); // 创建套接字
bind(...);   // 绑定
listen(...); // 监听
 
while(1)
{
    if(select(...) > 0) // 检测监听套接字是否可读
    {
        if(FD_ISSET(...)>0) // 套接字可读,证明有新客户端连接服务器  
        {
            accpet(...);// 取出已经完成的连接
            process(...);// 处理请求,反馈结果
        }
    }
    close(...); // 关闭连接套接字:accept()返回的套接字
}

二、select编程模型

程序实例:select实现的并发服务器,接收客户端发送的内容并显示

server.c:

#include <sys/types.h>  
#include <sys/socket.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/time.h>  
#include <sys/ioctl.h>  
#include <unistd.h>  
#include <netinet/in.h> 
#include <arpa/inet.h>
 
#define PORT 8888
#define RT_ERR (-1)
#define RT_OK 0
#define SERVERIP "192.168.0.200"
#define LISTEN_QUEUE 10
#define BUFFER_SIZE 1024
 
int main(int argc, char *argv[])
{
    int listenfd, connsockfd, fd;
    char readbuf[BUFFER_SIZE];
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0)
    {
            fprintf(stderr, "socket function failed.\n");
            exit(RT_ERR);
    }
    
    struct sockaddr_in serveraddr, clientaddr;
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = inet_addr(SERVERIP);
    
    unsigned int client_len = sizeof(struct sockaddr_in);
    if(bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0)
    {
            fprintf(stderr, "bind function failed.\n");
            close(listenfd);
            exit(RT_ERR);
    }
   
    if(listen(listenfd,LISTEN_QUEUE) < 0)
    {
            fprintf(stderr, "listen function failed.\n");
            close(listenfd);
       exit(RT_ERR);
    }
    fprintf(stdout, "The server IP is %s, listen on port: %d\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));
   
    fd_set readfdset, writefdset, currentset;
    FD_ZERO(&readfdset);
    FD_SET(listenfd, &readfdset);
    while(1)
    {
        currentset = readfdset;
        bzero(readbuf, sizeof(readbuf));
            if(!(select(FD_SETSIZE, ¤tset, NULL, NULL, NULL) > 0))
            {
            fprintf(stderr, "select function failed.\n");
            close(listenfd);
            exit(RT_ERR);
        }
     for(fd = 0; fd < FD_SETSIZE; fd++)
     {
         if(FD_ISSET(fd, currentset))
         {
             fprintf(stdout, "fd is %d, listenfd is %d\n", fd, listenfd);
             if(fd == listenfd)
             {
                 if((connsockfd = accept(listenfd, (struct sockaddr*)&clientaddr, &client_len)) < 0)
                 {
                     fprintf(stderr, "accept function failed.\n");
                     exit(RT_ERR);
                  }
                 FD_SET(connsockfd, &readfdset);
                      fprintf(stdout, "It is a new session from IP:%s port:%d\n",inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
             }
             else
             {
                 if(recv(fd, readbuf, BUFFER_SIZE, 0) > 0)
                 {
                     fprintf(stdout, "recv message: %s\n", readbuf);
                 }
                 else
                 {
                     close(fd);
                     FD_CLR(fd, &readfdset);
                     fprintf(stdout, "client socket %d close\n", fd);
                 }
             }
         }//end of if(FD_ISSET(fd, ¤tset))
     }//end of for
    }//end of while
    close(listenfd);
    return 0;
}

client.c:

#include <sys/types.h>  
#include <sys/socket.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <sys/time.h>  
#include <sys/ioctl.h>  
#include <unistd.h>  
#include <netinet/in.h> 
#include <arpa/inet.h>
 
#define SERVERIP "192.168.0.200"
#define PORT 8888
#define BUFFER_SIZE 512
 
int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in server;
    char sendbuf[BUFFER_SIZE];
    bzero(&sendbuf,sizeof(sendbuf));
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&server,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr.s_addr = inet_addr(SERVERIP);
    
 
    if(connect(sockfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) < 0)
    {
            perror("connect failed.\n");
            return -1;
    }
    while(1)
    { 
        fgets(sendbuf, sizeof(sendbuf), stdin);
        send(sockfd, sendbuf, sizeof(sendbuf), 0);
        bzero(&sendbuf,sizeof(sendbuf));
        }
    close(sockfd);
    return 0;
}
时间: 2024-10-14 01:37:17

嵌入式 Linux网络编程(四)——Select机制的相关文章

嵌入式 Linux网络编程(五)——epoll机制

嵌入式 Linux网络编程(五)--epoll机制 一.epoll简介 epoll是在2.6内核中提出的,是select和poll的增强版本.epoll更加灵活,没有描述符限制,使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中. 1.epoll函数 #include <sys/epoll.h> int epoll_create(int size); 创建一个epoll的句柄,size表示监听的文件描述的数量 int epoll_ctl(int epfd,

嵌入式 Linux网络编程(一)——Socket网络编程基础

嵌入式 Linux网络编程一--Socket网络编程基础 一.Socket简介 1.网络中进程间通信 本机进程使用进程号区别不同的进程进程间通信方式有管道.信号.消息队列.共享内存.信号量等.网络中进程间的通信首先需要识别进程所在主机在网络中的唯一标识即网络层的IP地址主机上的进程可以通过传输层的协议与端口号识别. 2.Socket原理 Socket是应用层与TCP/IP协议族通信的中间软件抽象层是一种编程接口.Socket屏蔽了不同网络协议的差异支持面向连接(Transmission Cont

嵌入式 Linux网络编程(二)——TCP编程模型

嵌入式 Linux网络编程(二)--TCP编程模型 一.TCP编程模型 TCP编程的一般模型如下图: TCP编程模型分为客户端和服务器端编程,两者编程流程如下: TCP服务器端编程流程: A.创建套接字: B.绑定套接字: C.设置套接字为监听模式,进入被动接受连接状态: D.接受请求,建立连接: E.读写数据: F.终止连接. TCP客户端编程流程: A.创建套接字: B.与远程服务器建立连接: C.读写数据: D.终止连接. 二.TCP迭代服务器编程模型 TCP循环服务器接受一个客户端的连接

嵌入式 Linux网络编程(三)——UDP编程模型

嵌入式 Linux网络编程(三)--UDP编程模型 UDP编程模型: UDP循环服务器模型为: socket(...); bind(...); while(1) {    recvfrom(...);    process(...);    sendto(...); } server.c代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #inc

Linux网络编程:select编程基本实现思路

一.打开网络通讯端口,选用TCP协议 listenfd = socket(AF_INET,SOCK_STREAM,0); 二.初始化servaddr,并且进行网络字节序转换 bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family         = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port           = htons(SERV_PO

linux网络编程:select()函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET(转)

从别人的博客中转载过来了这一篇文章,经过重新编辑排版之后展现于此,做一个知识点保存与学习. select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型: int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout); 所在的头文件为:#include <sys/time.h> 和#include <uni

嵌入式 Linux系统编程(四)——文件属性

嵌入式 Linux系统编程(四)--文件属性 一.文件属性概述 Linux 文件的属性主要包括:文件的节点.种类.权限模式.链接数量.所归属的用户和用户组.最近访问或修改的时间等内容.文件属性示例如下: 多个文件属性查看: ls -lih 1341714 -rw-r--r-- 1 root root 2.5K May 28 10:24 bit_marco.c 1341718 -rw-r--r-- 1 root root 2.1K May 28 09:08 bit_marco.c~ 1341706

linux网络编程学习笔记之五 -----并发机制与线程?

进程线程分配方式 简述下常见的进程和线程分配方式:(好吧,我仅仅是举几个样例作为笔记...并发的水太深了,不敢妄谈...) 1.进程线程预分配 简言之,当I/O开销大于计算开销且并发量较大时,为了节省每次都要创建和销毁进程和线程的开销.能够在请求到达前预先进行分配. 2.进程线程延迟分配 预分配节省了处理时的负担,但操作系统管理这些进程线程也会带来一定的开销.由此,有个折中的方法是,当某个处理须要花费较长时间的时候,我们创建一个并发的进程或线程来处理该请求.实现也非常easy,在主线程中定时,定

Linux网络编程 五种I/O 模式及select、epoll方法的理解

Linux网络编程 五种I/O 模式及select.epoll方法的理解 web优化必须了解的原理之I/o的五种模型和web的三种工作模式 五种I/O 模式--阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/OLinux网络编程 五种I/O 模式及select.epoll方法的理解