目录
分层设计:
今天在看源码时,代码分层设计深深吸引了我。就拿一个简单的输出举例。输出一般有三种方式stdout、socket(网络打印调试信息)、log日志。我们如何将它们揉在一块呢。
分析:
如下是它的结构图
接下来我们从外到内进行分析:这是项目布局
这里面有两种打印方式,一种是stdout,一种是socket网络打印,而debug_manager.c就是用来管理它两的,是不是发现这样写很容易扩展呢,自己还可以添加一个localog.c(日志)。
这与平常最大的不同是有debug_manager.c文件,我们对它进行分析分析。
typedef struct DebugOpr {
char *name; /*打印类型 stdout、netprint*/
int isCanUse;
int (*DebugInit)(void); /* 调试模块的初始化函数 */
int (*DebugExit)(void); /* 退出函数 */
int (*DebugPrint)(char *strData); /* 输出函数 */
struct DebugOpr *ptNext;
}T_DebugOpr, *PT_DebugOpr;
stdout.c文件
static int StdoutDebugPrint(char *strData)
{
/* 直接把输出信息用printf打印出来 */
printf("%s", strData);
return strlen(strData);
}
static T_DebugOpr g_tStdoutDbgOpr = {
.name = "stdout",
.isCanUse = 1, /* 1表示将使用它来输出调试信息 */
.DebugPrint = StdoutDebugPrint, /* 打印函数 */
};
int StdoutInit(void)
{
return RegisterDebugOpr(&g_tStdoutDbgOpr);
}
netprint.c文件
static int NetDbgInit(void)
{
/* socket初始化 */
int iRet;
g_iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == g_iSocketServer)
{
printf("socket error!\n");
return -1;
}
g_tSocketServerAddr.sin_family = AF_INET;
g_tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
g_tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(g_tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(g_iSocketServer, (const struct sockaddr *)&g_tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
printf("bind error!\n");
return -1;
}
g_pcNetPrintBuf = malloc(PRINT_BUF_SIZE);
if (NULL == g_pcNetPrintBuf)
{
close(g_iSocketServer);
return -1;
}
/* 创建netprint发送线程: 它用来发送打印信息给客户端 */
pthread_create(&g_tSendTreadID, NULL, NetDbgSendTreadFunction, NULL);
/* 创建netprint接收线否: 用来接收控制信息,比如修改打印级别,打开/关闭打印 */
pthread_create(&g_tRecvTreadID, NULL, NetDbgRecvTreadFunction, NULL);
return 0;
}
static int NetDbgExit(void)
{
/* 关闭socket,... */
close(g_iSocketServer);
free(g_pcNetPrintBuf);
return 0;
}
static int NetDbgPrint(char *strData)
{
/* 把数据放入环形缓冲区 */
int i;
for (i = 0; i < strlen(strData); i++)
{
if (0 != PutData(strData[i]))
break;
}
/* 如果已经有客户端连接了, 就把数据通过网络发送给客户端 */
/* 唤醒netprint的发送线程 */
pthread_mutex_lock(&g_tNetDbgSendMutex);
pthread_cond_signal(&g_tNetDbgSendConVar);
pthread_mutex_unlock(&g_tNetDbgSendMutex);
return i;
}
static T_DebugOpr g_tNetDbgOpr = {
.name = "netprint",
.isCanUse = 1,
.DebugInit = NetDbgInit,
.DebugExit = NetDbgExit,
.DebugPrint = NetDbgPrint,
};
int NetPrintInit(void)
{
return RegisterDebugOpr(&g_tNetDbgOpr);
}
使用:
char strFileName[256];
memset(strFileName,0,sizeof(strFileName));
strcpy(strFileName,"hello");
DebugInit();
//在需要打印的地方,输上这句话就可以用了
DBG_PRINTF("<7>hi %s error!\n", strFileName);
拓展:
有没有一种疑问,这么写的代码咋用makefile写呢,模块里面也有一个makefile文件。
AS = $(CROSS_COMPILE)as #编译选项
LD = $(CROSS_COMPILE)ld #链接选项
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
export AS LD CC CPP
export STRIP OBJCOPY OBJDUMP
CFLAGS := -Wall -Werror -O2 -g
CFLAGS += -I $(shell pwd)/include #头文件
LDFLAGS := -lm - -lpthread #库文件
export CFLAGS LDFLAGS
TOPDIR := $(shell pwd) #顶层目录
export TOPDIR
TARGET := Debug #目标名字
obj-y += main.o
obj-y += debug/
all :
make -C ./ -f $(TOPDIR)/Makefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
解析:-Wall -Werror -O2 -g (Werror就是把warning 当error处理)
-Wall:选项可以打印出编译时所有的错误或者警告信息。这个选项很容易被遗忘,编译的时候,没有错误或者警告提示,以为自己的程序很完美,其实,里面有可能隐藏着许多陷阱。变量没有初始化,类型不匹配,或者类型转换错误等警告提示需要重点注意,错误就隐藏在这些代码里面。没有使用的变量也需要注意,去掉无用的代码,让整个程序显得干净一点。下次写Makefile的时候,一定加-Wall编译选项。
-O0: 表示编译时没有优化。
-O1: 表示编译时使用默认优化。
-O2: 表示编译时使用二级优化。
-O3: 表示编译时使用最高级优化。