select系统调用

select函数简介
        select一般用在socket网络编程中,在网络编程的过程中,经常会遇到许多阻塞的函数,网络编程时使用的recv, recvfrom、connect函数都是阻塞的函数,当函数不能成功执行的时候,程序就会一直阻塞在这里,无法执行下面的代码。这是就需要用到非阻塞的编程方式,使用 selcet函数就可以实现非阻塞编程。
        selcet函数是一个轮循函数,即当循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续往下执行。
        select(),确定一个或多个套接口的状态,本函数用于确定一个或多个套接口的状态,对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。

        通常采用select实现多路复用,也就是说可以同时监听多个文件描述符;

select系统调用的用途 :
在一段指定时间内 , 监听用户感兴趣的文件描述符的可读 , 可写和异常等事件 .

select API
select 系统调用的原形如下 :

#include<sys/select.h>
int seletc(int nfds , fd_set* readfds , fd_set* writefds , fd_set* exxeptfds , struct timeval* timeout) ; 

nfds 参数指定被监听的文件描述符的总数 . 它通常被设置为select监听的所有文件描述符中的最大值加1 , 因为文件描述符是从 0 开始计数的 .
readfds 参数用来检查一组可读性的文件描述符 .
writefds 参数用来检查一组可写性的文件描述符 .
-exceptfds 参数用来检查文件文件描述符是否异常 .
timeout 参数是一个时间结构体,用来设置超时时间 ,对阻塞操作则为NULL .
struct timeval结构体是用来设置超时时间的 , 该结构体可以精确到秒和微秒 .

struct timeval
{
	time_t         tv_sec;     /* 秒 */
	suseconds_t    tv_usec;    /* 微秒 */
};

select函数的返回值 :

  • -1 : select失败
  • 正值 : 表示某些文件可读或可写 , 这个整数表示就绪描述符的数目
  • 0 : 等待超时 , 没有可读写或错误的文件

select相关函数 :

下面是一些跟slect一起使用的函数及结构的作用

void FD_ZERO(fd_set *set);
//清空一个文件描述符的集合 , 都置为 0 
void FD_SET(int fd, fd_set *set);
//将一个文件描述符添加到一个指定的文件描述符集合中
void FD_CLR(int fd, fd_set *set);
//将一个指定的文件描述符从集合中清除;
int  FD_ISSET(int fd, fd_set *set);
//检查集合中指定的文件描述符是否可以读写

 

select 使用范例及大致流程 :

        当声明了一个文件描述符集后 , 必须用 FD_ZERO 将所有位置为零 . 之后将我们所感兴趣的描述符所对应的位 置位

大致操作流程 :

fd_set fdset;	// 监听读事件的描述符
int fds[MAXFD];	// 收集描述符的数组
fds_add(fds, sockfd);	// 将监听套接字添加到数组
FD_ZERO(&fdset);//清空
FD_SET(fds[i], &fdset);

struct timeval tv = {5,0}; //设置超时时间
int n = select(maxfd+1, &fdset, NULL, NULL, &tv);	// n个就绪
if(FD_ISSET(fds[i], &fdset)) //有数据
{ ... }

select 机制的优势

为什么会出现select模型?
先看一下下面的这句代码:

int iResult = recv(s, buffer,1024);

这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永 远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。
再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。

        看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。

select模型的出现就是为了解决上述问题。
select模型的关键是使用一种有序的方式,对多个套接字进行统一管理与调度 。

å¨è¿éæå¥å¾çæè¿°

如上所示,用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

        从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

实例

select 端 :

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/select.h>

#define MAXFD 10

void fds_add(int fds[],int fd)
{
    int i = 0;
    for(; i < MAXFD; i++)
    {
        if(fds[i] == -1)
        {
            fds[i] = fd;
            break;
        }
    }
}
void fds_del(int fds[],int fd)
{
    int i = 0;
    for(; i < MAXFD; i++)
    {
        if(fds[i] == fd)
        {
            fds[i] = -1;
            break;
        }
    }
}
int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);//监听套接字
    assert(sockfd != -1);

    struct sockaddr_in saddr;
    memset(&saddr, 0, sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);//1024 root 4096 保留 5000 临时
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
    assert(res != -1);

    listen(sockfd, 5);//创建监听队列;以完成和未完成的数量
    
    fd_set fdset;	// 监听读事件的描述符
    int fds[MAXFD];	// 收集描述符的数组
	
    int i = 0;
    for(; i < MAXFD ;i++)
    {
        fds[i] = -1;    
    }
    
    fds_add(fds, sockfd);	// 将监听套接字添加到数组

    while(1)
    {
        FD_ZERO(&fdset);//清空

        int maxfd = -1;
        int i = 0;
        for(; i < MAXFD; i++)
        {
            if(fds[i] == -1)
            {
                continue;
            }

            FD_SET(fds[i], &fdset);

            if(fds[i] > maxfd)
            {
                maxfd = fds[i];
            }
        }

        struct timeval tv = {5,0}; //设置超时时间

        int n = select(maxfd+1, &fdset, NULL, NULL, &tv);	// n个就绪
        if(n == -1)
        {
            perror("select error");
        }
        else if(n == 0)
        {
            printf("time out\n");
        }
        else
        {
            for( i = 0; i < MAXFD; i++)
            {
                if(fds[i] == -1)
                {
                    continue;
                }
                
                if(FD_ISSET(fds[i], &fdset)) //有数据
                {
                    if(fds[i] == sockfd)
                    {
                        //accept
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);

                        int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
                        if(c < 0)
                        {
                            continue;
                        }

                        printf("accept c=%d\n",c);

                        fds_add(fds, c);
                    }
                    else
                    {
                        //recv
                        char buff[128] = {0};
                        int res = recv(fds[i], buff, 127, 0);
                        if(res <= 0)
                        {
                            close(fds[i]);
                            fds_del(fds, fds[i]);
                            printf("one client over\n");
                        }
                        else
                        {
                            printf("recv(%d)=%s\n", fds[i], buff);
                            send(fds[i], "ok", 2, 0);
                        }
                    }
                }
            }
        }
    }
}

客户端 :

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1 );

	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);

	while(1)
	{
		char buff[128] = {0};
		printf("input:\n");
		fgets(buff,128,stdin);
		if(strncmp(buff,"end",3) ==0 )
		{
			break;
		}
		send(sockfd,buff,strlen(buff),0);
		memset(buff,0,128);
		recv(sockfd,buff,127,0);
		printf("buff=%s\n",buff);
	}
	close(sockfd);
}

å¨è¿éæå¥å¾çæè¿°

深入理解 select 模型
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set; FD_ZERO(&set); 则set用位表示是0000,0000。

(2)若 fd=5 , 执行FD_SET(fd,&set);后 set 变为 0001,0000(第5位置为1)

(3)若再加入 fd=2,fd=1 , 则 set 变为 0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若 fd=1,fd=2 上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的 fd=5 被清空。

基于上面的讨论,可以轻松得出select模型的特点:

(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。

(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

(3)可见select模型必须在select前循环加fd,取maxfd,select返回后利用FD_ISSET判断是否有事件发生。

select 总结
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

1、单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat/proc/sys/fs/file-max察看。32位机默认是1024位 , 即32个元素。64位机默认是2048.

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。

3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
————————————————
版权声明:本文为CSDN博主「Nonpc123」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Nonpc123/article/details/89321438

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
package com.neusoft.dao.impl; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import com.neusoft.entity.Group; import com.neusoft.entity.Person; import com.neusoft.util.DbUtil; public class PersonTableDao { public static final String SQL1= "insert into Person_Table"+" (p_id,g_name,p_name,p_birthday,p_phone,p_Email,p_address)"+" values(?,?,?,?,?,?,?)"; public static final String SQL2= "update Person_Table set p_id=?,g_name=?,p_birthday=to_date(?,'dd-mm-yyyy'),p_phone=?,p_Email=?,p_address=? where p_name=?"; public static final String SQL3= "delete from Person_Table where p_name=?"; public static final String SQL4= "select * from Person_Table"; public static final String SQL5= "select * from Person_Table where p_name = ?"; public boolean addPerson(Person person){ Connection conn = null; PreparedStatement stmt = null; conn = DbUtil.getConn(); try { stmt = conn.prepareStatement(SQL1); //向Person_Table中插入数据 stmt.setInt(1,person.getP_id()); stmt.setString(2,person.getG_name()); stmt.setString(3,person.getP_name()); stmt.setDate(4,new java.sql.Date(person.getP_birthday().getTime())); stmt.setString(5,person.getP_phone()); stmt.setString(6,person.getP_Email()); stmt.setString(7,person.getP_address()); int rows = stmt.executeUpdate(); if(rows>0) return true; } catch (SQLException e) { // TODO: handle exception e.printStackTrace(); }finally{ DbUtil.getClose(conn, stmt); } return false; } public boolean updatePersonMessage(Person person){ Connection conn = null; PreparedStatement stmt = null; conn = DbUtil.getConn(); try { stmt = conn.prepareStatement(SQL2);//根据p_name更新person表中的数据。 stmt.setInt(1,person.getP_id()); stmt.setString(2,person.getG_name()); stmt.setDate(3,new java.sql.Date(person.getP_birthday().getTime())); stmt.setString(4,person.getP_phone());

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值