多路转接IO——Select模拟实现

Select

select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;

select函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 
参数nfds是需要监视的最大的文件描述符值+1;
rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;
参数timeout为结构timeval,用来设置select()的等待时间

fd_set的接口

void FD_CLR(int fd, fd_set *set);   // 用来清除描述词组set中相关fd 的位
 int FD_ISSET(int fd, fd_set *set);  // 用来测试描述词组set中相关fd 的位是否为真
 void FD_SET(int fd, fd_set *set);   // 用来设置描述词组set中相关fd的位
 void FD_ZERO(fd_set *set);       // 用来清除描述词组set的全部位

Select模拟实现

select.hpp
#include <cstdio>
#include <vector>
#include <sys/select.h>
#include "tcpsocket.hpp"

#define MAX_TIMEOUT 3000
class Select{
    public:
        Select():_maxfd(-1){
            FD_ZERO(&_rfds);//初始化清空集合
        }//初始化操作
        bool Add(TcpSocket &sock) {
            int fd = sock.GetFd();
            FD_SET(fd, &_rfds);
            _maxfd = _maxfd > fd ? _maxfd : fd;//每次修改集合都要重新判断最大描述符
            return true;
        }//将sock中的描述符添加到保存集合中
        bool Del(TcpSocket &sock) {
            int fd = sock.GetFd();
            FD_CLR(fd, &_rfds);
            for (int i = _maxfd; i >= 0; i--) {
                if (FD_ISSET(i, &_rfds)) {//移除之后。从后往前判断第一个还在集合中的就是最大的
                    _maxfd = i;
                    break;
                }
            }
            return true;
        }//从保存集合中移除这个描述符,不再监控这个描述符
        bool Wait(std::vector<TcpSocket> *list, int outtime = MAX_TIMEOUT) {
            struct timeval tv;
            tv.tv_sec = outtime / 1000;//outtime以毫秒为单位
            tv.tv_usec = (outtime % 1000) * 1000; //计算剩余的微妙
            fd_set set;
            set = _rfds;//不能直接使用_rfds,因为一旦_rfds被修改就需要外部重新添加描述符了
            int ret = select(_maxfd+1, &set, NULL, NULL, &tv);
            if (ret < 0) {
                perror("select error");
                return false;
            }if (ret == 0) {
                printf("wait timeout\n");
                return true;
            }
            for (int i = 0; i < _maxfd+1; i++) {
                if (FD_ISSET(i, &set)) {
                    //还在集合中证明是就绪的,因为没有额外保存描述符,因此只能从0开始判断
                    TcpSocket sock;
                    sock.SetFd(i);
                    list->push_back(sock);
                }
            }
            return true;
        }//进行监控,并且直接向外提供就绪的TcpSocket
    private:
        //保存要监控可读事件的描述符---每次监控使用的集合都是这个集合的复制版(select会修改集合)
        fd_set _rfds; 
        //每次监控都需要输入的最大描述符
        int _maxfd;
};
tcpsocket.hpp
#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>

//listen的第二个参数决定同一时间能够接收多少客户端连接
并不决定整体通信能够接收多少客户端连接
#define MAX_LISTEN 5
#define CHECK_RET(q) if((q)==false){return -1;}

class TcpSocket {
	public:
		TcpSocket ():_sockfd(-1){}
		int GetFd() {
			return _sockfd;
		}
		void SetFd(int fd) {
			_sockfd = fd;
		}
		bool Socket() {
			//socket(地址域, 套接字类型, 协议类型)
			_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (_sockfd < 0) {
				perror("socket error");
				return false;
			}
			return true;
		}
		bool Bind(const std::string &ip, uint16_t port) {
			struct sockaddr_in addr;
			addr.sin_family = AF_INET;
			addr.sin_port = htons(port);
			addr.sin_addr.s_addr = inet_addr(ip.c_str());
			socklen_t len = sizeof(struct sockaddr_in);
			int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
			if (ret < 0) {
				perror("bind error");
				return false;
			}
			return true;
		}
		bool Listen(int backlog = MAX_LISTEN) {
			//listen(套接字描述符, 最大并发连接数)
			int ret = listen(_sockfd, backlog);
			if (ret < 0) {
				perror("listen error");
				return false;
			}
			return true;
		}
		bool Accept(TcpSocket *new_sock,  std::string *ip=NULL, uint16_t *port=NULL) {
			//新建套接字描述符 = accept(监听套接字描述符, 客户端地址信息,地址长度);
			struct sockaddr_in addr;
			socklen_t len = sizeof(addr);
			int new_fd = accept(_sockfd, (struct sockaddr*)&addr, &len);
			if (new_fd < 0) {
				perror("accept error");
				return false;
			}
			new_sock->_sockfd = new_fd;
			if (ip != NULL) {
				(*ip) = inet_ntoa(addr.sin_addr);
			}
			if (port != NULL) {
				*port = ntohs(addr.sin_port);
			}
			return true;
		}
		bool Recv(std::string *buf) {
			//recv(通信套接字描述符,缓冲区首地址,接收数据长度, 标志位-0阻塞)
			char tmp[4096] = {0};
			int ret = recv(_sockfd, tmp, 4096, 0);
			if (ret < 0) {
				perror("recv error");
				return false;
			}else if (ret == 0) {//recv默认阻塞,没有数据就会等待,返回0,表示连接断开
				printf("connection broken\n");
				return false;
			}
			buf->assign(tmp, ret);
			return true;
		}
		bool Send(const std::string &data) {
			//send(描述符,要发送数据首地址,要发送的数据长度,标志位-0阻塞)
			int ret = send(_sockfd, data.c_str(), data.size(), 0);
			if (ret < 0) {
				perror("send error");
				return false;
			}
			return true;
		}
		bool Close() {
			if (_sockfd > 0) {
				close(_sockfd);
				_sockfd = -1;
			}
			return true;
		}
		bool Connect(const std::string &ip, uint16_t port) {
			//向服务端发起连接
			//connect(描述符, 服务端地址信息, 地址信息长度)
			struct sockaddr_in addr;
			addr.sin_family = AF_INET;
			addr.sin_port = htons(port);
			addr.sin_addr.s_addr = inet_addr(ip.c_str());
			socklen_t len = sizeof(struct sockaddr_in);
			int ret = connect(_sockfd, (struct sockaddr *)&addr, len);
			if (ret < 0) {
				perror("connect error");
				return false;
			}
			return true;
		}
	private:
		int _sockfd;
};
测试 select.cpp
#include <iostream>
#include "select.hpp"

int main (int argc, char *argv[])
{
    if (argc != 3) {
        std::cout << "Usage: ./main ip port\n";
        return -1;
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    TcpSocket lst_sock;
    CHECK_RET(lst_sock.Socket());
    CHECK_RET(lst_sock.Bind(ip, port));
    CHECK_RET(lst_sock.Listen());

    Select s;
    s.Add(lst_sock);
    while(1) {
        std::vector<TcpSocket> list;
        bool ret = s.Wait(&list);
        if (ret == false) {
            continue;
        }
        for (auto sock : list) {
            if (sock.GetFd() == lst_sock.GetFd()){
                //就绪的描述符与监听套接字描述符一样,就表示需要获取新连接
                TcpSocket new_sock;
                ret = lst_sock.Accept(&new_sock);
                if (ret == false) {
                    continue;
                }
                s.Add(new_sock);//将新建套接字也添加监控
            }else {
                //就绪的描述符不是监听套接字,那么就是通信套接字,则进行recv
                std::string buf;
                ret = sock.Recv(&buf);
                if (ret == false) {
                    sock.Close();
                    s.Del(sock);//关闭套接字则需要移除监控
                    continue;
                }
                std::cout << "client say: " << buf << std::endl;
                std::cout << "server say: ";
                buf.clear();
                std::cin >> buf;
                ret = sock.Send(buf);
                if (ret == false) {
                    sock.Close();
                    s.Del(sock);//关闭套接字则需要移除监控
                }
            }
        }
    }
    lst_sock.Close();

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值