Socket-IO模型:初步模型

news2024/11/23 11:49:49

1、基础的一个C++版本的服务器代码

        依照C语言服务器的基础流程进行更改,整个的TCP链接流程的实现:接上篇

Socket编程-IO模型-CSDN博客

先上代码结构图,因为是示例

                        

1、首先,这个示例的基本流程就是使用Socket,创建sockfd,进行获取sockfd'等操作,供之后的流程使用

2、InetAddress : 这个文件中只做和地址相关的信息,比如初始化 IP,Port还有,获取IP信息等的操作方法(sockaddr_in  )。

3、Acceptor  :这个类文件里面只做和accept相关的操作,包括以下几个流程(基础功能,可以最终实现accept的操作)

        设定地址和断开可复用后,流程开始,注意哦,这几个都是这个类的类成员函数,基于基础的函数进行设置。

                绑定和监听。这在c语言流程中,监听之后就是accept了

        但是在这里,将accept单独的设定成这个类的成员函数,自己可以决定什么时候去调用accept功能

                        

        前面的内容就是根据基本的流程更改后,C++版本的实现,我们只需要创建对象,进行调用各函数就可以了,功能实现也是函数之间的相互联系和嵌套。

4、InitConnetion :  这个就是对整体的链接进行操作和控制,(而创建一个对象,就相当于是创建了一个链接,然后我们执行这个链接所包含的操作),每个链接都是不同的。

与之类似的,就是这些类凸显了细分的功能:每一个功能,我们都将它设定成了对应的类,当我们需要创建大量的链接时,基于这些类创建出我们需要的对象,不同对象执行不同的操作。这里最明显的就是InetConnection ,可以使用其创建不同的对象,那么不同的对象是不是意味着不同的链接。只不过本例。只能实现单链接,其他的后面写。下面上代码

5、其中ProcessIO ,是为了进行程序的IO操作,读写功能的实现。

//写这个的时候,更多的还是注意类功能的实现和整体实现目标结构的设定(比如这里,就是服务器,就按照服务器结构来就行),不要盯着某一个成员函数的功能去看。我们需要调用谁,实现什么功能,就在合适的时候将它调用出来就行。

因为C++类的特征明显,每个类都是要解决一部分相关问题和操作的(类似这个,类的不同对应着服务器流程中的不同阶段或者某一模块功能)

其他代码也一样。之前看过很多代码文件命名不清晰或者名字相近还没有说明,只有一堆代码,也没有注释,理解上有歧义的情况下,特容易导致方向上的错误,看起来就太累了。

//这个可能写的也不太好。。后面多改进,在核心主线的基础上,拓展功能

代码示例:

Socket:

#ifndef __SOCKET_H__
#define __SOCKET_H__

#include "NonCopyable.h"

class Socket
: NonCopyable
{
public:
    Socket();
    explicit Socket(int fd);
    ~Socket();
    int fd() const;
    void shutDownWrite();

private:
    int _fd;
};

#endif
#include "Socket.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>

Socket::Socket()
{
    _fd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(_fd < 0)
    {
        perror("socket");
        return;
    }
}

Socket::Socket(int fd)
: _fd(fd)
{

}

Socket::~Socket()
{
    close(_fd);
}

int Socket::fd() const
{
    return _fd;
}

void Socket::shutDownWrite()
{
    //关闭写端
    int ret = shutdown(_fd, SHUT_WR);
    if(ret)
    {
        perror("shutdown");
        return;
    }
}

                                

InetAddress

#ifndef __INETADDRESS_H__
#define __INETADDRESS_H__
#include <arpa/inet.h>
#include <string>
#include <iostream>
using std::string;

class InetAddress{
private:   
    struct sockaddr_in _serverAddr;
    string _ip;
    int _port;
public:
    InetAddress(const string &ip,unsigned short port);
    InetAddress(const struct sockaddr_in &addr);
    ~InetAddress();
    string getServerIP() const;
    unsigned short getServerPort() const;

    const sockaddr_in *getInetAddrPtr() const;   //  因为我们的类通常需要和其他类进行关联协作
                                                 //  在必要时候需要获取到类中的数据信息(如果是私有的,就需要设定一个公共权限的获取函数)


};

#endif 

InetAddress.cc

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

//初始化服务器的IP,端口,和协议
InetAddress::InetAddress(const string &ip, unsigned short port){
    ::bzero(&_serverAddr, sizeof(struct sockaddr_in));
    _serverAddr.sin_addr.s_addr = inet_addr(ip.c_str());
    _serverAddr.sin_port = htons(port);
    _serverAddr.sin_family = AF_INET;
}
InetAddress::InetAddress(const struct sockaddr_in &addr){
    _serverAddr.sin_addr.s_addr = addr.sin_addr.s_addr;
    _serverAddr.sin_family = addr.sin_family;
    _serverAddr.sin_port = addr.sin_port;
}

InetAddress::~InetAddress(){

}
//返回sockaddr_in  sockaddr的地址族信息
const sockaddr_in * InetAddress::getInetAddrPtr() const
{
    return &_serverAddr;
}
string InetAddress::getServerIP() const{
    return string(inet_ntoa(_serverAddr.sin_addr));
}
unsigned short InetAddress::getServerPort()const{
    return _serverAddr.sin_port;
}

Acceptor

#ifndef __ACCEPTOR_H__
#define __ACCEPTOR_H__

#include "Socket.h"
#include "InetAddress.h"
#include <string>

using std::string;

class Acceptor
{
public:
    Acceptor(const string &ip, unsigned short port);
    ~Acceptor();
    void ready();
    void setReuseAddr();
    void setReusePort();
    void bind();
    void listen();
    int accept();
    int fd() const;

private:
    Socket _listenSock;
    InetAddress _servAddr;
};

#endif

Acceptor.cc

#include "Acceptor.h"
#include <stdio.h>

Acceptor::Acceptor(const string &ip, unsigned short port)
: _listenSock()
, _servAddr(ip, port)
{
}

Acceptor::~Acceptor()
{

}

void Acceptor::ready()
{
    setReuseAddr();
    setReusePort();
    bind();
    listen();
    //accept(); 
}

void Acceptor::setReuseAddr()
{
    int on = 1;
    int ret = setsockopt(_listenSock.fd(), SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    if(ret)
    {
        perror("setsockopt");
        return;
    }
}

void Acceptor::setReusePort()
{
    int on = 1;
    int ret = setsockopt(_listenSock.fd(), SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
    if(-1 == ret)
    {
        perror("setsockopt");
        return;
    }
}

void Acceptor::bind()
{
    int ret = ::bind(_listenSock.fd(), 
                     (struct sockaddr *)_servAddr.getInetAddrPtr(),
                     sizeof(struct sockaddr));
    if(-1 == ret)
    {
        perror("bind");
        return;
    }
}

void Acceptor::listen()
{
    int ret = ::listen(_listenSock.fd(), 128);
    if(-1 == ret)
    {
        perror("listen");
        return;
    }
}

int Acceptor::accept()
{
    int connfd = ::accept(_listenSock.fd(), nullptr, nullptr);
    if(-1 == connfd)
    {
        perror("listen");
        return -1;
    }
    return connfd;
    
}
int Acceptor::fd() const
{
    return _listenSock.fd();
}

InitConnection

/* 
/* 这个类就是用来实现初始化创建链接的整个过程(从socket 到accept 成功创建链接),或者说用来实现整个流程的枢纽,运行实现就行了
 */

#ifndef __INITCONNETION_H__
#define __INITCONNETION_H__

#include "Socket.h"
#include "InetAddress.h"
#include "ProcessIO.h"
#include <string>

using std::string;

class InitConnection{
private:
    Socket _sock;
    ProcessIO _processio;
    InetAddress _localaddr;
    InetAddress _peeraddr;
private:  
    InetAddress getLocalAddress();
    InetAddress getpeerAddress();

public:
    InitConnection(int fd);
    ~InitConnection();
    void send(const string &msg);
    string receive();
    string printConnetionInfo();

};


#endif
#include "InitConnection.h"
#include <iostream>
#include <sstream>
using std::cout;
using std::endl;
using std::ostringstream;

InitConnection::InitConnection(int fd)
:_sock(fd)
,_localaddr(getLocalAddress())
,_peeraddr(getpeerAddress())
,_processio(_sock.fd())   //实际上运行到这里,_sock的 fd已经时经过accept处理的已连接文件描述符,直接用就行
{ 

}
InitConnection::~InitConnection(){

}
InetAddress InitConnection::getLocalAddress(){
    struct sockaddr_in addr;
    socklen_t socklen = sizeof(struct sockaddr);
    int ret = getsockname(_sock.fd(),(struct sockaddr *)&addr,&socklen);
    if(ret == -1){
        perror("InitConnection getsockaddr ");
    }
    return InetAddress(addr);     //因为我们使用服务器的地址进行初始化时,
}
InetAddress InitConnection::getpeerAddress(){
    struct sockaddr_in addr;
    socklen_t socklen = sizeof(struct sockaddr);
    int ret = getpeername(_sock.fd(),(struct sockaddr *)&addr,&socklen);
    if(ret == -1){
        perror("InitConnection getpeersockaddr ");
    }
    return InetAddress(addr);
}

string InitConnection::printConnetionInfo(){
    ostringstream oss1;
    oss1 << "服务端地址 "<< _localaddr.getServerIP() << ": "
         << _localaddr.getServerPort() << "----> 客户端地址"
         << _peeraddr.getServerIP() << ": "
         << _peeraddr.getServerPort() << ": ";
         return oss1.str();
}


void InitConnection::send(const string &msg)
{
    _processio.writen(msg.c_str(), msg.size());
}

string InitConnection::receive()
{
    char buff[65535] = {0};    //这里设定的足够大,才能在我们真正进行读写IO操作时,可以保证正常读取,但是注意不要过大,毕竟系统存储空间是有限的,资源合理利用
    _processio.readLine(buff, sizeof(buff));

    return string(buff);
}





ProcessIO

#ifndef __SOCKETIO_H__
#define __SOCKETIO_H__

class ProcessIO{

public:
    explicit ProcessIO(int fd);
    ~ProcessIO();
    int readn(char *buf,int len);
    int readLine(char *buf, int len);
    int writen(const char *buf, int len);
private:
    int _fd;    //这个就是链接 accept后生成的文件描述符

};



#endif
#include "ProcessIO.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <error.h>
#include <stdio.h>

ProcessIO::ProcessIO(int fd){
    _fd = fd;
}

ProcessIO::~ProcessIO(){
    //因为这个只进行基于数据读写的操作,所以不在这里进行释放 _fd,会在这个访问链接整体断开后的情况下,进行释放。
    //因为系统的文件描述符是有限的,最好是释放后再重新创建
    //close(_fd);
}

int ProcessIO::readn(char *buf,int len){   //此处考虑从客户端接收的数据大小问题,buf大小可以设定大一些
    
    int left = len;
    char * pstr = buf;   //这个函数设定中能实现的基础时这个buf 足够大,可放得下读取的客户端信息信息
    int ret = 0;
    while(left > 0){
        ret = read(_fd, buf, len);
        if(-1 == ret){
            perror("read error -1");
            return len-ret;
        }
        else if(0 == ret){
            break;
        }
        else{
            pstr += ret;   //一次不能读完的情况下,将buf指针指向当前读取到数据的最后一个,方便将数写入buf中
            left -= ret;   //读取的数据总量
        }
    }
    return len - left; //这里,因为上面的循环读取,正常情况下一定会将数据读取完后退出,这里返回下buf中读取到的数据大小
}
int ProcessIO::readLine(char *buf, int len){
    //因为直接使用read,我们没办法控制读取一行数据,只能将数据读取后,进行处理
    //如果使用read,那我们就需要多开辟一块缓存空间去存储数据,不然无法有效将原buf中 某一行数据读取出来
    //但是可以使用recv(),这个不会清空缓存区,我们只要再次调用读写就行,不过就是对字节数有限制
    int left = len-1;
    char * pstr = buf;
    int total = 0,linesize = 0;  //读取的总字节数和行字数
    
    while(left > 0){
        int ret = recv(_fd, buf, len, MSG_PEEK);   //这个属性不会清空缓存区域,也就是读取后,本缓存区内容在下一次调用这同一个函数时,buf内容相同
        if( 0 == ret){
            break;
        }
        else if(-1 == ret){  //这里原来写了错误判断 errno, 但是头文件有问题删了。产生eintr系统调用错误时,本系统调用可以再次调用进行使用,可以重启此调用,但是有的不行
            continue;
        }
        if(-1 == ret){
            perror("readline error -1");
            return len - left;  //这里表示本次循环虽然出错了,之前可能正常读取到了信息,返回下数量
        }
        else{
            for(int idx  = 0; idx < ret ; idx++){
                if(pstr[idx] == '\n'){   
                    linesize = idx+1;    //读取一行时,最后一个字符一定时换行符,
                    readn(pstr,linesize);    //这里就是直接调用上面的函数,相当于执行一个read,读取一行数据,存储在buf中
                    pstr += linesize;
                    *pstr = '\n';
                    return total+linesize;
                }
            }
            //如果没有执行上面循环中的if结构,那就说明,还没有读到换行符,没有读够一行
            total += ret;  //这里因为 readn中也有ret  ,有时注意下变量命名
            readn(pstr, ret);  //直接将数据读入到缓冲区中
            pstr += ret;     //pstr位置更行
            left -= ret;     //剩余可读取数量更新 ,之后执行下一次读写循环
        }
    }
    *pstr = '\0';   //只要buf够大,最后一个元素一定时 \0
    return total =- left;  
}
int ProcessIO::writen(const char *buf, int len){
    int left = len;
    const char *pstr = buf;
    int ret = 0;

    while(left > 0){
        ret = write(_fd, pstr, left);
        if(-1 == ret)
        {
            continue;
        }
        else if(-1 == ret)
        {
            perror("writen error -1");
            return len - ret;
        }
        else if(0 == ret)
        {
            break;
        }
        else
        {
            pstr += ret;
            left -= ret;
        }
    }
    return len - left;
}

NonCopyable

#ifndef __NONCOPYABLE_H__
#define __NONCOPYABLE_H__
class NonCopyable{

protected:
    NonCopyable(){};
    ~NonCopyable(){};
    NonCopyable (const NonCopyable &) = delete;    //赋值运算符
    NonCopyable &operator&=(const NonCopyable) = delete;

};


#endif

testServer

#include "Acceptor.h"
#include "InitConnection.h"
#include <iostream>
#include <unistd.h>
//这个案例 目前只能链接一个客户端

using std::cout;
using std::endl;

void test1(){
    Acceptor acceptor1("127.0.0.1",8888);
    acceptor1.ready();  //这里就是已经到accept 步骤了

    InitConnection conn(acceptor1.accept());  //这里呢,实际上,就是connection 类中需要已经链接好的fd参数,进行Connection中的操作
    //InitConnection conn2(acceptor1.accept());
    cout<< conn.printConnetionInfo() << "链接成功"<<endl;
    //cout<< conn2.printConnetionInfo() << "链接成功"<<endl;   //测试同时开启两个不行,还是需要写多路复用后才可以
    while(1)
    {
        cout << ">>recv msg from client: " << conn.receive() << endl;
        conn.send("hello baby\n");
    }
}

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

测试结果

使用telnet进行测试,当我测试发送中文时,telnet 默认发送了数字

//有问题或者发现问题,可以直接留言

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1406189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】(3)字符

字符串 1. 字符串简介 在C语言中&#xff0c;字符串是由字符数组构成的序列&#xff0c;以空字符&#xff08;\0&#xff09;结尾。这个空字符不可见&#xff0c;用于标记字符串的结束。C语言中没有专门的字符串类型&#xff0c;通常使用字符数组表示字符串。 2. 声明和初始…

Redis 高可用之主从复制

1、简介 在 Redis 中&#xff0c;主从复制就是多个节点进行数据同步&#xff0c;在这些节点中&#xff0c;有 Master 和 slave 两个角色&#xff0c;Master 以写为主&#xff0c;slave 以读为主&#xff0c;当 Master 数据变化的时候&#xff0c;会自动将新的数据同步到其他的 …

让代码在键盘上跳“华尔兹”的10大原则

大家好&#xff0c;我是小❤&#xff0c;一个漂泊江湖多年的 985 非科班程序员&#xff0c;曾混迹于国企、互联网大厂和创业公司的后台开发攻城狮。 引言 编程界都知道&#xff0c;代码是一种艺术 —— 它是对技术职责和美学眼光的完美融合。 正如一名工匠在雕琢他的作品&am…

白酒:制曲工艺的特点与核心技术

白酒&#xff0c;其制曲工艺是品质的关键所在。制曲是酿酒过程中重要的环节&#xff0c;对于酒的口感和品质有着至关重要的影响。在云仓酒庄豪迈白酒的制曲工艺中&#xff0c;有几个显著的特点和核心技术。 制曲原料丰富多样&#xff0c;除了常见的麦类&#xff0c;还可采用了…

菜鸟导入导出assetbundle

因为菜鸟不会用unity c#什么的&#xff0c;所以最后参考贴吧的方法用的是UABE(Unity Assets Bundle Extractor)和UABEA(Unity Assets Bundle Extractor Avalonia) 可以去github上下载 对于txt、xml什么的可以直接改&#xff0c;但是byte文件里还是会有一些类似乱码的东西&…

使用 Swift 代码优化项目编译速度

引言 软件的性能是评价一个软件质量的重要指标&#xff0c;尤其在今天这个时代&#xff0c;性能已成为大型项目不可或缺的考虑因素之一。对于用户量极大的软件&#xff0c;如网银系统、在线购物商城等&#xff0c;更是必须保证其高效稳定的性能。在这种背景下&#xff0c;优化…

AJAX入门到实战,学习前端框架前必会的(ajax+node.js+webpack+git)(十)

又是认真学习的一天。 1.Git 初识 2.掌握 Git 仓库 3.Git 的三个区域 git ls-files 查看当前暂存区有哪些文件 4.Git 文件状态 5.Git 暂存区使用 使用git restore命令恢复修改过的index.css 使用git rm --catched命令从暂存区移除index.css文件 再用git add .放回9&#xff08;…

使用 SpringBoot 框架手撸一个本地缓存工具!

在实现本地缓存的时候&#xff0c;我们经常使用线程安全的ConcurrentHashMap来暂存数据&#xff0c;然后加上SpringBoot自带的Scheduled定时刷新缓存。虽然这样可以实现本地缓存&#xff0c;但既不优雅也不安全。 那看一下我的思路&#xff0c;首先看一张图! 1.每个处理器都有…

喷墨打印机市场分析:预计2029年将达到548亿美元

喷墨打印机是将彩色液体油墨经喷嘴变成细小微粒喷到印纸上,有的喷墨打印机有三个或四个打印喷头&#xff0c;以便打印黄、品红青黑四色;有的是共用一个喷头&#xff0c;分四色喷印。 喷墨打印机是在针式打印机之后发展起来的&#xff0c;采用非打击的工作方式。比较突出的优点有…

kubernetes Pod 异常排查步骤

kubernetes Pod 异常排查步骤 详细排查图查看容器状态查看容器列表容器未启动成功排查容器启动成功排查pod状态对应原因 详细排查图 查看容器状态 查看容器列表 查看容器列表,最好在后面跟上命名空间,不跟上查询出来是默认的 kubectl get pods -n kubesphere-system单独查看某…

eNSP学习——理解ARP及Proxy ARP

目录 名词解释 实验内容 实验目的 实验步骤 实验拓扑 配置过程 基础配置 配置静态ARP 名词解释 ARP (Address Resolution Protocol)是用来将IP地址解析为MAC地址的协议。ARP表项可以分为动态和静态两种类型。   动态ARP是利用ARP广播报文&#xff0c;动态执行并自动进…

sprignboot电商书城源码

运行环境: jdk1.8,maven,mysql 项目技术: 后台主要是springbootmybatisshirojsp&#xff0c;前端界面主要使用bootstrap框架搭建&#xff0c;并使用了ueditor富文本编辑器、highcharts图表库。 有需要的可以联系我。 功能介绍&#xff1a; 该系统分为前台展示和后台管理两…

芯课堂 | 通过ISP升级芯片固件方法及框架

一、升级原理 芯片在应用前&#xff0c;是一颗裸片&#xff0c;内部没有任何驱动或应用程序。芯片在贴上PCB板子后&#xff0c;会实现各种功能&#xff0c;这是时候会开发对应的驱动或者应用程序&#xff0c;在芯片上面运行的程序&#xff0c;一般称之为固件&#xff08;Firmw…

低代码,让软件开发不再复杂

低代码一词&#xff0c;有人认为它是第四代编程语言&#xff0c;有人认为它是开发模式的颠覆&#xff0c;也有人认为它是企业管理模式的变革……有很多声音&#xff0c;社区讨论很热烈。 即使这样&#xff0c;至今也有不少人还不知道这项技术&#xff0c;今天笼统的介绍一下低代…

DataStream API(输出算子)

源算子 源算子 转换算子 转换算子 输出算子 1.连接到外部系统 连接外部系统是计算机科学和信息技术领域中常见的一个任务&#xff0c;通常涉及到与外部数据源或服务进行交互。具体的方法和工具会根据不同的应用场景和需求而有所不同。以下是一些常见的连接外部系统的方法&…

BioTech - 量子化学与分子力场

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/135787607 量子化学是应用量子力学的规律和方法来研究化学问题的一门学科&#xff0c;主要关注分子的结构、性质和反应过程。 量子化学的理论方法…

Midjourney基础 | 使用流程 注册,基础文生图,图的放大微调,保存

文章目录 1 使用流程2 生成自己的第一张图3 图的放大&#xff0c;微调3.1 放大3.2 微调变化 4 图的保存 Midjourney是依托于Discord的&#xff0c;但我也是通过Midjourney才了解的Discord 维基百科说~~Discord是一款专为社群设计的免费网络实时通话&#xff0c;主要针对游戏玩家…

Dify学习笔记-手册(三)

1、应用构建及提示词 在 Dify 中&#xff0c;一个“应用”是指基于 GPT 等大型语言模型构建的实际场景应用。通过创建应用&#xff0c;您可以将智能 AI 技术应用于特定的需求。它既包含了开发 AI 应用的工程范式&#xff0c;也包含了具体的交付物。 简而言之&#xff0c;一个应…

【设计模式】美团三面:你连装饰器都举不出例子?

什么是装饰器模式&#xff1f; 装饰器模式&#xff0c;这个设计模式其实和它的名字一样&#xff0c;非常容易理解。 想象一下&#xff0c;每天出门的时候&#xff0c;我们都会思考今天穿什么。睡**衣、睡裤加拖鞋&#xff0c;还是西装、领带加皮鞋&#xff1f;又或者说是&…

获取b站目录

参考链接&#xff1a; JS获取B站视频选集目录 Week6 - 知乎 代码 var x document.getElementsByClassName("clickitem"); var i; for (i 0; i < x.length; i) {var page_num x[i].getElementsByClassName("page-num")[0].innerText;var part x[i…