【树莓派】DS18B20温度实时监控项目笔记与部分源码


前言

看sqlite那些内容的小伙伴肯定会发现,此项目是“未解封”时期的项目啦,不过写完后没有重新梳理,所以也就没有发博客,最近把它重新梳理复盘了一下然后整理到这里啦。


一、项目介绍

本项目中我们通过树莓派来模拟温度采样,当然用树莓派只是为了了解网络Socket通信的例子,在实际应用中因为成本等各种原因,我们一般使用单片机来进行采样。树莓派通过(1-Wire)一线协议来连接DS18B20温度传感器,而SHT30温湿度传感器通过的是I2C接口连接,在单片机项目中我们就用的是SHT30,可见:STM32-实时上报温湿度

在此项目中我们通过树莓派的DS18B20温度传感器获取温度后,实时上报到服务器端,并在两端都用到了数据库来对数据进行保存并保证数据,不会因其他因素在传输过程中丢失,同时客户端会保证Socket连接,断开后也能及时重连,而且会发送断开时的数据,服务器端选用了多路复用的epoll方式,同时结合了日志系统,Makefile文件等内容。

二、项目内容

1.项目结构

在这里插入图片描述

如上图所示,我们项目包括我们的客户端与服务器端,他们分别有三个文件夹,第一个是头文件文件夹,它当中包含了相应功能的所有头文件;第二个是项目文件文件夹,包含了我们的项目文件和一个makefile文件,编译后可生成可执行文件;第三个是资源文件文件夹,包含了所有功能的头文件和程序文件以及一个makefile文件,用于生成动态库或静态库,并会重新创建一个文件夹用于存放动态库或静态库。

sqlite相关知识可见:Linux下载安装和使用SQLite
动态库或静态库知识可见:动态库与静态库技术
makefile知识可见:Makefile编写和使用

2.客户端功能

Domain_Resol.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  Domain_Resol.h
 *    Description:  This file Domain_Resol.h
 *
 *        Version:  1.0.0(2022年11月19日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月19日 16时38分25秒"
 *                 
 ********************************************************************************/

#ifndef  _DOMAIN_RESOL_H_
#define  _DOMAIN_RESOL_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

int domain_getaddrinfo(char **servdn);//域名解析函数

#endif

socket_funcs.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  socket_funcs.h
 *    Description:  This file socket_funcs.h 
 *
 *        Version:  1.0.0(2022年11月19日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月19日 20时20分08秒"
 *                 
 ********************************************************************************/

#ifndef _SOCKET_FUNCS_H_
#define _SOCKET_FUNCS_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

int socket_and_connect(char *servip, int port);//建立Socket连接的函数
int socket_connect_state(int sockfd);//判断TCP连接是否断开的函数

#endif

functions.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  functions.h
 *    Description:  This file functions.h
 *
 *        Version:  1.0.0(2022年11月18日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月18日 19时50分13秒"
 *                 
 ********************************************************************************/

#ifndef _FUNCTIONS_H_
#define _FUNCTIONS_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

void print_usage(char *progname);//提示信息函数
int get_sn(char* SN,int len);//获取序列号函数
long get_time(char *datime,int len);//获取时间函数

#endif

ds18b20_get_temp.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  ds18b20_get_temp.h
 *    Description:  This file ds18b20_get_temp.h
 *
 *        Version:  1.0.0(2022年11月17日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月17日 21时47分24秒"
 *                 
 ********************************************************************************/

#ifndef _DS18B20_GET_TEMP_H_
#define _DS18B20_GET_TEMP_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

int get_temperature(float *temp);//通过DS18B20获取温度的函数

#endif

clisql.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  clisql.h
 *    Description:  This file client sqlite.h
 *
 *        Version:  1.0.0(2022年11月22日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月22日 11时21分57秒"
 *                 
 ********************************************************************************/

#ifndef _SERSQL_H_
#define _SERSQL_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

#define TABLENAME "client_table"

int Create_Database(sqlite3 **db);//创建数据库函数
static int callback(void *NotUsed, int argc, char **argv, char **azColName);//回调函数
int Create_Table(sqlite3 *db);//创建表函数
int Insert_Table(sqlite3 *db, char *SN, char *datime, float temp);//插入表函数
int Table_check_write_clean(sqlite3 *db,int sockfd,char *SN);//检查客户端临时储存表数据发送给服务器端并清空表的函数

#endif

logger.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  logger.h
 *    Description:  This file logger.h
 *
 *        Version:  1.0.0(2022年11月22日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月22日 19时58分13秒"
 *                 
 ********************************************************************************/

#ifndef  _LOGGER_H_
#define  _LOGGER_H_

/*
 * logger level
 */
enum
{
    LOG_LEVEL_ERROR,
    LOG_LEVEL_WARN,
    LOG_LEVEL_INFO,
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_MAX,
};

/*
 * logger prefix string for different logging levels
 */
#define LOG_STRING_ERROR  "ERROR"
#define LOG_STRING_WARN   "WARN "
#define LOG_STRING_INFO   "INFO "
#define LOG_STRING_DEBUG  "DEBUG"


/*
 * logger initial and terminate functions
 */
int logger_init(char *filename, int loglevel);
void logger_term(void);

/*
 * logging methods by levels
 */
void log_error(char* format, ...);
void log_warn(char* format, ...);
void log_info(char* format, ...);
void log_debug(char* format, ...);

static void check_and_rollback(void);

#endif   /* ----- #ifndef _LOGGER_H_  ----- */

3.服务器端功能

functions.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  functions.h
 *    Description:  This file functions.h
 *
 *        Version:  1.0.0(2022年11月23日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月23日 10时25分44秒"
 *                 
 ********************************************************************************/

#ifndef _FUNCTIONS_H_
#define _FUNCTIONS_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

void print_usage(char *progname);//提示信息函数

#endif

socket_funcs.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  socket_funcs.h
 *    Description:  This file socket_funcs.h
 *
 *        Version:  1.0.0(2022年11月20日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月20日 21时51分42秒"
 *                 
 ********************************************************************************/

#ifndef _SOCKET_FUNS_H_
#define _SOCKET_FUNS_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

int socket_server_init(char *listen_ip, int listen_port);//监听并建立Socket连接的函数
void set_socket_rlimit(void);//内核对文件描述符有最大的限制,而epoll没有,所以这里定义一个函数来打破内核对文件描述符的限制

#endif

sersql.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  sersql.h
 *    Description:  This file server sqlite.h
 *
 *        Version:  1.0.0(2022年11月22日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月22日 11时21分57秒"
 *                 
 ********************************************************************************/

#ifndef _SERSQL_H_
#define _SERSQL_H_

#define CONFIG_DEBUG
#ifdef  CONFIG_DEBUG
#define dbg_print(format,args...) printf(format, ##args)
#else      /* -----  not CONFIG_DEBUG  ----- */
#define dbg_print(format,args...) do{} while(0)
#endif     /* -----  not CONFIG_DEBUG  ----- */

#define TABLENAME "server_table"

int Create_Database(sqlite3 **db);//创建数据库函数
static int callback(void *NotUsed, int argc, char **argv, char **azColName);//回调函数
int Create_Table(sqlite3 *db);//创建表函数
int Insert_Table(sqlite3 *db, char *SN, char *datime, float temp);//插入表函数

#endif

logger.h

/********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  logger.h
 *    Description:  This file logger.h
 *
 *        Version:  1.0.0(2022年11月22日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月22日 19时58分13秒"
 *                 
 ********************************************************************************/

#ifndef  _LOGGER_H_
#define  _LOGGER_H_

/*
 * logger level
 */
enum
{
    LOG_LEVEL_ERROR,
    LOG_LEVEL_WARN,
    LOG_LEVEL_INFO,
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_MAX,
};

/*
 * logger prefix string for different logging levels
 */
#define LOG_STRING_ERROR  "ERROR"
#define LOG_STRING_WARN   "WARN "
#define LOG_STRING_INFO   "INFO "
#define LOG_STRING_DEBUG  "DEBUG"


/*
 * logger initial and terminate functions
 */
int logger_init(char *filename, int loglevel);
void logger_term(void);

/*
 * logging methods by levels
 */
void log_error(char* format, ...);
void log_warn(char* format, ...);
void log_info(char* format, ...);
void log_debug(char* format, ...);

static void check_and_rollback(void);

#endif   /* ----- #ifndef _LOGGER_H_  ----- */

三、主程序内容

1.客户端主程序

main.c

/*********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  main.c
 *    Description:  This file is main.c
 *                 
 *        Version:  1.0.0(2022年11月18日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月18日 20时06分03秒"
 *                 
 ********************************************************************************/

#include "main.h"

int main(int argc, char **argv)
{
	/*getaddrinfo*/
	int                 getaddrfd = -1;  //定义getaddrinfo函数返回值
	struct addrinfo     ainfo;    //定义一个结构体
	struct addrinfo     *res;     //定义函数返回的结构体链表的指针
	struct addrinfo     *hand;    //定义一个遍历链表的指针
	struct sockaddr_in  *seraddr;   //定义一个存储返回域名IP信息的结构体指针
	/*ip or domain*/
	char				*servip = NULL;
	char				*servdn = NULL;
	/*socket*/
	int					sockfd = -1;
	int					rv = -1;
	struct sockaddr_in	servaddr;//我们是ipv4
	int					port = 0;
	/*time*/
	char				datime[100];
	long				T1=0;
	long				T2=0;
	/*excute*/
	sqlite3				*db;
	char				SN[16];
	char				buf[1024];
	float				temp;
	char				tstr[]="temperature: ";
	/*getopt*/
	int					time = 10;
	int					daemon_run = 0;
	int					ch;
	char				str[64];
	struct option		opts[] = {
		{"ipaddr", required_argument, NULL, 'i'},
		{"domain name", required_argument, NULL, 'd'},
		{"port", required_argument, NULL, 'P'},
		{"time", required_argument, NULL, 't'},
		{"daemon", no_argument, NULL, 'b'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	while( (ch=getopt_long(argc, argv, "i:d:p:t:bh", opts, NULL)) != -1)//h不用加参数,所以后面不加":"
	{
		switch(ch)
		{
			case 'i':
				servip=optarg;
				break;
			case 'd':
				servdn=optarg;
				break;
			case 'p':
				port=atoi(optarg);
				break;
			case 't':
				time=atoi(optarg);
				break;
			case 'b':
				daemon_run=1;
				break;
			case 'h':
				print_usage(argv[0]);
				return 0;
		}

	}

	if( !(!servip ^ !servdn) || !port )//^两边的值不同为1,相同为0
	{
		print_usage(argv[0]);
		return 0;
	}

	if(!servip)
	{
		domain_getaddrinfo(&servdn);
		servip=servdn;
		log_info("%s",servdn);
	}

	if(logger_init("running.log",LOG_LEVEL_DEBUG)<0)
	//if(logger_init("stdout",LOG_LEVEL_DEBUG)<0)
	{
		fprintf(stderr, "initial logger system failure\n");
		dbg_print("initial logger system failure\n");
		return -1;
	}
	
	/*set signal */
	signal(SIGINT, stop);
	signal(SIGTERM, stop);

	/*daemon*/
	if(daemon_run)
	{
		daemon(0, 0);
		log_info("running daemon\n");
	}
	
	//get_sn(SN, sizeof(SN));
	Create_Database(&db);
	Create_Table(db);

	sockfd=socket_and_connect(servip, port);
	
	/*excute*/
	while(!pro_stop)//当它为0时就执行
	{
		rv = get_temperature(&temp);
		if(rv<0)
        {
            log_error("get temperature failure, return value: %d", rv);
            break;
        }
		
		get_time(datime, sizeof(datime));

		get_sn(SN, sizeof(SN));

		memset(buf, 0, sizeof(buf));
		snprintf(buf, 1024, "%s/%s/%f\n", SN, datime, temp);
		dbg_print("full data: %s\n", buf);
		while(!pro_stop)
		{
			sleep(2);
			if(socket_connect_state(sockfd)<0)
			{
				dbg_print("sockfd[%d] disconnected!\n", sockfd);
				log_error("sockfd[%d] disconnected!\n", sockfd);
				if(sockfd > 0)
				{
					close(sockfd);
					log_warn("sockfd[%d] close\n", sockfd);
				}
				sockfd = socket_and_connect(servip, port);
				log_info("socket connect again\n"); 
			}

			T2 = get_time(datime, sizeof(datime));
			dbg_print("T2:%ld\n", T2);
			
			if((T2 - T1) > time)
			{
				log_info("time reached\n");
				//snprintf(str,36,"%s%f\n",tstr,temp);
			
				if(socket_connect_state(sockfd)<0)
				{
					Insert_Table(db, SN, datime, temp);
					break;
				}

				rv = write(sockfd, buf, strlen(buf));
				if(rv < 0)
				{
						log_error("write to server by sockfd[%d] failure : %s\n",
								sockfd, strerror(errno));
						Insert_Table(db, SN, datime, temp);
						break;
				}
				log_info("write to server by sockfd[%d] successfully\n",sockfd);

				T1 = T2;
	
				memset(buf, 0, sizeof(buf));
				
				break;
			}
			else
			{
				log_info("time not reached \n");
				
				rv = Table_check_write_clean(db, sockfd, SN);
				if(rv < 0)
				{
					log_warn("table_check_write_clean error\n");
					continue;
				}
				log_info("send all data of table and clean table\n");

				sleep(1);

				continue;
			}
		}
	}
	close(sockfd);
}

void stop(int signum)
{
    pro_stop = 1;
}

2.服务器端主程序

main.c

/*********************************************************************************
 *      Copyright:  (C) 2022 Deng Yonghao<dengyonghao2001@163.com>
 *                  All rights reserved.
 *
 *       Filename:  main.c
 *    Description:  This file server main 
 *                 
 *        Version:  1.0.0(2022年11月12日)
 *         Author:  Deng Yonghao <dengyonghao2001@163.com>
 *      ChangeLog:  1, Release initial version on "2022年11月12日 20时53分47秒"
 *                 
 ********************************************************************************/

#include "main.h"

int main(int argc, char **argv)
{
	int					listenfd, connfd;
	int					serv_port = 0;
	int					daemon_run = 0;
	char				*progname = NULL;
	int					opt;
	int					rv;
	int					i, j;
	int					found;
	char				buf[1024];
	int					epollfd;
	struct				epoll_event event;
	struct				epoll_event event_array[MAX_EVENTS];
	int					events;
	sqlite3				*db;
	char				*SN;
	float				temp;
	char				*datime;

	struct		option long_options[] =
	{ 
		{"port", required_argument, NULL, 'p'},
		{"daemon", no_argument, NULL, 'b'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	}; 

	progname = basename(argv[0]);

	/* Parser the command line parameters */
	while ((opt = getopt_long(argc, argv, "p:bh", long_options, NULL)) != -1)
	{ 
		switch (opt)
		{ 
			case 'p':
				serv_port = atoi(optarg);
				break; 
			case 'b':
                daemon_run=1;
                break;
			case 'h': /* Get help information */
				print_usage(progname);
				return EXIT_SUCCESS;
			default:
				break;
		} 
	} 
	if( !serv_port )
	{ 
		print_usage(progname);
		return -1;
	}
	if(logger_init("server_run.log",LOG_LEVEL_DEBUG)<0)
	//if(logger_init("stdout",LOG_LEVEL_DEBUG)<0)
	{
		fprintf(stderr, "initial logger system failure\n");
		dbg_print("initial logger system failure\n");
		return -2;
	}
	
	set_socket_rlimit(); /* set max open socket count */
	
	signal(SIGINT, stop);
	signal(SIGTERM, stop);	

	if( (listenfd=socket_server_init(NULL, serv_port)) < 0 )
	{
		printf("ERROR: %s server listen on port %d failure\n", argv[0],serv_port);
		return -3;
	}
	printf("%s server start to listen on port %d\n", argv[0],serv_port);

	/* set program running on background */
	if( daemon_run )
	{
		daemon(0, 0);
	}

	Create_Database(&db);

	if( (epollfd=epoll_create(MAX_EVENTS)) < 0 )
	{
		printf("epoll_create() failure: %s\n", strerror(errno));
		return -3;
	}

	//event.events = EPOLLIN|EPOLLET;
	event.events = EPOLLIN;
	event.data.fd = listenfd;
	if( epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0)
	{
		printf("epoll add listen socket failure: %s\n", strerror(errno));
		return -4;
	}
	while(!pro_stop)
	{
		/* program will blocked here */
		events = epoll_wait(epollfd, event_array, MAX_EVENTS, -1);
		if(events < 0)
		{
			printf("epoll failure: %s\n", strerror(errno));
			break;
		}
		else if(events == 0)
		{
			printf("epoll get timeout\n");
			continue;
		}
	
		/* rv>0 is the active events count */
		for(i=0; i<events; i++)
		{
			if ( (event_array[i].events&EPOLLERR) || (event_array[i].events&EPOLLHUP) )
			{
				printf("epoll_wait get error on fd[%d]: %s\n", event_array[i].data.fd, strerror(errno));
				epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
				close(event_array[i].data.fd);
			}
		
			/* listen socket get event means new client start connect now */
			if( event_array[i].data.fd == listenfd )
			{
				if( (connfd=accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0)
				{
					printf("accept new client failure: %s\n", strerror(errno));
					continue;
				}
				event.data.fd = connfd;
		
				//event.events = EPOLLIN|EPOLLET;
				event.events = EPOLLIN;
				if( epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &event) < 0 )
				{
					printf("epoll add client socket failure: %s\n", strerror(errno));
					close(event_array[i].data.fd);
					continue;
				}
				printf("epoll add new client socket[%d] ok.\n", connfd);
			}
			else /* already connected client socket get data incoming */
			{
				memset(buf, 0, sizeof(buf));
				if( (rv=read(event_array[i].data.fd, buf, sizeof(buf))) <= 0)
				{
					printf("socket[%d] read failure or get disconncet and will be removed.\n",
							event_array[i].data.fd);
					epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
					close(event_array[i].data.fd);
					continue;
				}
				else
				{
					printf("socket[%d] read get %d bytes data\n", event_array[i].data.fd, rv);
					
					SN = strtok(buf, "/");
					log_info("SN: %s", SN);
					datime = strtok(NULL, "/");
					log_info("datime: %s", datime);
					temp = atof(strtok(NULL, "\n"));
					log_info("temp: %f", temp);

					Create_Table(db);
					Insert_Table(db, SN, datime, temp);
				}
			}
		} /* for(i=0; i<rv; i++) */
	} /* while(1) */
CleanUp:
	close(listenfd);
	return 0;
}

void stop(int signum)
{
    pro_stop = 1;
}

四、总结

本项目通过树莓派实现DS18B20温度实时监控项目,所用到了Socket通信、多路复用、数据库、日志系统等知识与内容,做本篇博客来对此项目进行一个记录。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓永豪

打赏一下,好运来敲门!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值