文件夹muduo-master>muduo下面有base(放公共的网络文件)和net(存放和网络相关的,比如poller是具体的多路事件分发器),我们写的时候就不这么复杂了,主要窥探的是它的网络模块。
我们创建的项目工程名:mymuduo,把所有的代码都放到这里面。
1. 构建项目编译CMake文件
我们这是一个网络库,并不是一个可以直接执行的一个应用服务,所以我们最终是把它编译成动态库,Muduo库本身是编译成静态库的,muduo库的安装真的很难,因为Muduo库用了boost库里面的很多东西,而我们在写的时候就全部脱离boost库了,用c++11把现有的muduo库里和boost库相关的都干掉,用c++本身来完成,使用muduo库直接编译成so库和头文件提供给用户使用。
cmake_minimum_required(VERSION 2.5) #要求当前系统环境必须装的cmake环境>=2.5,才可以编译当前的项目
project(mymuduo) #启动mymuduo
# cmake => makefile make
# mymuduo最终编译成so动态库,设置动态库路径,放在根目录的lib文件夹下面
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 设置调试信息 以及 启动C++11语言标准
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11 -fPIC") # 有些人本地的编译环境gcc,g++版本如果不是非常新的话,默认C++的语法没有打开,应该再加个-std=c++11
# 定义参与编译的源代码文件,把当前根目录下的名字源文件组合起来放在变量SRC_LIST里面
aux_source_directory(. SRC_LIST)
# 编译生成动态库mymuduo
add_library(mymuduo SHARED ${SRC_LIST})
生成libmuduo.so
2. noncopyable代码
不管是用接口类TcpServer编写服务器还是用TcpClient编写客户端程序,还是在看源码很多相关的类eventloop,socked,acceptor,channel,connection,可以发现每一个类都是从noncopyable继承而来。
noncopy 不可复制的意思
我们进去看看究竟
把拷贝构造函数和赋值函数都delete掉了,默认的构造函数和析构函数
TCPServer的对象可以创建,因为派生类对象的创建会调用基类的构造函数,基类的构造和析构是保护的,派生类是可以访问的。
基类的拷贝构造函数和赋值函数直接delete掉了,那当我们对TCPServer的对象进行拷贝构造和赋值的时候,肯定要先调用基类部分的拷贝构造和赋值,然后才是派生类的拷贝构造和赋值,但是基类的拷贝构造和赋值已经删掉了,所以说,从noncopyable继承而来的好处是直接让其派生类都不可拷贝构造和赋值。
这个写法我们要学习啊!!!
有些人会觉得搞得这么麻烦干什么,也可以不从noncopyable继承而来,可以把TcpServer的拷贝构造和赋值在这里面delete掉,当然可以,但是这是一个大的项目,里面有100个类,让这么多类都不能做拷贝构造和赋值,要写100次把拷贝构造和赋值delete掉吗?这样的设计非常丑陋。
重写noncopyable.h
#pragma once // 防止头文件被重复包含
/*
noncopyable被继承以后,派生类对象可以正常的构造和析构,
但派生类对象无法进行拷贝构造和赋值操作
*/
class noncopyable
{
public:
noncopyable(const noncopyable&) = delete;
noncopyable& operator=(const noncopyable&) = delete;
protected:
noncopyable() = default;
~noncopyable() = default;
};
3. Logger日志代码
很多时候当软件应用以后gdb调试还是有很多不便的,日志是我们来处理问题最直接的最佳的路径,muduo库的日志设计的还不是非常的好,在许多c++的日志里面都是用格式化字符的方式进行日志的输出,可以很好的按作者的风格进行输出,可以很好的组织输出的格式。
Logger.h
#pragma once
#include<string>
#include "noncopyable.h"
//LOG_INFO("%s %d", arg1, arg2)
//__VA_ARGS__获取可变参的宏
//logmsgFormat:字符串,后面...是可变参
//为了防止造成意想不到的错误用do-while(0)
#define LOG_INFO(logmsgFormat,...)\
do\
{\
Logger &logger = Logger::instance();\
logger.setLogLevel(INFO);\
char buf[1024] = {0};\
snprintf(buf,1024,logmsgFormat,##__VA_ARGS__);\
logger.log(buf);\
}while(0)
#define LOG_ERROR(logmsgFormat,...)\
do\
{\
Logger &logger = Logger::instance();\
logger.setLogLevel(ERROR);\
char buf[1024] = {0};\
snprintf(buf,1024,logmsgFormat,##__VA_ARGS__);\
logger.log(buf);\
}while(0)
#define LOG_FATAL(logmsgFormat,...)\
do\
{\
Logger &logger = Logger::instance();\
logger.setLogLevel(FATAL);\
char buf[1024] = {0};\
snprintf(buf,1024,logmsgFormat,##__VA_ARGS__);\
logger.log(buf);\
exit(-1);\
}while(0)
#ifdef MUDEBUG
#define LOG_DEBUG(logmsgFormat,...)\
do\
{\
Logger &logger = Logger::instance();\
logger.setLogLevel(DEBUG);\
char buf[1024] = {0};\
snprintf(buf,1024,logmsgFormat,##__VA_ARGS__);\
logger.log(buf);\
}while(0)
#else
#define LOG_DEBUG(logmsgFormat,...)
#endif
// 定义日志的级别,基本上日志分为这几个级别:INFO打印重要的流程信息;
// ERROR并不是所有的ERROR都得exit;
// FATAL这种问题出现系统无法继续向下运行,就得输出关键的日志信息然后exit;
// DEBUG在系统正常运行会默认把DEBUG关掉,在需要输出DEBUG日志才会打开开关
enum LogLevel
{
INFO, //普通信息
ERROR, //错误信息
FATAL, //core信息
DEBUG, //调试信息
};
// 输出一个日志类
class Logger : noncopyable
{
public:
// 获取日志唯一的实例对象
static Logger& instance();
// 设置日志级别
void setLogLevel(int level);
// 写日志
void log(std::string msg);
private:
int logLevel_;
};
我们系统的变量很多都写_开头,我们书写定义成员变量把_放在末尾,去和系统的变量不产生冲突,而且为了区分函数内的变量(局部变量)。
Logger.cc
#include "Logger.h"
#include "Timestamp.h"
#include<iostream>
// 获取日志唯一的实例对象
Logger& Logger::instance()
{
static Logger logger;
return logger;
}
// 设置日志级别
void Logger::setLogLevel(int level)
{
logLevel_ = level;
}
// 写日志 打印顺序:[级别信息] time msg
void Logger::log(std::string msg)
{
switch(logLevel_)
{
case INFO:
std::cout << "[INFO]";
break;
case ERROR:
std::cout << "[ERROR]";
break;
case FATAL:
std::cout << "[FATAL]";
break;
case DEBUG:
std::cout << "[DEBUG]";
break;
}
//打印时间和msg
std::cout << Timestamp::now().toString() << ":" << msg << std::endl;
}
4. TimeStamp时间代码
这个类里面有2个主要方法和一个成员变量
手写Timestamp
Timestamp.h
#pragma once
#include<iostream>
#include<string>
class Timestamp
{
public:
Timestamp(); //默认构造
explicit Timestamp(int64_t microSecondsSinceEpoch); //带参数的构造,带参数的构造函数都加了explicit关键字:避免隐式对象转换
static Timestamp now(); //获取当前时间
std::string toString() const; //获取当前时间的年月日时分秒的输出
private:
int64_t microSecondsSinceEpoch_;
};
带参数的构造函数都加了explicit关键字:避免隐式对象转换,防止程序最后的行为和我们预想的不一样。 Timestamp.cc
#include "Timestamp.h"
#include<time.h>
Timestamp::Timestamp():microSecondsSinceEpoch_(0){}
Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
:microSecondsSinceEpoch_(microSecondsSinceEpoch)
{}
Timestamp Timestamp::now()
{
return Timestamp(time(NULL)); //获取当前时间
}
std::string Timestamp::toString()const
{
char buf[128] = {0};
tm *tm_time = localtime(µSecondsSinceEpoch_);
snprintf(buf,128,"%4d/%02d/%02d %02d:%02d:%02d",
tm_time->tm_year + 1900,
tm_time->tm_mon + 1,
tm_time->tm_mday,
tm_time->tm_hour,
tm_time->tm_min,
tm_time->tm_sec);
return buf;
}
// #include<iostream>
// int main()
// {
// std::cout<<Timestamp::now().toString()<<std::endl;
// }
5. InetAddress
TCPServer就是我们用muduo库编程 服务器程序的入口的类,我们经常自定义一个类,把TCPServer对象组合一下。
eventloop就是事件循环,相当于多路事件分发器,相当于epoll,InetAddress,需要这个对象来打包IP地址和端口号作为TCPServer对象的构造函数的参数。
我们来看看InetAddress
InetAddress.h
#pragma once
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string>
class InetAddress
{
public:
explicit InetAddress(uint16_t port = 8888, std::string ip = "127.0.0.1");
explicit InetAddress(const sockaddr_in &addr)
:addr_(addr)
{}
std::string toIp()const;
std::string toIpPort()const;
uint16_t toPort()const;
const sockaddr_in* getSockAddr() const { return &addr_; }
void setSockAddr(const sockaddr_in &addr) { addr_ = addr; }
private:
sockaddr_in addr_;
};
InetAddress.cc
#include "InetAddress.h"
#include<strings.h>
#include<string.h>
InetAddress::InetAddress(uint16_t port,std::string ip)
{
bzero(&addr_,sizeof addr_); //清零
addr_.sin_family = AF_INET;
addr_.sin_port = htons(port); //把本地字节序转成网络字节序,两个不同的端要通信的时候,系统都有可能不一样,我是小端你是大端,网路字节都是大端,我们需要都转成网络字节序,通过网络传送到对端后,再将网络字节序转成本地字节序,这样互相传输的数据都能识别了
addr_.sin_addr.s_addr = inet_addr(ip.c_str());
}
std::string InetAddress::toIp()const
{
char buf[64] = {0};
::inet_ntop(AF_INET,&addr_.sin_addr,buf,sizeof buf);//读出整数的表示网络字节序转成本地字节序
return buf;
}
std::string InetAddress::toIpPort()const
{
char buf[64] = {0};
::inet_ntop(AF_INET,&addr_.sin_addr,buf,sizeof buf);
size_t end = strlen(buf);
uint16_t port = ntohs(addr_.sin_port);
sprintf(buf+end,":%u",port);
return buf;
}
uint16_t InetAddress::toPort()const
{
return ntohs(addr_.sin_port);
}
// #include<iostream>
// int main()
// {
// InetAddress addr(8080);
// std::cout<<addr.toIpPort()<<std::endl;
// }