Linux - IO

四种典型IO方式

IO:输入输出–过程:等待IO就绪,进行数据拷贝

阻塞:为了完成某功能,发起一个调用,若完成功能条件不具备,则一直等待
非阻塞:发起一个调用,若完成功能条件不具备,则立即报错返回
阻塞与非阻塞:通常用于描述某个接口发起调用后是否能够立即返回
同步:一个功能完成后,才能进行下一个,若不能立即完成则一直等待
异步:发起一个调用,让别人完成具体功能,不用等待功能完成后才能继续推进
同步与异步:通常用于描述功能的完成流程。(外部体现就是功能是否是自己完成的)
异步阻塞与异步非阻塞:
异步阻塞:发起一个调用,让系统完成任务,进程一直等着系统完成任务。
异步非阻塞:发起一个调用,让系统完成任务,进程继续做自己的事情

阻塞IO

阻塞IO:发起IO调用,若IO未就绪(IO条件不具备)则一直等待
优点:流程最为简单
缺点:效率较为低下

非阻塞IO

非阻塞:发起IO调用,若IO未就绪,则立即报错返回
优点:效率相较于阻塞有所提高
缺点:需要循环进行操作,不够实时

信号驱动

信号驱动:自定义IO信号处理,等待IO就绪收到信号打断当前操作进行IO
优点:效率更高,实时性更强
缺点:操作流程更为复杂-需要定于信号处理

异步IO

异步IO:自定义IO信号处理,发起IO调用,调用后立即返回,让系统完成IO,完成后通过信号通知进程。
优点:对于资源利用率极高,效率极高。
缺点:流程最为复杂

多路转接模型

IO多路转接:IO多路复用
作用:针对大量描述符进行IO就绪事件监控,让进程仅仅针对已经就绪了IO事件的描述符进行IO操作,避免了进程对未就绪的描述符进行操作所带来的性能损失或者阻塞。
实现:select、poll、epoll
IO就绪事件:可读,可写,异常

都适用于有大量描述符需要监控,但是同一事件只有少量活跃的场景
poll/select适用于单个描述符的超时控制
单个描述符的临时超时控制在这里不适用epoll
在实际使用中,多路转接模型通常搭配线程池一起使用
对大量描述符进行监控,就绪事件后则抛入线程池进行处理

select模型

select模型:针对大量描述符进行IO就绪事件监控
操作流程:
1.定义指定IO事件的描述符集合,将需要监控指定时间的描述符添加到对应集合中
2.发起调用,将需要监控的事件描述符集合拷贝到内核,进行事件监控。若监控超时了都没有描述符就绪则返回,若有描述符就绪了指定监控的事件则返回。在监控调用返回前,都会将描述符集合中没有就绪事件的描述符移除。也就是说,调用返回后,集合中保留的只有就绪的描述符。
3.判断哪个描述符还在哪个集合中,就知道哪个描述符就绪了什么事件,进而进行对应IO操作。

接口

1.定义集合:fd_set rfds, wfds, efds;
2.清空集合:void FD_ZERO(fd_set *set);
3.将描述符添加到集合中:void FD_SET(int fd, fd_set *set);
4.发起监控调用

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

nfds:所有集合中最大的描述符的值+1
readfds / writefds / exceptfds:可读,可写,异常,不监控置空
timeout:监控超时等待时间 struct timeval{tv_usec, tv_sec} 一直等待则置NULL,非阻塞则数据置0
返回值:返回实际就绪的描述符个数;出错返回-1;超时返回0;

5.调用返回后,判断哪个描述符还在集合中确定哪个描述符就绪了什么事件

int FD_ISSET(int fd, fd_set *set);

6.从指定集合中移除指定的描述符

void FD_CLR(int fd, fd_set *set);

简单举例:

/*
 *针对标准输入进行可读事件监控
 *当标准输入有数据可读,再对其进行read操作
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>

int main()
{
        fd_set rfds;
        FD_ZERO(&rfds);
        while(1)
        {
                //因为select每次调用返回前都会修改监控集合以及事件
                //所以每次都要重新添加监控描述符以及设置超时时间
                FD_SET(0, &rfds);
                struct timeval tv;
                tv.tv_sec = 3;
                tv.tv_usec = 0;
                //select(最大描述符+1, 读集合, 写集合, 异常, 超时)
                int ret = select(1, &rfds, NULL, NULL, &tv);
                if(ret < 0)
                {
                        perror("select error");
                        return -1;
                }
                else if(ret == 0)
                {
                        printf("监控超时\n");
                        continue;
                }
                if(FD_ISSET(0, &rfds))
                {
                        printf("0-就绪\n");
                        char buf[1024] = {0};
                        int ret = read(0, buf, 1023);
                        if(ret <= 0)
                        {
                                printf("error\n");
                                continue;
                        }
                        printf("buf:[%s]\n", buf);
                }
        }
        return 0;
}

实例

select.hpp头文件,将select模型封装

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

class Select
{
public: 
        Select()
        : _max_fd(-1)
        {
                FD_ZERO(&_rfds);
        }
        //添加监控
        bool Add(TcpSocket &sock)
        {
                int fd = sock.GetFd();
                FD_SET(fd, &_rfds);
                _max_fd = _max_fd > fd ? _max_fd : fd;
                return true;
        }
        //移除监控
        bool Del(TcpSocket &sock)
        {
                int fd = sock.GetFd();
                //从集合中移除描述符
                FD_CLR(fd, &_rfds);
                //重新判断最大的描述符
                for(int i = _max_fd; i >= 0; i--)
                {
                        if(FD_ISSET(i, &_rfds))
                        {
                                _max_fd = i;
                                break;
                        }
                }
                return true;
        }
        //Wait接口对集合中所有描述符进行监控
        //通过参数arry将所有就绪的描述符返回给外界
        bool Wait(std::vector<TcpSocket> *arry)
        {
                struct timeval tv;
                tv.tv_sec = 3;
                tv.tv_usec = 0;
                fd_set tmp = _rfds;
                //select(max+1,读,写,异常,超时)
                int ret = select(_max_fd + 1, &tmp, NULL, NULL, &tv);
                if(ret < 0)
                {
                        perror("select error");
                        return false;
                }
                //超时,意味着没有完成就绪
                else if(ret == 0)
                {
                        arry->clear();
                        return true;
                }
                for(int i = 0; i <= _max_fd; i++)
                {
                        if(FD_ISSET(i, &tmp))
                        {
                                TcpSocket sock;
                                sock.SetFd(i);
                                arry->push_back(sock);
                        }
                }
                return true;
        }
private:
        fd_set _rfds;//需要监控的描述符集合-备份
        int _max_fd;//保存当前集合中最大的描述符
};

tcp套接字头文件

#pragma once
#include<cstdio>
#include<iostream>
#include<string>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>

#define CHECK_RET(q) if((q)==false){return -1;}
#define LISTEN_BACKLOG 5
class TcpSocket
{
private:
        int _sockfd;
public: 
        TcpSocket()
        : _sockfd(-1)
        {}
        void SetFd(int fd)
        {
                _sockfd = fd;
        }
        int GetFd()
        {
                return _sockfd;
        }
        bool 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, const uint16_t port)
        {
                sockaddr_in addr;
                addr.sin_family = AF_INET;
                addr.sin_port = htons(port);
                addr.sin_addr.s_addr = inet_addr(&ip[0]);
                socklen_t len = sizeof(sockaddr);
                int ret;
                ret = bind(_sockfd, (sockaddr*)&addr, len);
                if(ret < 0)
                {
                        perror("bind error");
                        return false;
                }
                return true;
        }
        bool Listen(int backlog = LISTEN_BACKLOG)
        {
                int ret = listen(_sockfd, backlog);
                if(ret < 0)
                {
                        perror("listen error");
                        return false;
                }
                return true;
        }
        bool Connect(const std::string &ip, const int port)
        {
                sockaddr_in addr;
                addr.sin_family = AF_INET;
                addr.sin_port = htons(port);
                addr.sin_addr.s_addr = inet_addr(&ip[0]);
                socklen_t len = sizeof(sockaddr);
                int ret;
                ret = connect(_sockfd, (sockaddr*)&addr, len);
                if(ret < 0)
                {
                        perror("connect error");
                        return false;
                }
                return true;
        }
        bool Accept(TcpSocket *sock, std::string *ip = NULL, uint16_t *port = NULL)
        {
                //int accept(监听套接字, 客户端地址, 长度)
                sockaddr_in addr;
                socklen_t len = sizeof(sockaddr_in);
                int newfd = accept(_sockfd, (sockaddr*)&addr, &len);
                if(newfd < 0)
                {
                        perror("accept error");
                        return false;
                }
                sock->_sockfd = newfd;
                if(ip != NULL)
                {
                        *ip = inet_ntoa(addr.sin_addr);
                }
                if(port != NULL)
                {
                        *port = ntohs(addr.sin_port);
                }
                return true;
        }
        bool Recv(std::string *buf)
        {
                //int recv(描述符,空间,数据长度,标志位)
                //返回值:实际获取大小,0-连接断开;-1-出错
                char tmp[4096] = {0};
                int ret = recv(_sockfd, tmp, 4096, 0);
                if(ret < 0)
                {
                        perror("recv error");
                        return false;
                }
                else if(ret == 0)
                {
                        printf("peer shutdown");
                        return false;
                }
                buf->assign(tmp, ret);
                return true;
        }
        bool Send(const std::string &data)
        {
                //int send(描述符,数据,长度,标志位)
                int total = 0;
                while(total < data.size())
                {
                        int ret = send(_sockfd, &data[0]+total, data.size()-total, 0);
                        if(ret < 0)
                        {
                                perror("send error");
                                return false;
                        }
                        total += ret;
                }
                return true;
        }
        bool Close()
        {
                if(_sockfd != -1)
                {
                        close(_sockfd);
                }
                return true;
        }
};

tcp_srv.cpp服务端代码

#include "tcpsocket.hpp"
#include "select.hpp"

int main(int argc, char *argv[])
{
        //通过程序运行参数执行服务端要绑定的地址
        //./tcp_srv 192.168.2.2 9000
        if(argc != 3)
        {
                printf("usage: ./tcp_src 10.106.200.199 9000\n");
                return -1;
        }
        std::string srvip = argv[1];
        uint16_t srvport = std::stoi(argv[2]);
        TcpSocket lst_sock;//监听套接字
        //1.创建套接字
        CHECK_RET(lst_sock.Socket());
        //2.绑定地址信息
        CHECK_RET(lst_sock.Bind(srvip, srvport));
        //3.开始监听
        CHECK_RET(lst_sock.Listen())
        Select s;
        s.Add(lst_sock);
        while(1)
        {
                std::vector<TcpSocket> arry;
                bool ret = s.Wait(&arry);//开始监控,arry返回就绪套接字
                if(ret == false)
                {
                        return false;
                }
                for(int i = 0; i < arry.size(); i++)
                {
                        if(arry[i].GetFd() == lst_sock.GetFd())
                        {
                                //4.获取新建连接
                                TcpSocket clisock;
                                std::string cliip;
                                uint16_t cliport;
                                bool ret = lst_sock.Accept(&clisock, &cliip, &cliport);
                                if(ret == false)
                                {
                                        continue;
                                }
                                std::cout << "get new conn:" << cliip << "-" << cliport << "\n";
                                s.Add(clisock);//将通信套接字添加监控
                        }
                        else//表示就绪套接字不是监听而是收发数据
                        {
                                //5.收发数据    --使用获取的新建套接字进行通信
                                std::string buf;
                                ret = arry[i].Recv(&buf);
                                if(ret == false)
                                {
                                        s.Del(arry[i]);
                                        arry[i].Close();
                                        continue;
                                }
                                std::cout << "client say:" << buf << std::endl;
                                buf.clear();
                                std::cout << "server say:";
                                std::cin >> buf;
                                ret = arry[i].Send(buf);
                                if(ret == false)
                                {
                                        s.Del(arry[i]);
                                        arry[i].Close();
                                }

                        }
                }
        }
        //6.关闭套接字
        lst_sock.Close();
        return 0;
}

tcp_cli.cpp客户端

#include "tcpsocket.hpp"


int main(int argc, char *argv[])
{
        //通过参数传入要连接的服务端的地址信息
        if(argc != 3)
        {
                printf("usage: ./tcp_lic srvip srvport\n");
                return -1;
        }
        std::string srvip = argv[1];
        uint16_t srvport = std::stoi(argv[2]);

        TcpSocket cli_sock;
        //1.创建套接字
        CHECK_RET(cli_sock.Socket());
        //2.绑定地址信息(不推荐)
        //3.向服务端发起连接
        CHECK_RET(cli_sock.Connect(srvip, srvport));
        while(1)
        {
                //4.收发数据
                std::string buf;
                std::cout << "client say:";
                std::cin >> buf;
                CHECK_RET(cli_sock.Send(buf));

                buf.clear();
                CHECK_RET(cli_sock.Recv(&buf));
                std::cout << "server say:" << buf << std::endl;
        }
        //5.关闭套接字
        CHECK_RET(cli_sock.Close());
        return 0;
}

总结

多路转接模型是针对一个或多个描述符进行IO就绪事件监控的功能
通常应用于tcp服务器端,针对大量套接字描述符进行监控,让程序能够仅仅针对就绪的描述符进行操作,进而提高处理效率
而udp服务端大多针对单个套接字进行操作,大多数情况也会用到多路转接模型,因为多路转接模型不但可以进行就绪事件监控,还可以进行超时控制。

select特性总结:
优点:跨平台移植性较好
缺点:
1.select所能监控的描述符有数量上限,上限取决于宏_FD_SETSIZE
2.select每次进行监控都要重新向集合中添加描述符(每次都会修改),并且每次都重新将集合拷贝到内核
3.select监控原理是在内核中进行轮训遍历,性能随着描述符的增多而下降:①将集合中描述符遍历一遍看看有没有就绪的;②有酒直接移除未就绪返回,没有则挂起等待;③有描述符就绪/超时后被唤醒,重新遍历一遍移除未就绪后返回
4.select返回的是就绪集合,需要用户判断那个描述符还在哪个集合中,才能确定哪个描述符就绪了哪个事件

poll

接口认识:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd
{
	int fd;//要监控的描述符
	short events;//fd描述符要监控的事件 POLLIN-读/POLLOUT-写
	short revent;//监控调用返回后记录实际就绪的事件
}

fds:描述符事件结构数组;
nfds:数组中有效节点个数;
to:超时时间
返回值:等于0表示超时;小于0表示出错;有就绪则大于0

操作流程:
1.定义事件结构体数组,为每个需要监控的描述符定义事件结构
2.发起监控调用,将数组中有效节点拷贝到内核进行监控,超时/就绪则调用返回,返回前将描述符实际就绪的事件记录到对应节点的revents成员中。
3.调用返回后,遍历时间数组,通过每个节点的revents成员确定对应节点描述符是否就绪了某个事件

实例

#include<poll.h>
#include<unistd.h>
#include<stdio.h>
#define MAX_COUNT 10
int main()
{
        struct pollfd poll_fd[MAX_COUNT];
        poll_fd[0].fd = 0;
        poll_fd[0].events = POLLIN;
        int nfds = 1;

        for(;;) 
        {  
                int ret = poll(poll_fd, nfds, 3000);
                if(ret < 0)
                { 
                        perror("poll");
                        usleep(1000);
                        continue;
                } 
                if(ret == 0)
                { 
                        printf("poll timeout\n");
                        continue;
                }
                for(int i = 0; i < nfds; i++)
                {
                        if(poll_fd[i].revents & POLLIN)
                        { 
                                printf("fd:%d 就绪了\n",poll_fd[i].fd);
                                char buf[1024] = {0};
                                read(poll_fd[i].fd, buf, sizeof(buf)-1);
                                printf("stdin:%s", buf);
                        }
                }
        }
        return 0;
}

总结

poll优缺点:
优点:
1.poll能够监控的描述符数量没有上线限制
2.代码操作流程相较于select较为简单
缺点:
1.跨平台移植性较差
2,监控原理依然是遍历轮询,性能会随着描述符增多而下降
3.监控返回后依然需要遍历事件结构数组确定描述符是否就绪

epoll

epoll:linux下最好用的多路转接模型
接口认识:
1.

//在内核中创建epoll句柄
int epoll_create(int size);

在内核中创建epoll句柄
size:监控的数量上限,linux2.6之后被忽略,大于0即可
返回值;成功返回描述符,失败返回-1

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);

struct epoll_event
{
	uint32_t events;//要监控的事件-监控返回后实际就绪事件
	epoll_data_t {void *ptr; int fd;}data;//自定义信息,通常用于保存事件节点对应要监控的描述符
}

epfd:epoll——create返回的epoll描述符;
op:EPOLL_CTRL_ADD / EPOLL_CTRL_EDL / EPOLL_CTRL_MOD
fd:针对op类型所操作的监控的描述符
ev:针对fd描述符所定义的事件结构体
返回值:成功返回0;失败返回-1

int epoll_wait(int epfd, struct epoll_event *evs, int maxe, int to);

epfd:epoll描述符;
evs:struct epoll_event数组首地址,用于保存就绪的描述符对应时间结构
maxe:通常设定为evs数组的节点个数–指定要获取的事件最大个数
to:超时事件-毫秒
返回值:超时返回0;出错返回-1;有就绪返回就绪事件个数

操作流程:

1.在内核中创建epoll句柄结构
struct eventpoll{…rdllist–双向链表, rbr-红黑树…}
rbr:用于保存要监控的描述符节点
rdllist:用于保存就绪的描述符对应事件结构
2.向内核epoll句柄结构中添加要监控的描述符以及对应事件结构
3.开始监控,传入一个事件结构数组,开始监控,监控是一个异步阻塞操作①告诉系统开始监控,而描述符的监控由系统完成;②系统为每个描述符的就绪事件做了一个事件回调函数,一旦某个描述符就绪了指定的事件,则会调用事件回调函数,将这个描述符对应的事件结构添加到就绪事件双向链表;③epoll_wait接口每隔一段时间查看epoll句柄结构的rdllist-就绪双向链表是否为空,就可以判断有没有描述符就绪,超时则直接返回,如果有就绪,则将就绪的事件结构信息拷贝到传入的数组中。
4.监控调用返回后,只需要遍历evs数组,逐个对节点中的描述符进行对应事件的处理即可。

epoll的事件触发方式:

IO事件的就绪:
可写:描述符的发送缓冲区中剩余空间大小大于低水位标记(类似于一个基准值–默认1个字节)
可读:描述符的接收缓冲区中数据大小大于低水位标记
IO就绪事件的触发方式:水平出发-默认、边缘触发-epoll特有
水平触发:EPOLLLT-默认
可读:只要缓冲区中有数据就会触发可读事件
可写:只要缓冲区中有剩余空间就会触发可写事件
边缘触发:EPOLLET
可读:只有新数据到来的时候才会触发可读事件
可写:只有缓冲区从没有剩余空间变为有剩余才会触发可写事件
边缘触发可以提高任务处理效率:
假设有个描述符有新数据来了,但是数据不完整,这种情况下使用水平触发,如果不取出数据就会一直触发事件,而使用边缘触发则可以实现在有新数据到来的时候触发事件,查看数据是否完整。(防止一种事件不断被触发,但是不太想去处理的场景)
边缘触发会导致问题:
边缘触发只有在新数据到来的时候才会触发事件,意味着在一次事件触发中就必须将需要处理的数据完全取出处理,因为在没有新数据到来的情况下不会再次触发事件去处理剩余的数据。
如何将缓冲区中所有数据全部取出?
因为不知道缓冲区中有多少数据,因此只能循环进行读取,直到取不出数据为止。但是这样会出现一种情况–没有数据则继续读就会阻塞。
因此边缘触发的IO必须使用非阻塞操作,循环读取到缓冲区中没有数据的时候就会报错返回。errno=EAGAIN
int fcntl(int fd, int cmd, …/arg/)
cmd:F_GETFL获取描述符属性通过返回值返回,arg被忽略
F_SETFL设置描述符属性 arg=O_NONBLOCK
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK);

实例

/*
 *封装Eoll类,向外提供简单的接口即可完成对大量描述符的监控
 *返回就绪的套接字
 */

#include<iostream>
#include<vector>
#include<cstdlib>
#include<sys/epoll.h>
#include"tcpsocket.hpp"

class Epoll
{
        public:
                Epoll() 
                :_epfd(-1)
                {
                        _epfd = epoll_create(1);
                        if(_epfd < 0)
                        {
                                perror("epoll_create error");
                                exit(-1);
                        }
                }
                bool Add(tcpSocket &sock)
                {
                        //epoll_ctl(epoll句柄,类型,描述符,事件)
                        //EPOLL_CTL_ADD  struct epoll_event
                        int fd = sock.GetFd();
                        struct epoll_event ev;
                        ev.data.fd = fd;
                        ev.events = EPOLLIN;
                        int ret = epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
                        if(ret < 0)
                        {
                                perror("epoll ctl add error");
                                return false;
                        }
                        return true;
                }
                bool Del(tcpSocket &sock)
                {
                        int fd = sock.GetFd();
                        int ret = epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, NULL);
                        if(ret < 0)
                        {
                                perror("epoll ctl del error");
                                return false;
                        }
                        return true;
                }
                bool Wait(std::vector<tcpSocket> *arry)
                {
                        //epoll_wait(句柄,数组,节点数量,超时)
                        struct epoll_event evs[10];
                        int ret;
                        ret = epoll_wait(_epfd, evs, 10, 3000);
                        if(ret < 0)
                        {
                                perror("epoll_wait");
                                return false;
                        }
                        else if(ret == 0)
                        {
                                arry->clear();
                                std::cout << "timeout\n";
                                return true;
                        }
                        for(int i = 0; i < ret; i++)
                        {
                                if(evs[i].events & EPOLLIN)
                                {
                                        TcpSocket sock;
                                        sock.SetFd(evs[i].data.fd);
                                        arry->push_back(sock);
                                }
                        }
                        return true;
                }

        private:
                int _epfd;
};

tcp套接字头文件

#pragma once
#include<cstdio>
#include<iostream>
#include<string>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<fcntl.h>
#include<errno.h>

#define CHECK_RET(q) if((q)==false){return -1;}
#define LISTEN_BACKLOG 5
class TcpSocket
{
private:
        int _sockfd;
public: 
        TcpSocket()
        : _sockfd(-1)
        {}
        void SetFd(int fd)
        {
                _sockfd = fd;
        }
        int GetFd()
        {
                return _sockfd;
        }
        bool Socket()
        {
                _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
                if(_sockfd < 0)
                {
                        perror("socket error");
                        return false;
                }
                return true;
        }
        bool SetNonBlock()
        {
                int flag = fcntl(_sockfd, F_GETFL, 0);
                fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);
        }

        bool Bind(const std::string &ip, const uint16_t port)
        {
                sockaddr_in addr;
                addr.sin_family = AF_INET;
                addr.sin_port = htons(port);
                addr.sin_addr.s_addr = inet_addr(&ip[0]);
                socklen_t len = sizeof(sockaddr);
                int ret;
                ret = bind(_sockfd, (sockaddr*)&addr, len);
                if(ret < 0)
                {
                        perror("bind error");
                        return false;
                }
                return true;
        }
        bool Listen(int backlog = LISTEN_BACKLOG)
        {
                int ret = listen(_sockfd, backlog);
                if(ret < 0)
                {
                        perror("listen error");
                        return false;
                }
                return true;
        }
        bool Connect(const std::string &ip, const int port)
        {
                sockaddr_in addr;
                addr.sin_family = AF_INET;
                addr.sin_port = htons(port);
                addr.sin_addr.s_addr = inet_addr(&ip[0]);
                socklen_t len = sizeof(sockaddr);
                int ret;
                ret = connect(_sockfd, (sockaddr*)&addr, len);
                if(ret < 0)
                {
                        perror("connect error");
                        return false;
                }
                return true;
        }
        bool Accept(TcpSocket *sock, std::string *ip = NULL, uint16_t *port = NULL)
        {
                //int accept(监听套接字, 客户端地址, 长度)
                sockaddr_in addr;
                socklen_t len = sizeof(sockaddr_in);
                int newfd = accept(_sockfd, (sockaddr*)&addr, &len);
                if(newfd < 0)
                {
                        perror("accept error");
                        return false;
                }
                sock->_sockfd = newfd;
                if(ip != NULL)
                {
                        *ip = inet_ntoa(addr.sin_addr);
                }
                if(port != NULL)
                {
                        *port = ntohs(addr.sin_port);
                }
                return true;
        }
        bool Recv(std::string *buf)
        {
                //int recv(描述符,空间,数据长度,标志位)
                //返回值:实际获取大小,0-连接断开;-1-出错
                char tmp[4096] = {0};
                int len = 4096;
                int total = 0;
                while(total < len)
                {
                        int ret = recv(_sockfd, tmp+total, 5, 0);
                        if(ret < 0)
                        { 
                                if(errno == EAGAIN)
                                {
                                        printf("所有数据都读完了\n");
                                        break;
                                }
                                perror("recv error");
                                return false;
                        }
                        else if(ret == 0)
                        {
                                printf("peer shutdown");
                                return false;
                        }
                        total += ret;
                }
                buf->assign(tmp, total);
                return true;
        }

        bool Send(const std::string &data)
        {
                //int send(描述符,数据,长度,标志位)
                int total = 0;
                while(total < data.size())
                {
                        int ret = send(_sockfd, &data[0]+total, data.size()-total, 0);
                        if(ret < 0)
                        {
                                perror("send error");
                                return false;
                        }
                        total += ret;
                }
                return true;
        }
        bool Close()
        {
                if(_sockfd != -1)
                {
                        close(_sockfd);
                }
                return true;
        }
};

tcp_srv.cpp服务端代码

#include "tcpsocket.hpp"
#include "select.hpp"

int main(int argc, char *argv[])
{
        //通过程序运行参数执行服务端要绑定的地址
        //./tcp_srv 192.168.2.2 9000
        if(argc != 3)
        {
                printf("usage: ./tcp_src 10.106.200.199 9000\n");
                return -1;
        }
        std::string srvip = argv[1];
        uint16_t srvport = std::stoi(argv[2]);
        TcpSocket lst_sock;//监听套接字
        //1.创建套接字
        CHECK_RET(lst_sock.Socket());
        lst_sock.SetNonBlock();
        //2.绑定地址信息
        CHECK_RET(lst_sock.Bind(srvip, srvport));
        //3.开始监听
        CHECK_RET(lst_sock.Listen())
        Epoll s;
        s.Add(lst_sock);
        while(1)
        {
                std::vector<TcpSocket> arry;
                bool ret = s.Wait(&arry);//开始监控,arry返回就绪套接字
                if(ret == false)
                {
                        return false;
                }
                for(int i = 0; i < arry.size(); i++)
                {
                        if(arry[i].GetFd() == lst_sock.GetFd())
                        {
                                //4.获取新建连接
                                TcpSocket clisock;
                                std::string cliip;
                                uint16_t cliport;
                                bool ret = lst_sock.Accept(&clisock, &cliip, &cliport);
                                if(ret == false)
                                {
                                        continue;
                                }
                                std::cout << "get new conn:" << cliip << "-" << cliport << "\n";
                                clisock.SetNonBlock();
                                s.Add(clisock);//将通信套接字添加监控
                        }
                        else//表示就绪套接字不是监听而是收发数据
                        {
                                //5.收发数据    --使用获取的新建套接字进行通信
                                std::string buf;
                                ret = arry[i].Recv(&buf);
                                if(ret == false)
                                {
                                        s.Del(arry[i]);
                                        arry[i].Close();
                                        continue;
                                }
                                std::cout << "client say:" << buf << std::endl;
                                buf.clear();
                                std::cout << "server say:";
                                std::cin >> buf;
                                ret = arry[i].Send(buf);
                                if(ret == false)
                                {
                                        s.Del(arry[i]);
                                        arry[i].Close();
                                }

                        }
                }
        }
        //6.关闭套接字
        lst_sock.Close();
        return 0;
}

tcp_cli.cpp客户端

#include "tcpsocket.hpp"


int main(int argc, char *argv[])
{
        //通过参数传入要连接的服务端的地址信息
        if(argc != 3)
        {
                printf("usage: ./tcp_lic srvip srvport\n");
                return -1;
        }
        std::string srvip = argv[1];
        uint16_t srvport = std::stoi(argv[2]);

        TcpSocket cli_sock;
        //1.创建套接字
        CHECK_RET(cli_sock.Socket());
        //2.绑定地址信息(不推荐)
        //3.向服务端发起连接
        CHECK_RET(cli_sock.Connect(srvip, srvport));
        while(1)
        {
                //4.收发数据
                std::string buf;
                std::cout << "client say:";
                std::cin >> buf;
                CHECK_RET(cli_sock.Send(buf));

                buf.clear();
                CHECK_RET(cli_sock.Recv(&buf));
                std::cout << "server say:" << buf << std::endl;
        }
        //5.关闭套接字
        CHECK_RET(cli_sock.Close());
        return 0;
}

epoll优缺点:

优点:
1.所能监控的描述符没有数量上限
2.描述符以及事件结构只需要向内核拷贝一次
3.监控原理采用异步阻塞,监控由系统完成,进程只需要判断就绪链表是否为NULL即可,性能不会随着描述符增多而下降。
4.直接返回都是就绪的描述符对应事件结构,减少空遍历
缺点:跨平台移植性较差

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值