找工作小项目:day9-缓冲区构建

day9-缓冲区构建

之前构造的缓冲区是利用字符串数组构建的,注意之前有说过在客户端发送给服务端一个信号后,服务端会回显给客户端,客户端读取方式仍然是面向过程的,而面向对象的目前只针对服务器。
今天开始,由于有了新的缓冲区,古早的客户端也将发生改变。老规矩通过标题复习一下之前做的所有工作。

1、错误检测机制(没变)

2、地址创建(没变)

3、Socket创建(没变)

4、并发epoll(没变)

5、Channel(没变)

6、EventLoop(没变)

7、Acceptor(没变)

看标题回忆我们做了什么,怎么做的,实现了哪些功能(这对于面向对象变成很重要,毕竟封装起来就看不到实现方案了)

8、Connection

能够预料到这部分会发生改变,因为在前一天我们将这读操作和释放放到了这部分,既然负责读操作必然涉及到缓冲区,那么声明应该多一个缓冲区,而事实是确实多了一个缓冲区类,但是又增加了一个inBuffer的字符串指针,有可能这个是指向当前要往缓冲区写的内容的。

class EventLoop;
class Socket;
class Channel;
class Buffer;
class Connection
{
private:
    EventLoop *loop;
    Socket *sock;
    Channel *channel;
    std::function<void(Socket*)> deleteConnectionCallback;
    std::string *inBuffer;
    Buffer *readBuffer;
public:
    Connection(EventLoop *_loop, Socket *_sock);
    ~Connection();
    
    void echo(int sockfd);
    void setDeleteConnectionCallback(std::function<void(Socket*)>);
};

看看实现和我们的推测是否一致,构造函数应该是多了对两个成员的构造。
析构函数应该析构所有在所属类中未析构的部分,比如Socket类仅关闭了socket对于sock并不做处理。

Connection::Connection(EventLoop *_loop, Socket *_sock) : loop(_loop), sock(_sock), channel(nullptr), inBuffer(new std::string()), readBuffer(nullptr){
    channel = new Channel(loop, sock->getFd());
    std::function<void()> cb = std::bind(&Connection::echo, this, sock->getFd());
    channel->setCallback(cb);
    channel->enableReading();
    readBuffer = new Buffer();
}

Connection::~Connection(){
    delete channel;
    delete sock;
}

之后是发生变化最大的部分-读操作,原本是用buf[1024]完成全部操作,目前看来新的方案是将buf中的数据全部缓冲到readBuffer中然后回显,差别是原本在bytes_read > 0时回显,而现在是数据完全读取完毕再回显(详述一下,比如我现在有一个1035长度的数据,之前会在1024处进行一次回显,现在是整体读取完毕后回显)。那么buf就是一个简单的中间对象,保证完全读完一批数据(即缓冲区长度可变)。

void Connection::echo(int sockfd){
    char buf[1024];     //这个buf大小无所谓
    while(true){    //由于使用非阻塞IO,读取客户端buffer,一次读取buf大小数据,直到全部读取完毕
        bzero(&buf, sizeof(buf));
        ssize_t bytes_read = read(sockfd, buf, sizeof(buf));
        if(bytes_read > 0){
            readBuffer->append(buf, bytes_read);
        } else if(bytes_read == -1 && errno == EINTR){  //客户端正常中断、继续读取
            printf("continue reading");
            continue;
        } else if(bytes_read == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))){//非阻塞IO,这个条件表示数据全部读取完毕
            printf("finish reading once\n");
            printf("message from client fd %d: %s\n", sockfd, readBuffer->c_str());
            errif(write(sockfd, readBuffer->c_str(), readBuffer->size()) == -1, "socket write error");
            readBuffer->clear();
            break;
        } else if(bytes_read == 0){  //EOF,客户端断开连接
            printf("EOF, client fd %d disconnected\n", sockfd);
            // close(sockfd);   //关闭socket会自动将文件描述符从epoll树上移除
            deleteConnectionCallback(sock);
            break;
        }
    }
}

void Connection::setDeleteConnectionCallback(std::function<void(Socket*)> _cb){
    deleteConnectionCallback = _cb;
}

9、Buffer

思考一下,之前很多变化都是造成结构上的变化,而这一次只是造成内容上的变化,所以Buffer应当是一个比较简单的类,甚至不需要调用其它类就能完成,相对比较独立。

class Buffer
{
private:
    std::string buf;
public:
    Buffer();
    ~Buffer();
    void append(const char* _str, int _size);
    ssize_t size();
    const char* c_str();
    void clear();
    void getline();
};

这里有在Connection中调用的append、c_str、size和clear,推测分别是向缓冲区添加数据,获取缓冲区数据以及清空缓冲区。
直接上实现来看看
构造和析构都是空的。

Buffer::Buffer()
{
}

Buffer::~Buffer()
{
}

来看看上面调用过的方法,其中用的相对较少的是string的c_str方法,这个方法会返回一个临时指针(完成任务马上释放)用以指向字符串。

void Buffer::append(const char* _str, int _size){
    for(int i = 0; i < _size; ++i){
        if(_str[i] == '\0') break;
        buf.push_back(_str[i]);
    }
}
const char* Buffer::c_str(){
    return buf.c_str();
}
void Buffer::clear(){
    buf.clear();
}
ssize_t Buffer::size(){
    return buf.size();
}

之后是没用过的方法,getline实现来看是写入操作。

void Buffer::getline(){
    buf.clear();
    std::getline(std::cin, buf);
}

10、服务器类(没变)

11、客户端

在服务器创建过程也抽象为了类

Socket *sock = new Socket();
InetAddress *addr = new InetAddress("127.0.0.1", 1234);
sock->connect(addr);

int sockfd = sock->getFd();

创建两个缓冲区,一个用来发送数据,一个用来读取数据

Buffer *sendBuffer = new Buffer();
Buffer *readBuffer = new Buffer();

通过getline来向sendBuffer写入数据,之后将sendBuffer中的数据写入到服务器,用already_read 记录下当前已经读取的数据量,不断地向readBuffer中加入数据,当readBuffer中的数据超过sendBuffer中的数据长度返回向客户端写入数据的客户端编号。

while(true){
	sendBuffer->getline();
	ssize_t write_bytes = write(sockfd, sendBuffer->c_str(), sendBuffer->size());
	if(write_bytes == -1){
		printf("socket already disconnected, can't write any more!\n");
		break;
	}
	int already_read = 0;
	char buf[1024];    //这个buf大小无所谓
	while(true){
		bzero(&buf, sizeof(buf));
		ssize_t read_bytes = read(sockfd, buf, sizeof(buf));
		if(read_bytes > 0){
			readBuffer->append(buf, read_bytes);
			already_read += read_bytes;
		} else if(read_bytes == 0){         //EOF
			printf("server disconnected!\n");
			exit(EXIT_SUCCESS);
		}
		if(already_read >= sendBuffer->size()){
			printf("message from server: %s\n", readBuffer->c_str());
			break;
		} 
	}
	readBuffer->clear();
}

总结一下,今天仅仅是对缓冲区进行操作,整体结构没有变化,只针对数据处理方式抽象成一个类。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值