网络编程(5)—— Reactor实现(v1)

文章目录

回顾

先回顾一下我们整个的实现框架,所以第一步应该是实现的是------封装系统调用,另外为了方便打印调试信息,可以把日志系统用单例模式先实现出来。

关于用单例模式实现日志系统我们在之前的文章中已经实现过了,这里就不过多赘述了。详情可以跳转到该文章中查看

Reactor网络模型本质上就是使用epoll的并发网络模型,通过连接器连接到服务器网络,然后注册三个事件(对应epoll的三个接口函数),通过事件驱动(事件循环和事件分发)来响应客户端的请求。

Reactor_v1实现

在写代码之前要整理一下思绪,框架图中已经很明确的写出了有哪些类,我们在第一个版本的内容中实际上就是对系统调用的封装,下面就画一个类图,整理一下具体实现的内容。

Acceptor类中可以将地址复用、端口复用、bind、listen这些写法固定的流程封装在一个ready函数中。关于其它类行该可以比较容易的理清思路。需要着重考虑的是IO操作,因为使用send/recv或write/read一次处理的字符数量是由上限的。所以我们应该设置一个合适大小的缓冲,用来接收数据流。

Socket类实现

头文件

cpp 复制代码
/**
 * 封装文件描述符
 */


#ifndef _SOCKET_H
#define _SOCKET_H
#include<sys/types.h>
#include<sys/socket.h>

class Socket {
public:

Socket();

/**
 * @param fd
 * 禁止隐式转换防止出现问题
 */
explicit Socket(int fd);

~Socket();

int getFd();
private:
    int _fd;
};

#endif //_SOCKET_H

源文件

cpp 复制代码
/**
 * 封装文件描述符
 */


#include "Socket.h"
#include <stdio.h>
#include <stdlib.h>
/**
 * Socket implementation
 */


Socket::Socket() {
    _fd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(_fd == -1){
        perror("socket error");
        exit(EXIT_FAILURE);
    }
}

/**
 * @param fd
 */
Socket::Socket(int fd):_fd(fd){

}

Socket::~Socket() {

}

/**
 * @return int
 */
int Socket::getFd() {
    return _fd;
}

InetAddr类的实现

头文件

cpp 复制代码
/**
 * 地址相关操作
 */


#ifndef _INETADDRESS_H
#define _INETADDRESS_H
#include<string>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<sys/socket.h>
#include<arpa/inet.h>
using std::string;

class InetAddress {
public:

/**
 * @param ip
 * @param port
 */
InetAddress(const string & ip, unsigned short port);

/**
 * @param addr
 */
InetAddress(const struct sockaddr_in & addr);

~InetAddress();

string getIp();

unsigned short getPort();

const struct sockaddr_in * getInetAddressPtr();
private:
    struct sockaddr_in _addr;
};

#endif //_INETADDRESS_H

源文件

cpp 复制代码
/**
 * 地址相关操作
 */


#include "InetAddress.h"
/**
 * InetAddress implementation
 */


/**
 * @param ip
 * @param port
 */
InetAddress::InetAddress(const string & ip, unsigned short port) {
    _addr.sin_family = AF_INET;
    _addr.sin_port = htons(port);
    _addr.sin_addr.s_addr = ::inet_addr(ip.c_str());
}

/**
 * @param addr
 */
InetAddress::InetAddress(const struct sockaddr_in & addr):_addr(addr) {

}

InetAddress::~InetAddress() {

}

/**
 * @return string
 */
string InetAddress::getIp() {
    string temp = ::inet_ntoa(_addr.sin_addr);
    return temp;
}

/**
 * @return unsigned short
 */
unsigned short InetAddress::getPort() {
    return ::ntohs(_addr.sin_port);
}

/**
 * @return const struct sockaddr_in *
 */
const struct sockaddr_in * InetAddress::getInetAddressPtr() {
    return &_addr;
}

Acceptor类的实现

头文件

cpp 复制代码
/**
 * 连接器的实现
 */


#ifndef _ACCEPTOR_H
#define _ACCEPTOR_H
#include"InetAddress.h"
#include"Socket.h"
using std::string;


class Acceptor {
public:

/**
 * @param ip
 * @param port
 */
Acceptor(const string & ip, unsigned short port);

~Acceptor();

void ready();

int accept();
private:
    Socket _sock;
    InetAddress _addr;

void setReuseAddr();

void setReusePort();

void bind();

void listen();
};

#endif //_ACCEPTOR_H

源文件

cpp 复制代码
/**
 * 连接器的实现
 */


#include "Acceptor.h"

/**
 * Acceptor implementation
*/


/**
 * @param ip
 * @param port
 */
Acceptor::Acceptor(const string & ip, unsigned short port):
_sock(),
_addr(ip, port){

}

Acceptor::~Acceptor() {

}

/**
 * @return void
 */
void Acceptor::ready() {
    setReuseAddr();
    setReusePort();
    bind();
    listen();
    return;
}

/**
 * @return int
 */
int Acceptor::accept() {
    int clientfd = ::accept(_sock.getFd(), NULL, NULL);
    if(clientfd == -1){
        perror("accept error");
        return -1;
    }
    return clientfd;
}

/**
 * @return void
 */
void Acceptor::setReuseAddr() {
    int opt = 1;
    if(::setsockopt(_sock.getFd(), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1){
        perror("reuse sockaddr error");
        exit(EXIT_FAILURE);
    }
    return;
}

/**
 * @return void
 */
void Acceptor::setReusePort() {
    int opt = 1;
    if(::setsockopt(_sock.getFd(), SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) == -1){
        perror("reuse port error");
        exit(EXIT_FAILURE);
    }
    return;
}

/**
 * @return void
 */
void Acceptor::bind() {
    if(::bind(_sock.getFd(), (struct sockaddr *)_addr.getInetAddressPtr(), sizeof(struct sockaddr)) == -1){
        perror("bind error");
        exit(EXIT_FAILURE);
    }

    return;
}

/**
 * @return void
 */
void Acceptor::listen() {
    if(::listen(_sock.getFd(), 100) == -1){
        perror("listen error");
        exit(EXIT_FAILURE);
    }

    return;
}

SocketIO类的实现

头文件

cpp 复制代码
/**
 * IO操作
 */


#ifndef _SOCKETIO_H
#define _SOCKETIO_H
#include<unistd.h>
#include<errno.h>
#include<stdlib.h>
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
class SocketIO {
public:

/**
 * @param fd
 */
explicit SocketIO(int fd);

~SocketIO();

/**
 * @param buf
 * @param len
 */
int readn(char * buf, int len);

/**
 * @param buf
 * @param len
 */
int readLine(char * buf, int len);

/**
 * @param buf
 * @param len
 */
int writen(const char * buf, int len);
private:
    int _fd;
};

#endif //_SOCKETIO_H

源文件

cpp 复制代码
/**
 * IO操作
 */


#include "SocketIO.h"

/**
 * SocketIO implementation
 */


/**
 * @param fd
 */
SocketIO::SocketIO(int fd):_fd(fd) {

}

SocketIO::~SocketIO() {
    if(_fd != -1){
        close(_fd);
        _fd = -1;
    }
}

/**
 * @param buf
 * @param len
 * @return int
 */
int SocketIO::readn(char * buf, int len) {

    int remain = len; // 剩余没读的字节
    int ret = 0;
    char *pstr = buf;

    while(remain > 0){
        ret = ::read(_fd, pstr, sizeof(remain));

        if(ret == -1 && errno == EINTR){
            ::perror("read error");
            return -1;
        }
        else if(ret == 0){
            break;
        }
        else{
            pstr += ret;
            remain -= ret;
        }
    }


    return len - remain;
}

/**
 * @param buf
 * @param len
 * @return int
 */
int SocketIO::readLine(char * buf, int len) {
    int remain = len - 1;
    int ret = 0, total = 0;
    char *pstr = buf;

    while(remain > 0){
        ret = ::recv(_fd, pstr, remain, MSG_PEEK);
        if(ret == -1 && errno == EINTR){
            ::perror("readLine error");
            return -1;
        }
        else if(ret == 0){
            break;
        }
        else{
            for(int i = 0; i < ret; i++){
                if(pstr[i] == '\n'){
                    int sz = i + 1;
                    readn(pstr, sz);
                    pstr += sz;
                    *pstr = '\0';

                    return sz + total;
                }
            }
            readn(pstr, ret);
            total += ret;
            pstr += ret;
            remain -= ret;
        }
    }
    *pstr = '\0';
    return total;
}

/**
 * @param buf
 * @param len
 * @return int
 */
int SocketIO::writen(const char * buf, int len) {
    int remain = len;
    const char *pstr = buf;
    int ret = 0;
    while(remain > 0){
        ret = write(_fd, pstr, remain);
        if(ret == -1 && errno == EINTR){
            ::perror("writen error");
            return -1;
        }
        else if(ret == 0){
            break;
        }
        else{
            pstr += ret;
            remain -= ret;
        }

    }
    return  len - remain;
}

TcpConnection类的实现

头文件

cpp 复制代码
/**
 * 连接管理
 */


#ifndef _TCPCONNECTION_H
#define _TCPCONNECTION_H

#include<string>
#include"SocketIO.h"

using std::string;

class TcpConnection {
public:

/**
 * @param fd
 */
explicit TcpConnection(int fd);

~TcpConnection();

string receive();

/**
 * @param msg
 */
void send(const string & msg);
private:
    SocketIO _sockIO;
};

#endif //_TCPCONNECTION_H

源文件

cpp 复制代码
/**
 * 连接管理
 */


#include "TcpConnection.h"

/**
 * TcpConnection implementation
 */


/**
 * @param fd
 */
TcpConnection::TcpConnection(int fd):_sockIO(fd) {

}

TcpConnection::~TcpConnection() {

}

/**
 * @return string
 */
string TcpConnection::receive() {
    char buffer[1024] = {0};
    _sockIO.readLine(buffer, sizeof(buffer));

    return string(buffer);
}

/**
 * @param msg
 * @return void
 */
void TcpConnection::send(const string & msg) {
    _sockIO.writen(msg.c_str(), msg.size());


    return;
}

以上就是Reactor的全部函数,完成了对系统调用的封装。下面再写一个测试文件

cpp 复制代码
#include "Acceptor.h"
#include "TcpConnection.h"
#include <iostream>
using std::cout;
using std::endl;

void test0()
{
    Acceptor acceptor("0.0.0.0",8080);
    acceptor.ready();

    TcpConnection con(acceptor.accept());

    while(1)
    {
        /* cout << ">>recv msg from client: " << con.receive() << endl; */
        /* con.send("hello,world\n"); */

        string msg = con.receive();
        cout << ">>recv msg from client: " << msg << endl;
        con.send("msg:" + msg);
    }
}

int main()
{
    test0();
    return 0;
}

下面再开一个终端自测一下

基本上还是符合我们预期的也就是说明,我们对于底层的封装没有了太大问题。我们的Reactor第一代基本完成了,当然可以把之前封装的log4cpp的代码替代perror()。

下面的工作就是完成核心层,事件驱动的代码,就是对epoll的封装,另外,TcpConnection是用来管理连接的,所以,当我们使用服务器采用事件驱动的形式时,TcpConnection中的数据成员和成员函数也要随之变动。

相关推荐
nice先生的狂想曲14 分钟前
javascript高级程序设计(六)——2026.9.5
前端·javascript
我爱写代码i22 分钟前
SKAPP SK影视反编译详细教程+源码 含苹果端ipa
开发语言·javascript·ecmascript
开开心心就好31 分钟前
免费刷题软件推荐,支持导入题库自动判题
网络·网络协议·tcp/ip·jupyter·智能手机·电脑·idea
এ慕ོ冬℘゜1 小时前
前端实战:基于jQuery递归实现通用树形组织菜单(可直接复用)
前端·javascript·jquery
变与不变8062 小时前
js函数与封装详细解答
前端·javascript·vue.js
码农小韩3 小时前
Linux应用开发(一)——Linux的标准IO
linux·运维·服务器
YUJIANYUE3 小时前
免费安卓手机版网络工具箱ping traceroute mtr等10+功能
网络
路弥行至3 小时前
Linux (ARM64 / Jetson) 挂载 exFAT 移动硬盘排障与离线安装指南
linux·运维·服务器·经验分享·笔记·jetson·exfat
那年窗外下的雪.3 小时前
AIDC 学习日志|第 21 天|EVPN 多归属故障演练与中断窗口定位
网络·git·学习
AZaLEan__3 小时前
事件循环讲解
javascript