Epoll-ET模式读写之用户连接的封装

Posted shiyicode

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Epoll-ET模式读写之用户连接的封装相关的知识,希望对你有一定的参考价值。

前言

服务端而言,对于每一个新的连接我们都需要去保存其基本信息,如ip地址,套接字fd,也需要赋予其唯一标识如用户名。
这里,我们来谈谈对用户连接的封装。

用户连接需要哪些数据

1. 套接字描述符 sockfd               
        执行读写操作时当然不可缺
2. 连接信息  sockaddr                 
        基本信息的保存
3. 用户缓存区 Buffer          
        非阻塞读写不可缺
4. 唯一标识                        
        服务端进行消息转发时不可能根据sockfd去进行查找,所以我们需要对每一个连接有一个唯一标识,用于对其进行检索  

关于Buffer细节可看Epoll-ET模式下非阻塞读写之Buffer的封装

用户连接需要哪些操作

1. 读
    epoll-Et模式下,读必须将缓冲区全部读完,否则不会再次触发
2. 写
    epoll-Et模式下,写必须将所写数据全部写入或是写到缓冲区满,否则不会再次触发
3. 断开
    断开时,如果是异步读写,需要需要确认写事件已全部完成,再关闭套接字
4. 初始化 
    保证连接可复用,否则每来一个连接,都要开辟空间,太过低效。

关于读写的细节

本身 send和recv都是线程安全的。
因为读操作,我们可以确定只会有一个线程来对读操作进行处理(通过设置EPOLLONESHOT),也就是说对读缓冲区的写入(接受数据)和取出(处理数据)都是在一个线程内,所以读缓冲区本质上是线程安全的。
但是写操作,写缓冲区可能会有多个线程对其进行写入和更改,所以我们用一个原子变量,来标志写缓冲区是否被操作,来保证数据读写不会冲突。

代码

本代码数据处理只是简单的进行转发到原本socket里

//
//  Connection.h
//  QuoridorServer
//
//  Created by shiyi on 2016/12/2.
//  Copyright © 2016年 shiyi. All rights reserved.
//

#ifndef Connection_H
#define Connection_H

#include <stdio.h>
#include <iostream>
#include <atomic>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include "Buffer.h"
#include "Util.h"
#include "BaseFunc.h"

const size_t BUFFER_SIZE = 65535;

class Connection

public:
    Connection() : m_writeing(true), m_epollfd(-1), m_sockfd(-1)
    

    ~Connection()

    //初始化连接
    void init(int epollfd, int sockfd, const sockaddr_in& client_addr)
    
        m_epollfd = epollfd;
        m_sockfd = sockfd;
        m_writeing = true;
        m_address = client_addr;

        //初始化读写缓冲区
        m_inBuff.init();
        m_outBuff.init();
    

    void HandleRead()
    
        while(true)
        
            char buf[BUFFER_SIZE];
            int ret = recv(m_sockfd, buf, BUFFER_SIZE, 0);
            if(ret < 0)
            
                //缓冲区内容已读完
                if((errno == EAGAIN) || (errno == EWOULDBLOCK))
                
                    break;
                
                //其他错误直接断开连接
                Util::outError("HandleRead");
                shutdown();
            
            //断开连接
            else if(ret == 0)
            
                shutdown();
            
            else
            
                //将读取的内容加入缓冲区
                m_inBuff.PutData(buf, ret);
            

            //printf("[%s]\\n", buf);
        
        worker();
    

    void HandleWrite()
    
        //更改临界值
        if(!m_writeing)
            return;
        m_writeing = false;

        //取出数据
        char buf[BUFFER_SIZE];

        int len = m_outBuff.GetData(buf, BUFFER_SIZE);

        int n = len;
        while (n > 0)
        
            int ret = send(m_sockfd, buf+len-n, n, 0);
            if (ret < n)
            
                if (ret == -1 && errno != EAGAIN)
                
                    Util::outError("write error");
                
                break;
            
            n -= ret;
        
        //n=0表示数据全部写完,删除写事件
        if(n == 0)
            modFd(m_epollfd, m_sockfd, EPOLLIN | EPOLLET | EPOLLONESHOT);

        //恢复临界值
        m_writeing = true;
    

    void worker()
    
        //解析
        //取出数据
        char buf[BUFFER_SIZE];

        int len = m_inBuff.GetData(buf, BUFFER_SIZE);

        while(!m_writeing)
            usleep(1000);
        m_writeing = false;

        m_outBuff.PutData(buf, len);
        modFd(m_epollfd, m_sockfd, EPOLLIN | EPOLLOUT | EPOLLET | EPOLLONESHOT);

        m_writeing = true;
    

    void shutdown()
    
        //等待写事件完成后关闭
        while(!m_writeing)
            usleep(1000);

        m_writeing = false;
        removeFd(m_epollfd, m_sockfd);
    

private:
    int m_epollfd;              //epoll描述符
    int m_sockfd;               //套接字描述符
    sockaddr_in m_address;      //套接字地址
    Buffer m_inBuff;            //读缓冲
    Buffer m_outBuff;           //写缓冲
    std::atomic_bool m_writeing;     //是否正在写
;


#endif /* Connection_H */

以上是关于Epoll-ET模式读写之用户连接的封装的主要内容,如果未能解决你的问题,请参考以下文章

socket

iOS蓝牙开发:蓝牙的连接和数据的读写

行为型设计模式之状态模式

行为型设计模式之状态模式

.NET Core MongoDB数据仓储和工作单元模式实操

web自动化针对PO模式进行二次封装之 basepage