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();
}
总结一下,今天仅仅是对缓冲区进行操作,整体结构没有变化,只针对数据处理方式抽象成一个类。