Socket-IO模型:初步模型

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] == '
'){   
                    linesize = idx+1;    //读取一行时,最后一个字符一定时换行符,
                    readn(pstr,linesize);    //这里就是直接调用上面的函数,相当于执行一个read,读取一行数据,存储在buf中
                    pstr += linesize;
                    *pstr = '
';
                    return total+linesize;
                }
            }
            //如果没有执行上面循环中的if结构,那就说明,还没有读到换行符,没有读够一行
            total += ret;  //这里因为 readn中也有ret  ,有时注意下变量命名
            readn(pstr, ret);  //直接将数据读入到缓冲区中
            pstr += ret;     //pstr位置更行
            left -= ret;     //剩余可读取数量更新 ,之后执行下一次读写循环
        }
    }
    *pstr = '';   //只要buf够大,最后一个元素一定时 
    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
");
    }
}

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

测试结果

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

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