【C++服务器入门基础------9.IPC进程间通信--socket-io复用技术】

大学生寒假在家过于无聊,整理一下以前学过的知识,顺便复习一下,水平较低,专业性差,仅供参考,不喜勿喷(反正也没人看)。解封了,但是有点小失恋,我好难过呜呜呜呜。。。

一、五个IO模型

(1)阻塞I/O模型

最流行的I/O模型是阻塞I/O模型,缺省时,所有的套接口都是阻塞的

(2)非阻塞I/O模型

应用程序连续不断地查询内核,看看某操作是否准备好,这对cpu时间是极大的浪费,一般只在专门提供某种功能的系统中才会用到

(3)I/O复用模型

有了I/O复用,我们就可以调用select或poll,在这两个系统调用的某一个上阻塞,而不是真正阻塞于真正的I/O系统调用

(4) 信号驱动I/O模型

 我们也可以用信号,让内核在描述字准备好时用信号SIGIO通知我们,我们将此方法称为信号驱动I/O

(5)异步I/O模型

 我们让内核启动操作,并在整个操作完成后通知我们

二、IO复用 

(1)概念

之前的案例中服务器在每面对一个客户端的时候,会为它开辟一个进程,这就是最传统的多进程并发模型,但是如果客户端数量足够大,CPU的负荷也很大,显然这种方式不够合理。所以人们提出了I/O多路复用这个模型,单个线程,通过记录跟踪每个I/O流的状态,来同时管理多个I/O流

(2)方式

目前的常用的IO复用模型有三种:select,poll,epoll。

select

poll

epoll

操作方式

遍历

遍历

回调

底层实现

数组

链表

红黑树

IO效率

每次调用都进行线性遍历,时间复杂度为O(n)

每次调用都进行线性遍历,时间复杂度为O(n)

事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1)

最大连接数

1024(x86)或2048(x64)

无上限

无上限

fd拷贝

每次调用select,都需要把fd集合从用户态拷贝到内核态

每次调用poll,都需要把fd集合从用户态拷贝到内核态

调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不拷贝

epoll是Linux目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。下面详细介绍epoll用法。

三、epoll

Linux中提供的epoll相关函数如下:

int epoll_create(int size);

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

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

(1)epoll_create函数

作用

创建一个 epoll 对象,返回该对象的描述符,注意要使用 close 关闭该描述符。

原型

int epoll_create(int size)

参数

size:创建的红黑树的监听节点数量。(仅供内核参考)

返回值

​  成功:指向新创建的红黑树的根结点的fd。

​​  失败:-1 errno

(2)epoll_ctl函数

作用

控制某个epoll监控的文件描述符上的事件:注册、修改、删除。

原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

参数

        epfd:epoll_creat的句柄(即epoll_create函数返回值)

​  op:对该监听红黑树所做的操作

        (1)EPOLL_CTL_ADD (添加fd到监听红黑树)

        (2)EPOLL_CTL_MOD (修改fd在监听红黑树上的监听事件)

​        (3)EPOLL_CTL_DEL (将一个fd从监听红黑树上删除)

​  fd:待监听的fd

​  event:本质是struct epoll_event结构体的地址、

struct epoll_event {
	__uint32_t events; /* Epoll events 【EPOLLIN,EPOLLOUT,EPOLLERR 等】*/
	epoll_data_t data; /* User data variable */
};
//联合体
typedef union epoll_data {
			void *ptr;//泛型指针(内核自动调回调函数)
			int fd;//对应监听事件
			uint32_t u32;
			uint64_t u64;
		} epoll_data_t;

返回值

        成功:0

        失败:-1 errno

(3)epoll_wait函数

原型

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

作用

等待所监控文件描述符上有事件的产生。

参数

  ​events:用来存内核得到事件的集合。输出满足监听条件的fd结构体。【数组】

  ​maxevents:【数组元素的总个数】。告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size。

  ​timeout:超时时间

    -1:阻塞

    0:立即返回,非阻塞

    >0:指定毫秒

返回值

​  大于0:有多少文件描述符就绪(满足监听的总个数,用作循环上限)

  等于0:没有fd满足监听事件

  等于-1:失败 ,errno

(4)代码实例

服务器

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/epoll.h>
#include <map>


using namespace std;


int main()//服务器
{

    map<int, struct sockaddr_in> accept_map;

    //网络信息结构体
    struct sockaddr_in s_addr, c_addr;
    int len = sizeof(struct sockaddr_in);
    socklen_t  client_len = sizeof(struct sockaddr_in);
    //socket 标识符
    int socket_fd;

    //accept 标识符
    int accept_fd;
    /*
    参数1 协议族  iPv4  iPv6
    参数2 TCP/UDP
    参数3 默认0
    */
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd < 0)
    {
        perror("socket error");
        return 0;
    }
    //需要使用协议族 AF_INET 表示 IPv4 地址 AF_INET6 表示 IPv6 地址
    s_addr.sin_family = AF_INET;
    //设置端口号 因为大小端的问题 字节顺序转换
    s_addr.sin_port = htons(10086);
    //IP 地址  INADDR_ANY泛指本机
    s_addr.sin_addr.s_addr = INADDR_ANY;


    if (bind(socket_fd, (struct sockaddr*)&s_addr, len) < 0)//绑定
    {
        perror("bind error");
        return 0;
    }
    //第二个参数 服务器可以同时接收几个客户端
    if (listen(socket_fd, 10) < 0)
    {
        perror("listen error");
        return 0;
    }



    int epollwaitfd;
    int epoll_fd;
    //epoll事件结构体
    struct epoll_event epollEvent;
    //epoll容器
    struct epoll_event epollEventArray[30];


    //初始化epoll事件结构体
    bzero(&epollEvent, sizeof(epollEvent));
    //绑定当前已经打通的网络通道标识
    epollEvent.data.fd = socket_fd;
    //捆绑事件为进入事件
    epollEvent.events = EPOLLIN;
    //创建epoll
    epoll_fd = epoll_create(30);
    //将打通的网络添加到epoll中
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &epollEvent);


    char buffer[50] = {'\0'};
    while (1)
    {
        //网络打通后 让epoll里面所有的socket都处于等待状态
        epollwaitfd = epoll_wait(epoll_fd, epollEventArray, 30, -1);
        if (epollwaitfd < 0)
        {
            perror("epoll_wait error");
        }
        for (int i = 0; i < epollwaitfd; i++)
        {
            //说明有客户端上线
            if (epollEventArray[i].data.fd == socket_fd)
            {
                accept_fd = accept(socket_fd, (struct sockaddr*)&c_addr, &client_len);
                printf("\n%s:%d 登录服务器!\n\n", inet_ntoa(c_addr.sin_addr), ntohs(c_addr.sin_port));
                epollEvent.data.fd = accept_fd;
                epollEvent.events = EPOLLIN;
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &epollEvent);

                accept_map[accept_fd] = c_addr;
            }
            else if (epollEventArray[i].events & EPOLLIN)//说明有数据发过来
            {
                bzero(buffer, sizeof(buffer));
                int ret = read(epollEventArray[i].data.fd, buffer, sizeof(buffer));
                if (ret <= 0)
                {
                    
                    cout << "客户端" << inet_ntoa(accept_map[epollEventArray[i].data.fd].sin_addr) << ":" << ntohs(accept_map[epollEventArray[i].data.fd].sin_port) << "下线"  << endl;
                    //关闭socket
                    close(epollEventArray[i].data.fd);
                    //如果客户端掉线 就从epoll容器里面删除
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epollEventArray[i].data.fd, NULL);

                    //从map容器中删除
                    accept_map.erase(epollEventArray[i].data.fd);
                }
                else//ret>0
                {
                    buffer[ret] = '\0';
                    cout << "客户端" << inet_ntoa(accept_map[epollEventArray[i].data.fd].sin_addr)<<":"<< ntohs(accept_map[epollEventArray[i].data.fd].sin_port )<< "说:" << buffer << endl;
                }
            }
            else
            {
                continue;
            }
        }

    }

   

    close(socket_fd);
    return 0;
}

客户端

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


using namespace std;

int main()
{
    //网络信息结构体
    struct sockaddr_in s_addr;
    int len = sizeof(struct sockaddr_in);
    //socket 标识符
    int client_fd;


    /*
    参数1 协议族  iPv4  iPv6
    参数2 TCP/UDP
    参数3 默认0
    */
    client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd < 0)
    {
        perror("socket error");
        return 0;
    }
    //需要使用协议族
    s_addr.sin_family = AF_INET;
    //设置端口号 因为大小端的问题 字节顺序转换
    s_addr.sin_port = htons(10086);
    //IP 地址
    s_addr.sin_addr.s_addr = inet_addr("192.168.52.130");

    if (connect(client_fd, (struct sockaddr*)&s_addr, len) < 0)
    {
        perror("connect error");
        return 0;
    }
    cout << "连接服务器成功,可以开始发送消息" << endl;
    

    while (1)
    {
        char msg[50] = { '\0' };
        scanf("%s", msg);
        //发送至服务器
        if (write(client_fd, msg, sizeof(msg)) == -1)
        {
            perror("发送失败");
        }


       

    }

    close(client_fd);


    return 0;
}

执行结果

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值