C/C++15天气APP:服务端(client.cpp,shtqappserver.cpp)
Posted 码农编程录
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++15天气APP:服务端(client.cpp,shtqappserver.cpp)相关的知识,希望对你有一定的参考价值。
文章目录
1.APP需求和架构:图片用linux下工具imagemagick用system函数调用,多进程,nextval
多进程用信号灯加锁麻烦,一般用多线程并加锁。数据库连接也是socket服务端,不可能让多个进程同时写如事务
。下面是多个线程可共用一个socket连接(协调单个发)
,但不能说多个线程共享一个socket连接(一起发)
,一个socket连接同一时间只能有一个线程使用,用完后提交事务,连接才释放出来。
如下开始APP服务端设计,客户端就是手机app软件。第一次客户端将手机编号
传给服务端,服务端将站点信息
传给客户端。
短连接
:客户端即用户点击按钮一次建立一次socket连接请求,处理完一个就断开
。响应慢:建立一次socket连接费时间,服务端fork一个进程也要时间,之后和数据库连接也要时间。
长连接
:客户端与服务端socket一直连接着进行数据通信,没有数据通信时用心跳(之前文件传输都用的是长连接)
,用户关了app,连接才断开。费服务端资源
:长连接连上后,数据库连接和进程都已准备好,一直通信完才断开。响应快
:用户看到数据越快越好控制在1秒内。如下项目组织(shtqapp是一个独立的项目)。
如下第一行是上面创建用户sql,pdm文件是数据结构设计。
如下不需收集用户密码,T_USERINFO表
和T_OBTCODE表
放入shtqapp数据库用户里,T_USERLOG表
放入shappdata数据库用户里。
如下preview的sql语句是建表。
以下是多进程,正式上线时用http80端口(用户在某个网络内部80端口肯定开放,不开放外网上不了,qq微信虽不是用http协议但也采用80端口)。
// client.cpp,模拟tcp手机客户端,客户端用短链接还是长连接由客户端自己安排
#include "_freecplus.h"
CTcpClient TcpClient;
char strSendBuffer[301],strRecvBuffer[301];
bool biz10000(); // 心跳
bool biz10001(); // 新用户登录:只传个设备编号id,服务端把城市站点信息传给客户端,手机利用定位匹配
int main(int argc,char *argv[])
//if (TcpClient.ConnectToServer("127.0.0.1",5015)==false) printf("conn failed.\\n"); return -1;
if (TcpClient.ConnectToServer("172.16.0.15",5015)==false) printf("conn failed.\\n"); return -1;
//if (TcpClient.ConnectToServer("118.89.50.198",5015)==false) printf("conn failed.\\n"); return -1;
if (biz10000()==false) return 0; // 心跳
CTimer Timer;
if (biz10001()==false) return 0; // 新用户登录
printf("biz10001=%lf\\n",Timer.Elapsed());
sleep(1);
return 0;
bool biz10000()
memset(strSendBuffer,0,sizeof(strSendBuffer));
memset(strRecvBuffer,0,sizeof(strRecvBuffer));
strcpy(strSendBuffer,"<bizid>10000</bizid>");
//printf("send=%s=\\n",strSendBuffer);
if (TcpClient.Write(strSendBuffer)==false) printf("send failed.\\n"); return false;
if (TcpClient.Read(strRecvBuffer,20)==false) printf("recv failed.\\n"); return false;
//printf("recv=%s=\\n",strRecvBuffer);
return true;
bool biz10001()
memset(strSendBuffer,0,sizeof(strSendBuffer));
memset(strRecvBuffer,0,sizeof(strRecvBuffer));
// 如下请求报文
strcpy(strSendBuffer,"<bizid>10001</bizid><userid>52:54:00:83:0f:c1</userid><ttytype>1</ttytype><lat>20.234518</lat><lon>115.90832</lon><height>150.5</height>");
//printf("send=%s=\\n",strSendBuffer);
if (TcpClient.Write(strSendBuffer)==false) printf("send failed.\\n"); return false;
//如下用一个循环接收全部的站点信息
while (1)
memset(strRecvBuffer,0,sizeof(strRecvBuffer));
if (TcpClient.Read(strRecvBuffer,20)==false) printf("recv failed.\\n"); return false;
// printf("recv=%s=\\n",strRecvBuffer); //手机端没数据库,手机软件真正处理方法把数据保存到xml文件里
if (strcmp(strRecvBuffer,"ok")==0) break; //接收到ok的话表示数据处理完了
return true;
// 上海天气APP软件服务端主程序shtqappserver.cpp多进程。
#include "_public.h"
#include "_ooci.h"
struct st_biz // 业务请求
int bizid; // 业务代码
char userid[51]; // 设备ID
int ttytype; // 用户的设备类型,0-未知;1-ios;2-Andriod,2-鸿蒙。
int usertype; // 用户分类,0-未知;1-普通用户;2-气象志愿者;3-内部用户。
double lon;
double lat;
double height;
char obtid[11];
char xmlbuffer[1001];
stbiz;
void xmltobiz(char *strxmlbuffer); // 把xml解析到参数stbiz结构中
CTcpServer TcpServer;
CLogFile logfile;
connection conn;
//如上定义为全局变量共享但不会在main函数主进程里连数据库,在子进程里连。如果在主进程连,子进程用是不行的
//conn若定义为局部变量,ChldEXIT()中conn不会调用析构函数,全局会调用释放资源。
//CTcpServer析构函数会自动调用CloseClient()方法关闭socket连接
char strRecvBuffer[TCPBUFLEN+10]; // 接收报文的缓冲区
char strSendBuffer[TCPBUFLEN+10]; // 发送报文的缓冲区
void FathEXIT(int sig); // 程序退出时调用的函数
void ChldEXIT(int sig);
void ChldMain(); // 业务处理进程主函数
bool biz10000(); // 心跳业务
bool biz10001(); // 新用户登录业务,用户基本信息表和用户使用日志都插入一条记录,select全国气象站点数据返回给客户端
bool biz10002(); // 获取天气实况
bool InsertUSERLOG(); // 插入用户请求日志表
int main(int argc,char *argv[])
if (argc != 3)
printf("\\n");
printf("Using:/htidc/shtqapp/bin/shtqappserver logfilename port\\n");
printf("Example:/htidc/shtqapp/bin/shtqappserver /log/shtqapp/shtqappserver.log 5015\\n\\n");
printf("本程序是上海天气APP软件的服务端。\\n");
printf("logfilename 日志文件名。\\n");
printf("port 用于传输文件的TCP端口。\\n");
return -1;
CloseIOAndSignal(); signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);
if (logfile.Open(argv[1],"a+",false) == false) // 打开程序运行日志,这是一个多进程程序,日志不能自动切换
printf("logfile.Open(%s) failed.\\n",argv[1]); return -1;
logfile.Write("shtqappserver started(%s).\\n",argv[2]);
if (TcpServer.InitServer(atoi(argv[2])) == false)
logfile.Write("TcpServer.InitServer(%s) failed.\\n",argv[2]); exit(-1);
while (true)
if (TcpServer.Accept() == false) // 等待客户端的连接
logfile.Write("TcpServer.Accept() failed.\\n"); continue;
if (fork() > 0) // 新的客户端连接来fork一个进程
TcpServer.CloseClient(); continue; //父进程关闭刚建立起来的sock连接,并回到Accept继续监听
signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT); //进入子进程的流程
TcpServer.CloseListen(); //子进程需要关掉监听的sock
ChldMain(); //子进程业务处理进程主函数
ChldEXIT(0);
return 0;
// 父进程退出时调用的函数
void FathEXIT(int sig)
if (sig > 0) //如果收到信号就屏蔽信号
signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
logfile.Write("catching the signal(%d).\\n",sig);
kill(0,15); //通知全部子进程退出
logfile.Write("shtqappserver EXIT.\\n");
exit(0);
// 子进程退出时调用的函数
void ChldEXIT(int sig)
if (sig > 0) //如果不屏蔽的话会收到两个退出信号
signal(sig,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);
exit(0);
//11111111111111111111111111111111111111111111111111业务处理进程主函数
void ChldMain()
while (true)
memset(strRecvBuffer,0,sizeof(strRecvBuffer));
memset(strSendBuffer,0,sizeof(strSendBuffer));
// 接收客户端的业务请求报文,如果返回false,认为是客户端退出或网络原因,直接return不写错误日志
if (TcpServer.Read(strRecvBuffer,50) == false)
// 比如用户手机app回到桌面,程序挂起不需要app体面退出,Read失败很多不用写日志
// logfile.Write("TcpServer.Read() failed.\\n");
return;
logfile.Write("strRecvBuffer=%s\\n",strRecvBuffer); // xxxxxx
// 把参数解析出来,客户端与服务端通信全用xml,每种业务请求弄一个业务编号
xmltobiz(strRecvBuffer);
if (stbiz.bizid==10000) // 心跳报文
if (biz10000()==true) continue;
else return;
CTimer Timer;
// 连接数据库,第三个参数为true自动提交
if (conn.connecttodb("shtqapp/pwdidc@snorcl11g_198","Simplified Chinese_China.ZHS16GBK",true)!=0)
logfile.Write("conn.connettodb() failed.\\n"); return;
logfile.Write("conn=%lf\\n",Timer.Elapsed());
// 新用户登录
if (stbiz.bizid==10001)
if (biz10001()==true) continue;
else return;
// 获取天气实况
if (stbiz.bizid==10002)
if (biz10002()==true) continue;
else return;
// 协议:你发什么给我,我发什么给你。报文格式不符合要求的话直接return
logfile.Write("非法报文%s\\n",strRecvBuffer); return;
//11111111111111111111把xml解析到参数starg结构中,客户端全部请求放入starg数据结构里
void xmltobiz(char *strxmlbuffer)
memset(&stbiz,0,sizeof(struct st_biz));
// 客户端发给服务端全部报文都会有个业务代码
GetXMLBuffer(strxmlbuffer,"bizid",&stbiz.bizid);
// logfile.Write("bizid=%d\\n",stbiz.bizid);
// 用户设备ID
GetXMLBuffer(strxmlbuffer,"userid",stbiz.userid,50);
// logfile.Write("userid=%s\\n",stbiz.userid);
GetXMLBuffer(strxmlbuffer,"obtid",stbiz.obtid,10);
// logfile.Write("obtid=%s\\n",stbiz.obtid);
GetXMLBuffer(strxmlbuffer,"lat",&stbiz.lat);
// logfile.Write("lat=%lf\\n",stbiz.lat);
GetXMLBuffer(strxmlbuffer,"lon",&stbiz.lon);
// logfile.Write("lon=%lf\\n",stbiz.lon);
GetXMLBuffer(strxmlbuffer,"height",&stbiz.height);
// logfile.Write("height=%lf\\n",stbiz.height);
strncpy(stbiz.xmlbuffer,strxmlbuffer,1000);
return;
//111111111111111111111111111111111111111111心跳业务
bool biz10000()
memset(strSendBuffer,0,sizeof(strSendBuffer));
strcpy(strSendBuffer,"ok");
if (TcpServer.Write(strSendBuffer) == false)
logfile.Write("biz10000 TcpServer.Write() failed.\\n"); return false;
return true;
//1111111111111111111111111111111111111111111111新用户登录
bool biz10001()
CTimer Timer;
//1111111111111111 插入用户基本信息表T_USERINFO:设备ID和设备类型
sqlstatement stmt(&conn);
stmt.prepare("insert into T_USERINFO(userid,downtime,ttytype,keyid) values(:1,sysdate,:2,SEQ_USERINFO.nextval)");
stmt.bindin(1, stbiz.userid,50);
stmt.bindin(2,&stbiz.ttytype);
if (stmt.execute() != 0)
if (stmt.m_cda.rc!=1) //出现主键冲突不管(之前下载过,表里留下了数据,所以会出现主键冲突)
logfile.Write("insert T_USERINFO failed.\\n%s\\n%s\\n",stmt.m_cda.message,stmt.m_sql); return false;
logfile.Write("insert T_USERINFO =%lf\\n",Timer.Elapsed());
//11111111111111111111111111111111插入用户请求日志表
if (InsertUSERLOG()==false) return false; // 是模块通用功能,所以写成函数
logfile.Write("insert T_USERLOG =%lf\\n",Timer.Elapsed());
//1111111111111111111111111111111拿出全国站点参数信息返回给客户端
char strobtid[6],strobtname[31],strlon[11],strlat[11];
stmt.prepare("select obtid,obtname,lon,lat from T_OBTCODE where rsts=1 and rownum<=30");
stmt.bindout(1,strobtid,5);
stmt.bindout(2,strobtname,30);
stmt.bindout(3,strlon,10);
stmt.bindout(4,strlat,10);
if (stmt.execute() != 0)
logfile.Write("select T_OBTCODE failed.\\n%s\\n%s\\n",stmt.m_cda.message,stmt.m_sql); return false;
while (true)
memset(strobtid,0,sizeof(strobtid));
memset(strobtname,0,sizeof(strobtname));
memset(strlon,0,sizeof(strlon));
memset(strlat,0,sizeof(strlat));
memset(strSendBuffer,0,sizeof(strSendBuffer));
if (stmt.next()!=0) break;
//获得一条记录后生成一个buffer写过去
sprintf(strSendBuffer,"<obtid>%s</obtid><obtname>%s</obtname><lon>%s</lon><lat>%s</lat><endl/>",strobtid,strobtname,strlon,strlat);
if (TcpServer.Write(strSendBuffer) == false)
logfile.Write("biz10001 TcpServer.Write() failed.\\n"); return false;
logfile.Write("select =%lf\\n",Timer.Elapsed());
// 直到全部记录被处理完,最后发送一个ok
strcpy(strSendBuffer,"ok"以上是关于C/C++15天气APP:服务端(client.cpp,shtqappserver.cpp)的主要内容,如果未能解决你的问题,请参考以下文章
C/C++7天气APP:生成观测数据txt/xml文件(crtsurfdata.cpp),ftp协议及ftp采集模块(_ftp.h,_ftp.cpp,ftpgetfiles.cpp)
C/C++12天气APP:不同数据建表入表,数据交换(exptables.cpp,ftpputfiles.cpp)
C/C++10天气APP:MySQL,PostgreSQL,环境变量,动静态库,Linux/Oracle字符集
C/C++9天气APP:Oracle的虚表/日期/序列,索引/视图/链路/同义词,数据库高可用性
C/C++14天气APP:文件传输系统(tcpput/getfile.cpp客户端,tcpfileserver.cpp)
C/C++11天气APP:txt/xml文件处理入库(psurfdata.cpp,_shqx.h),数据结构设计(PowerDesigner)