温度实时监控上报服务器端,采用多线程以及互斥锁来实现多个客户端并发访问。
1 ,通过命令行指定监听的端口;
2,程序放到后台运行,并通过syslog记录程序的运行出错、调试日志;
3, 程序能够捕捉kill信号正常退出;
4, 服务器要支持多个客户端并发访问;
5, 服务器收到每个客户端的数据都解析后保存到数据库中,接收到的数据格式为: “ID/时间/温度”,如RPI0001/2019-0105 11:40:30/30.0C”;
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "sqlite3.h"
#define BACKLOG 13
//函数声明,打印帮助信息;捕捉信号并结束程序
void print_usage(char* progname);
void sig_stop(int signum);
//定义一个全局变量g_stop ,与sig_stop()函数配合,达到结束程序的目的
int g_stop = 0;
void* thread_worker(void* args);
typedef struct worker_ctx_s
{
pthread_mutex_t lock;
sqlite3 *db;
int *client_fd;
}worker_ctx_t;
int main(int argc, char* argv[])
{
int rv;
int ret;
int opt;
int idx;
int port;
int log_fd;
int ch = 1;
int daemon_run = 0;
int ser_fd = -1;
int cli_fd = -1;
pid_t pid = -1;
struct sockaddr_in ser_addr;
struct sockaddr_in cli_addr;
socklen_t cliaddr_len = 20;
worker_ctx_t worker_ctx;
pthread_t tid;
pthread_attr_t thread_attr;
char* zErrMsg = NULL;
char sql1[128];
//描述创建数据库中表的信息
char* sql = "create table if not exists temperature(sn char(10), datetime char(50), temperature char(10))";
worker_ctx.db = NULL;
worker_ctx.client_fd = &cli_fd;
pthread_mutex_init(worker_ctx.lock, NULL);//锁的初始化
/*命令行参数解析
deamon:程序后台运行
port:指定服务器开设端口
help:打印帮助信息
*/
struct option opts[] = {
{"daemon", no_argument, NULL, 'd'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ((opt = getopt_long(argc, argv, "dp:h", opts, &idx)) != -1)
{
switch (opt)
{
case 'd':
daemon_run = 1;
break;
case 'p':
port = atoi(optarg); //将字符串形式的端口号转换成整型
break;
case 'h':
print_usage(argv[0]); //打印帮助信息
return 0;
}
}
//判断是否指定端口或正确端口
if (!port)
{
print_usage(argv[0]);
return 0;
}
if (daemon_run)
{
printf("Program %s is running at the background now\n", argv[0]);
//建立日志系统
log_fd = open("receive_temper.log", O_CREAT | O_RDWR, 0666);
if (log_fd < 0)
{
printf("Open the logfile failure : %s\n", strerror(errno));
return 0;
}
//标准输出、标准出错重定向
dup2(log_fd, STDOUT_FILENO);
dup2(log_fd, STDERR_FILENO);
//程序后台运行
if ((daemon(1, 1)) < 0)
{
printf("Deamon failure : %s\n", strerror(errno));
return 0;
}
}
//安装SIGUSR1信号
signal(SIGUSR1, sig_stop);
//创建socket_fd,以供后续使用
//第一个参数为协议域,这里使用AF_INET(32位ipv4地址、16位端口)
//第二个参数为type,指定socket类型,这里使用SOCK_STREAM
//第三个参数为指定协议,当其为0时,自动选择与type类型相对应的默认协议
ser_fd = socket(AF_INET, SOCK_STREAM, 0);
if (ser_fd < 0)
{
printf("Creat socket failure:%s\n", strerror(errno));
return 0;
}
printf("Creat the ser_fd[%d]!\n", ser_fd);
//设置服务器地址信息
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(port); //调用htons(),将端口号由主机字节序转变成网络字节序
//设置服务器IP地址(INADDR_ANY,代表监听所有IP),并调用htonl(0,将其由主机字节序转变成网络字节序
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//解决服务器程序停止后立刻运行,出现(端口)被占用的问题
setsockopt(ser_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&ch, sizeof(ch));
//绑定服务器地址(IP+端口),用于提供服务
rv = bind(ser_fd, (struct sockaddr*) & ser_addr, sizeof(ser_addr));
if (rv < 0)
{
printf("Bind server's port failure:%s\n", strerror(errno));
return 0;
}
printf("The port[%d] has been bind by this server!\n", port);
//调用listen(),将主动类型的socket套接字变为被动类型(等待客户端连接)
//第一个参数为sockfd,指定所要监听的socket套接字,用于接收外部请求
//第二个参数为backlog, TCP连接是一个过程,内核会在进程空间中维护一个连接队列以跟踪与服务器建立连接但还未着手处理的连接
listen(ser_fd, BACKLOG);
len = sqlite3_open("temper.db", &db);
if (len)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
else
{
printf("You have opened a sqlite3 database named temper.db successfully!\n");
}
//创建temperature表,用来存放来自客户端的数据
ret = sqlite3_exec(db, sql, 0, 0, &zErrMsg);
if (ret != SQLITE_OK)
{
sqlite3_close(db);
printf("Creat table failure \n");
return 0;
}
printf("Create table successfully!\n");
printf("Child process start to communicate with client by cli_fd[%d]...\n", cli_fd);
printf("Now ready to connect the client and recive message from client...\n");
printf("\n");
sqlite3_close(db);
if (pthread_attr_init(&thread_attr))
{
printf("pthread_attr_init() failure:%s\n", strerror(errno));
goto cleanup;
}
if (pthread_attr_setdetachstate(&thread_attr, PTHREADD_CREATE_DETACHED))
{
printf("pthread_attr_setdetachstate() failure:%s\n", strerror(errno));
goto cleanup;
}
while (!g_stop)
{
//接受来自客户端的请求,并创建一个cli_fd与客户端进行连接
cli_fd = accept(ser_fd, (struct sockaddr*) & cli_addr, &cliaddr_len);
if (cli_fd < 0)
{
printf("Accept the request from client failure:%s\n", strerror(errno));
continue;
}
if (pthread_create(&tid, &thread_attr, thread_worker, &worker_ctx))
{
printf("create thread failure:%ss\n", strerror(errno));
goto cleanup;
}
}
cleanup:
pthread_attr_destory(&thread_attr);
close(ser_fd);
return 0;
}
void* thread_worker(void* ctx)
{
int a;
int len;
char* sn;
char* cut;
char* temp;
char buf[512];
char* datetime;
char sql1[128];
//描述创建数据库中表的信息
char* sql = "create table if not exists temperature(sn char(10), datetime char(50), temperature char(10))";
worker_ctx_t* ctx1 = (worker_ctx_t*)ctx;
if (!args)
{
printf("get innvalid arguements\n");
pthread_exit(NULL);
}
pthread_mutex_lock(&ctx->lock);//申请锁,这里是阻塞锁,如果锁被别的线程持有则不会返回
//调用sqlite3_open(),打开.db数据库文件。若没有,则创建
len = sqlite3_open("temper.db", &db);
if (len)
{
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
else
{
printf("You have opened a sqlite3 database named temper.db successfully!\n");
}
memset(buf, 0, sizeof(buf));
a = read(*client_fd, buf, sizeof(buf)); //从buf中读数据(客户端写入)
if (a < 0) //返回值小于零,则表示接收数据出错
{
printf("Read information from client failure:%s\n", strerror(errno));
close(*client_fd);
exit(0);
}
else if (a == 0) //返回值等于0,则表示客户端主动断开连接
{
printf("The connection with client has broken!\n");
close(*client_fd);
exit(0);
}
else //返回值大于零,则表示接收数据成功
{
printf("The message recived from client[%s,%d] is \"%s\"\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
//对接收到的数据进行分割操作,便于存储至数据库
cut = strtok(buf, "/");
if (cut != NULL)
{
sn = cut;
//printf("%s\n", sn);
if ((cut = strtok(NULL, "/")) != NULL)
{
datetime = cut;
//printf("%s\n", datetime);
if ((cut = strtok(NULL, "/")) != NULL)
{
temp = cut;
//printf("%s\n", temp);
}
}
}
snprintf(sql1, 128, "insert into temperature values('%s', '%s', '%s');",sn, datetime, temp);
sql1[127] = '\0';
printf("%s\n", sql1);
//将数据存储至temperature表中
ret = sqlite3_exec(db, sql1, 0, 0, &zErrMsg);
if (ret != SQLITE_OK)
{
sqlite3_close(db);
printf("insert data failure ; %s!\n", zErrMsg);
return 0;
}
printf("insert data successfully!\n");
printf("\n");
close(*client_fd);
pthread_mutex_unlock(&ctx->lock);
sleep(1);
return;
}
//打印帮助信息函数
void print_usage(char* progname)
{
printf("-d(--daemon):let program run in the background.\n");
printf("-p(--port):enter server port.\n");
printf("-h(--help):print this help information.\n");
return;
}
//回调函数sig_stop()
void sig_stop(int signum)
{
if (SIGUSR1 == signum) / 判断捕捉到信号是否为SIGUSR1
{
g_stop = 1; //g_stop为1,while(!g_stop)循环结束,程序关闭
}
return;
}