select实现多路IO转接设计思路

一、实现思路

1、服务器

socket();		//设置套接字
bind();			//绑定服务器
listen();		//设置监听上限
fd_set	reset, allset;  //创建“读”监听集合
FD_ZERO (&allset);		//	将“读”监听集合清空
FD_SET (lfd, &allset);	//将lfd添加至读集合种.
while (1){
	rest = allset;		//保存读监听集合
	ret = select (MAXFD+1, &rset, NULL, NULL, NULL); //监听文件描述符集合对应的事件,这里只监听“读”事件。
	
	if (ret > 0) {
		if (FD_ISSET (lfd, &rset)){
			cfd = accept();  //建立连接,返回cfd
			FD_SET (cfd, &allset); //将新的cfd描述符添加到读集合中
		}
	}
	
	for  (i = lfd + 1; i <= 最大文件描述符; i++){
		FD_ISSET (i, &rset);
		read ();
		//用户自定义功能
		write ();
	}
}
  • selcet 解析
    1)所需头文件:
    #include <sys/types.h>
    #include <sys/times.h>
    #include <sys/select.h>

    2)函数原型:
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set * exceptfds, struct timeval *timeout)

    nfds:select监视的文件句柄数,视进程中打开的文件数而定,一般设为你要监视各文件中的最大文件号加一。(注:nfds并非一定表示监视的文件句柄数。官方文档仅指出nfds is the highest-numbered file descriptor in any of the three sets, plus 1. (可在linux环境中通过man select命令查得))

    readfds:select监视的可读文件句柄集合。

    writefds: select监视的可写文件句柄集合。

    exceptfds:select监视的异常文件句柄集合。

    timeout:本次select()的超时结束时间。设置为NULL时,select阻塞监听。

    返回值:-1出错;等于0或大于0,返回监听集合中,满足对应事件的总数。
    注意:第二、三、四参数,

    3)优点
    跨平台:windows、linux、macos、unix、类unix、mips…

    4)缺点
    ①监听上限受文件描述符限制,最大1024.
    ②检测满足条件的fd,需要自己编码,编码难度不小。

2、客户端

1)socket() 创建socket。
2)connect() 与服务器建立连接。
3)write() 写数据到socket的写缓冲,将数据发送到服务器。
4)read() 读数据。
5)显示读取结果。
6)close() 关闭fd。

二、详细代码

1、服务器

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>
#include "warp.h"

#define PORT	666				//端口
#define MAX_LISTEN  128			//监听上限
int main (void)
{
	printf ("hello\n");
	int lfd, cfd;
	struct sockaddr_in ser_addr, clt_addr;
	socklen_t addr_len;
	char addr_buf[INET_ADDRSTRLEN];
	
	lfd = Socket (AF_INET, SOCK_STREAM, 0);		//创建套接字

	int opt = 1;
	setsockopt (lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));		//端口复用

	bzero (&ser_addr, sizeof (ser_addr));		//结构体清零
	ser_addr.sin_family = AF_INET;
	ser_addr.sin_addr.s_addr = htonl (INADDR_ANY);
	ser_addr.sin_port =	htons (PORT);
	Bind (lfd, (struct sockaddr*)&ser_addr, sizeof (ser_addr));		//绑定服务器
	printf ("host ip: %s\n", inet_ntop(AF_INET, &ser_addr.sin_addr, addr_buf, sizeof(addr_buf)));
	Listen (lfd, MAX_LISTEN);		//设置监听上限

	fd_set rset, allset;		//创建 读监听集合
	int sockfd;
	int client[FD_SETSIZE];		//用于记录连接成功的cfd
	int maxfd;
	int maxi;
	int nready;
	int i,n;
	char buf[256] = {0};
	int read_len;
	FD_ZERO (&rset);
	FD_ZERO (&allset);
	FD_SET (lfd, &allset);

	maxfd = lfd;
	maxi = -1;
	for (i=0; i<FD_SETSIZE; i++)	//初始化为-1,表示全为空
		client[i] = -1;


	while (1)
	{
		rset = allset; 
		nready = select (maxfd + 1, &rset, NULL, NULL, NULL);	//nready,接收满足对应读事件的总数.
		if (nready < 0)
			PERROR ("select");
		
		if (FD_ISSET (lfd, &rset))		//判断是否有客户端请求连接
		{
			addr_len = sizeof (clt_addr);
			cfd = Accept (lfd, (struct sockaddr*)&clt_addr, &addr_len);	//这里的accept不会阻塞,因为有连接请求事件被触发,它才会进入此分支。
			printf ("access network IP:%s  prot:%d\n", 
					inet_ntop(AF_INET, &clt_addr.sin_addr,
								addr_buf, sizeof(addr_buf)),
					ntohs(clt_addr.sin_port));
			
			for (i=0; i<FD_SETSIZE; i++)	//寻找client的空位置
				if (client[i] == -1) {
					client[i] = cfd;
					break;
				}
			
			if (i == FD_SETSIZE){			//如果满了(等于1024),提示错误,退出程序
				fputs ("too many clients\n", stderr);
				exit (1);
			}

			FD_SET (cfd, &allset);

			if (cfd > maxfd)
				maxfd = cfd;

			if (i > maxi)
				maxi = i;

			if (--nready == 0)	//如果满足事件数量为1,则返回select处继续等待事件;如果满足事件数量为大于1,则进行往下执行for循环,说明还有其它事件被触发。
				continue;
		}
		
		for (i=0; i <= maxi; i++)	
		{
			if ((sockfd = client[i]) < 0)	//client为-1的不处理
				continue;
			if (FD_ISSET (sockfd, &rset))	//实现大小写转换
			{
				read_len = Read (sockfd, buf, sizeof(buf));
				if (read_len == 0)				//说明客户端断开
				{
					Close (sockfd);
					FD_CLR (sockfd, &allset);		
					client[i] = -1;
				}
				else if (read_len > 0)		//读到客户端发来的数据
				{
					for (n=0; n < read_len; n++)
						buf[n] = toupper (buf[n]);
					printf ("%s",buf);
					memset (buf, '\0', sizeof (buf));
				}
				if (--nready == 0)
					break;
			}
		}
	}

	Close (lfd);
	return 0;

}

2、客户端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h>
#include "warp.h"


#define SEVR_PORT  666

int main (void)
{
	int cfd;
	struct sockaddr_in serv_addr;
	int count = 10;
	char buf[1024] = {0};

	cfd = Socket (AF_INET, SOCK_STREAM, 0);

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons (SEVR_PORT);
	inet_pton (AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
	Connect (cfd, (struct sockaddr *)&serv_addr, sizeof (serv_addr));

	while (count--)
	{
		int ret = 0 ;
		write (cfd, "i love u\n", 9);
		ret = read (cfd, buf, strlen (buf));
		//write (STDOUT_FILENO, buf, ret);
		printf ("%s", buf);
		sleep (1);
	}

	close (cfd);
	return 0;
}

三、测试

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值