linux服务器编程之select

select函数

(select使用及原理详细说明 http://blog.csdn.net/rankun1/article/details/69815522

该函数可同时等待多个描述符状态改变,在有描述符状态改变(一个或是多个)或指定的超时时间到了之后才唤醒它。我们调用select告知内核对哪些描述符(就读、写或异常条件)感兴趣以及等待多长时间。我们感兴趣的描述字不局限于套接口,任何描述符都可以使用select来测试。

select函数原型:

#include<sys/select.h>
#include<sys/time.h>
int select (int maxfd , fd_set *readset ,fd_set *writeset, fd_set *exceptionset , const struct timeval * timeout);
返回:就绪描述字的正数目,0——超时,-1——出错

select函数的参数介绍:

maxfd表示所有监视集合(读,写,异常)中描述符的最大范围,其值应该为所有监视集合中最大的描述符+1

readset,writeset,exceptionset指定我们要让内核测试读、写、异常条件的描述符号集合,

timeout 指定等待超时时间。

timeval结构:

struct timeval {
long tv_sec; //seconds
long tv_usec ; //microseconds
}

timeval参数有三种可能值:1、NULL:代表永远等待下去,相当于完全阻塞。2、一个固定的值,代表等待一段固定的时间。3、timeval的属性值为0,表示根本不等待,检查描述字之后立即返回,也就是说事非阻塞的。

fd_set结构:

fd_set结构表示一个描述字集。它典型的应该以一个整数数组来表示,其中每个整数中的每一位对应一个描述字。关于fd_set有以下四个宏:

void FD_ZERO(fd_set *fdset); /* clear all bits in fdset */
void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */
void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */
int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */

select函数修改由指针readset,writeset,exceptionset所指向的描述字集,因而这三个参数都是值-结果参数。也就是说,在select函数执行过程中,会修改其中的值。调用该函数时,我们指定关心的描述字的值,该函数返回时,结果指示哪些描述字已就绪。该函数返回后,我们使用FD_ISSET来测试fd_set数据类型中的描述字。描述字集中任何与未就绪的描述字对应的位返回时均清为0.为此,每次重新调用select函数中,我们都得再次把所有描述字集合中的所关心的位置为1。这也是在稍候的通信例子里,我们设置resset和allset两个集合的原因所在。

select函数返回某个套接口就绪的条件:

select例子:一个简单的TCP回射服务器程

本程序只是为了简单介绍select的基本用法,其中对client数组的遍历效率太低,请无视

//#include <cstdio>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUF_LEN 1024
#define SERV_PORT 6000
#define FD_SIZE 100
#define MAX_BACK 100

int main(int argc, char ** argv)
{
	int listenfd;//监听socket
	int client[FD_SIZE];//保存select监听的套接字
	fd_set allset;//select监听的集合
	struct sockaddr_in servaddr, chiaddr;//服务器地址和客户端地址
	//socket创建监听socket
	if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		printf("Create socket Error : %d\n", errno);
		exit(EXIT_FAILURE);
	}

	//初始化服务器地址信息
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	//bind绑定监听socket与服务器addr
	if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
	{
		printf("Bind Error : %d\n", errno);
		exit(EXIT_FAILURE);
	}

	//监听
	if (listen(listenfd, MAX_BACK) == -1)
	{
		printf("Listen Error : %d\n", errno);
		exit(EXIT_FAILURE);
	}

	//初始化套接字数组
	for (int i = 0; i < FD_SIZE; i++)    //首先置为全-1
	{
		client[i] = -1;
	}

	while (1)
	{
		int nready = 0;
		int nMaxFd = listenfd;
		//select每次返回都会将集合中无信号的套接字清空,所以要重新加入
		//初始化select监听集合
		FD_ZERO(&allset);
		//服务器套接字加入select监听集合
		FD_SET(listenfd, &allset);
		//循环加入客户端套接字
		for (int i = 0; i < FD_SIZE; i++)
		{
			if (-1 != client[i])
			{
				if (nMaxFd < client[i])
				{
					nMaxFd = client[i];
				}
				FD_SET(client[i], &allset);
			}
		}
		//开始select,select第一个参数要求为所有监听集合中最大fd+1
		if ((nready = select(nMaxFd + 1, &allset, NULL, NULL, NULL)) == -1)
		{
			printf("Select Erorr : %d\n", errno);
			exit(EXIT_FAILURE);
		}

		if (nready <= 0)            //如果所有套接字都没有通知,继续select
		{
			continue;
		}

		//如果是服务端套接字有通知
		if (FD_ISSET(listenfd, &allset))
		{
			int connfd;//保存连接上来的客户端
			socklen_t clilen = sizeof(chiaddr);

			printf("Start doing... \n");

			if ((connfd = accept(listenfd, (struct sockaddr *)&chiaddr, &clilen)) == -1)
			{                                        //!> accept 返回的还是套接字
				printf("Accept Error : %d\n", errno);
				continue;
			}
			//保存客户端套接字
			bool bSave = false;//是否保存
			for (int i = 0; i < FD_SIZE; i++)
			{
				if (-1 == client[i])
				{
					client[i] = connfd;
					bSave = true;
					break;
				}
			}

			if (!bSave)//客户端太多了
			{
				printf("To many ... ");
				close(connfd);
				continue;
			}
		}

		//循环检查哪个客户端套接字有通知
		for (int i = 0; i < FD_SIZE; i++)
		{
			int sockClient = client[i];
			if (0 > sockClient)
			{
				continue;
			}
			char buf[BUF_LEN] = { 0 };

			if (FD_ISSET(sockClient, &allset))
			{
				int nRead = 0;
				nRead = read(sockClient, buf, BUF_LEN);
				if (nRead < 0)
				{
					printf("Error!\n");
					close(sockClient);            //说明在这个请求端口上出错了!
					client[i] = -1;
					continue;
				}
				if (nRead == 0)
				{
					printf("Error!\n");
					close(sockClient);            //客户端退出
					client[i] = -1;
					continue;
				}

				printf("Server Recv: %s\n", buf);

				if (strcmp(buf, "q") == 0)                //!> 客户端输入“q”退出标志
				{
					printf("Error!\n");
					close(sockClient);            //客户端退出
					client[i] = -1;
					continue;
				}

				printf("Server send : %s\n", buf);
				write(sockClient, buf, nRead);        //!> 读出来的写进去
			}
		}
	}

	return 0;
}

启动服务程序,新启一个终端,使用nc命令测试一下吧

nc 127.0.0.1 6000


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Barry__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值