学习计算机网络编程

一、思路和学习方法

  本文学习于:C语言技术网(www.freecplus.net),在 b 站学习于 C 语言技术网,并加以自己的一些理解和复现,如有侵权会删除。
  接下来对网络编程继续深入学习。

二、网络编程继续深入

2.1 搭建多进程网络服务框架

  前置学习需要理解 linux 网络编程基础,linux 多线程,多进程的相关知识和使用。然后搭建一个多进程网络服务的框架。建立一个多进程的 socket 服务端,然后多个客户端都能和服务端进行通信。涉及到的是信号,多进程的相关知识。其实现原理是,服务端当接受到一个服务端的连接时,建立一个进程,对服务端进行服务,那么这个服务端就可以响应多个客户端
  这里程序的框架主要是在之前的封装以后的服务端和客户端 CTcpServer.cpp 和 CTcpClient.cpp 中改写,注意在服务端加入这两句,就能够对多个客户端进行响应。CTcpServer.cpp 代码如下,

/** 程序名:book248.cpp,此程序用于演示用C++的方法封装socket服务端* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>class CTcpServer
{public:int m_listenfd;   // 服务端用于监听的socketint m_clientfd;   // 客户端连上来的socketCTcpServer();bool InitServer(int port);  // 初始化服务端bool Accept();  // 等待客户端的连接// 向对端发送报文int  Send(const void *buf,const int buflen);// 接收对端的报文int  Recv(void *buf,const int buflen);~CTcpServer();
};int main()
{signal(SIGCHLD, SIG_IGN); // 屏蔽子进程退出的信号,避免产生僵尸进程CTcpServer TcpServer;if (TcpServer.InitServer(5005)==false){ printf("TcpServer.InitServer(5051) failed,exit...\n"); return -1; }while(true){if (TcpServer.Accept() == false) { printf("TcpServer.Accept() failed,exit...\n"); return -1; }if(fork() > 0){close(TcpServer.m_clientfd); continue;} // 父进程返回到循环首部close(TcpServer.m_listenfd);// 以下是子进程,负责与客户端通信printf("客户端已连接。\n");char strbuffer[1024];while (1){memset(strbuffer,0,sizeof(strbuffer));if (TcpServer.Recv(strbuffer,sizeof(strbuffer))<=0) break;printf("接收:%s\n",strbuffer);strcpy(strbuffer,"ok");if (TcpServer.Send(strbuffer,strlen(strbuffer))<=0) break;printf("发送:%s\n",strbuffer);}printf("客户端已断开连接。\n");exit(0); // 子进程结束退出}
}CTcpServer::CTcpServer()
{// 构造函数初始化socketm_listenfd=m_clientfd=0;
}CTcpServer::~CTcpServer()
{if (m_listenfd!=0) close(m_listenfd);  // 析构函数关闭socketif (m_clientfd!=0) close(m_clientfd);  // 析构函数关闭socket
}// 初始化服务端的socket,port为通信端口
bool CTcpServer::InitServer(int port)
{m_listenfd = socket(AF_INET,SOCK_STREAM,0);  // 创建服务端的socket// 把服务端用于通信的地址和端口绑定到socket上struct sockaddr_in servaddr;    // 服务端地址信息的数据结构memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;  // 协议族,在socket编程中只能是AF_INETservaddr.sin_addr.s_addr = htonl(INADDR_ANY);  // 本主机的任意ip地址servaddr.sin_port = htons(port);  // 绑定通信端口if (bind(m_listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 ){ close(m_listenfd); m_listenfd=0; return false; }// 把socket设置为监听模式if (listen(m_listenfd,5) != 0 ) { close(m_listenfd); m_listenfd=0; return false; }return true;
}bool CTcpServer::Accept()
{if ( (m_clientfd=accept(m_listenfd,0,0)) <= 0) return false;return true;
}int CTcpServer::Send(const void *buf,const int buflen)
{return send(m_clientfd,buf,buflen,0);
}int CTcpServer::Recv(void *buf,const int buflen)
{return recv(m_clientfd,buf,buflen,0);
}

  需要注意的是添加的 while(true) 和 if(fork() < 0) continue; 还有 exit(0) 函数,exit() 在 #include <stdlib.h> 头文件中如果没有上述语句,在客户端和服务端对接一次后,就结束了,其结果如下

  加上述语句,服务端用多进程方式对服务端进行服务,结果如下

  这样就完成了最最基础的框架。但是还需要改进。程序中加如以下程序signal(SIGCHLD, SIG_IGN); ,注意加入头文件 #include <signal.h> 是屏蔽子进程退出的信号,避免产生僵尸进程,其中,可以用

ps -ef | grep CTcpServer查看僵尸进程,其中为 [CTcpServer] <defunct> 为僵尸进程。
僵尸进程就是子进程运行完,父进程没有回收资源,就叫僵尸进程,如果父进程结束,那子进程
也自然结束。

  还要关掉多余的 socket ,对于父进程来说,不用和客户端连接,要关掉 m_connfd 的 socket ;对于子进程来说,不用监听 socket ,要关掉 m_listened 的 socket ,因此加入程序,

if(fork() > 0){close(TcpServer.m_clientfd); continue;} // 父进程返回到循环首部close(TcpServer.m_listenfd);

2.2 多进程服务程序的退出和资源释放

  freecplus 框架具体使用,参考 up 主博客来学习,网址如下:
http://www.freecplus.net/9ebb8276265b404488a3415be224ed85.html

  用 Ctrl+C 关闭进程,是不合适,因此采用其他方法来完成。这里为了方便使用,把作者写的 freecplus 框架放入程序中,里面封装了通信的一般方法,直接调用就行,对于 TCP 通信方面可以查看前面的 TCP 封装类,和那里的是一样的。还有如何使用 freecplus 框架,可以看作者的网站,写的很清楚,我就不赘叙了。使用多进程服务程序的退出和资源释放,其中程序如下。
  下面是使用 freecplus 框架写的关于多进程服务的退出和资源释放,是服务端程序 ExitAndFreeServer.cpp,

/** 程序功能:服务端程序,多进程程序的退出和资源释放* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"CTcpServer TcpServer;  // 创建服务端对象// 程序退出时调用的函数
void FathEXIT(int sig);  // 父进程退出函数
void ChldEXIT(int sig);  // 子进程退出函数int main(int argc, char *argv[]){// 关闭全部的信号,也把僵尸进程关闭for(int ii = 0; ii <= 64; ii++) signal(ii, SIG_IGN);// 设置信号,在 shell 状态下可用 “kill + 进程号”正常终止些进程 Ctrl + c// 但请不要用 “kill -9 + 进程号”signal(SIGINT, FathEXIT); signal(SIGTERM, FathEXIT);// 初始化 TcpServer 的通信端口if(TcpServer.InitServer(5005) == false){printf("TcpServer.InitServer(5005) failed. \n"); FathEXIT(-1);}while(true){if(TcpServer.Accept() == false){  // 等待客户端连接printf("TcpServer.Accept() failed. \n"); continue;}// 父进程返回到循环首部if(fork() > 0){TcpServer.CloseClient(); continue; }// 子进程重新设置退出信号signal(SIGINT, ChldEXIT); signal(SIGTERM, ChldEXIT);TcpServer.CloseListen();// 以下是子进程,负责与客户端通信printf("客户端(%s)已连接。 \n", TcpServer.GetIP());char strbuffer[1024];  // 存放数据的缓冲区while(true){memset(strbuffer, 0, sizeof(strbuffer));// 接收客户端发过来的请求报文if(TcpServer.Read(strbuffer, 10) == false) break;printf("接收:%s \n", strbuffer);strcat(strbuffer, "ok"); // 在客户端的报文后加上“ok”printf("发送:%s \n", strbuffer);if(TcpServer.Write(strbuffer) == false)break; // 向客户端回应报文}printf("客户端已断开。 \n");  // 程序直接退出,析构函数会释放资源ChldEXIT(-1); // 通信完成后,子进程退出。}
}void FathEXIT(int sig){  // 父进程退出函数if(sig > 0){// 免除不再受到其他信号的打扰signal(sig, SIG_IGN);signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);printf("catching the signal(%d). \n", sig);}kill(0, 15);  // 通知其它的子进程退出。printf("父进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}void ChldEXIT(int sig){ // 子进程退出函数if(sig > 0){signal(sig, SIG_IGN);signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);}printf("子进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}

  下面是使用 freecplus 框架写的关于多进程客户端程序,程序文件名是 freecplusClient.cpp ,其中程序如下,

/** 程序名:book247.cpp,此程序用于演示用C++的方法封装socket客户端* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"CTcpClient TcpClient;int main(int argc, char *argv[]){// 向服务器发起连接请求if (TcpClient.ConnectToServer("127.0.0.1",5005) == false){ printf("TcpClient.ConnectToServer(\"127.0.0.1\",5005) failed,exit...\n"); return -1; }// fork(); // 子进程char strbuffer[1024];for (int ii=0;ii<5;ii++){memset(strbuffer,0,sizeof(strbuffer));snprintf(strbuffer,50,"%d: 这是第%d个超级女生,编号%03d。",getpid(),ii+1,ii+1);printf("发送:%s\n",strbuffer);if (TcpClient.Write(strbuffer) == false) break; // 向服务端发送请求报文memset(strbuffer,0,sizeof(strbuffer));if (TcpClient.Read(strbuffer,20) == false) break; // 接收服务端的回应报文printf("接收:%s\n",strbuffer);sleep(1);}
}

  其中 makefile 文件如下

all:ExitAndFreeServer freecplusClientExitAndFreeServer:ExitAndFreeServer.cppg++ -g -o ExitAndFreeServer ExitAndFreeServer.cpp _freecplus.cppfreecplusClient:freecplusClient.cppg++ -g -o freecplusClient freecplusClient.cpp _freecplus.cpp

  记住,使用查看进程的 linux 指令为,

ps -ef | grep ExitAndFree(文件名)

  然后让他们进行通信,两个客户端与服务端通信,然后观察运行效果,

  当使用 ps 查看进程号,然后用 kill 杀死进程时候,服务端程序显示如下,

  当使用 ctrl + c 结束进程时,显示如下,

  可以看出,是成功完成上述要求功能的。

2.3 多进程服务程序的日志

  日志模块对于一个程序而言是必不可少的,对于服务端程序更重要。因为几乎所有服务端程序都是作为守护进程而运行的,依靠日志记录关键信息就很重要了,当然日志的作用远不止记录信息,它还可以用于恢复。本文中,依旧使用作者的 freecplus 开发框架,注意 CLogFile logfile 已经封装在 _freecplus.h 里面,具体的可以查看源代码,其中服务端程序为,

/** 程序功能:* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"CLogFile logfile;
CTcpServer TcpServer;  // 创建服务端对象// 程序退出时调用的函数
void FathEXIT(int sig);  // 父进程退出函数
void ChldEXIT(int sig);  // 子进程退出函数int main(int argc, char *argv[]){if(argc != 3){printf("Using:./ExitAndFreeServer port logfile\nExample:./ExitAndFreeServer 5005 /tmp/ExitAndFreeServer.log\n\n"); return -1;}// 关闭全部的信号,也把僵尸进程关闭for(int ii = 0; ii <= 64; ii++) signal(ii, SIG_IGN);// 打开日志文件if(logfile.Open(argv[2], "a+") == false){printf("logfile.OPen(%s) failed.\n", argv[2]); return -1;}// 设置信号,在 shell 状态下可用 “kill + 进程号”正常终止些进程 Ctrl + c// 但请不要用 “kill -9 + 进程号”signal(SIGINT, FathEXIT); signal(SIGTERM, FathEXIT);// 初始化 TcpServer 的通信端口if(TcpServer.InitServer(5005) == false){logfile.Write("TcpServer.InitServer(5005) failed. \n"); FathEXIT(-1);}while(true){if(TcpServer.Accept() == false){  // 等待客户端连接logfile.Write("TcpServer.Accept() failed. \n"); continue;}// 父进程返回到循环首部if(fork() > 0){TcpServer.CloseClient(); continue; }// 子进程重新设置退出信号signal(SIGINT, ChldEXIT); signal(SIGTERM, ChldEXIT);TcpServer.CloseListen();// 以下是子进程,负责与客户端通信printf("客户端(%s)已连接。 \n", TcpServer.GetIP());char strbuffer[1024];  // 存放数据的缓冲区while(true){memset(strbuffer, 0, sizeof(strbuffer));// 接收客户端发过来的请求报文if(TcpServer.Read(strbuffer, 10) == false) break;printf("接收:%s \n", strbuffer);strcat(strbuffer, "ok"); // 在客户端的报文后加上“ok”printf("发送:%s \n", strbuffer);if(TcpServer.Write(strbuffer) == false)break; // 向客户端回应报文}printf("客户端已断开。 \n");  // 程序直接退出,析构函数会释放资源ChldEXIT(-1); // 通信完成后,子进程退出。}
}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("父进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}void ChldEXIT(int sig){ // 子进程退出函数if(sig > 0){signal(sig, SIG_IGN);signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);}logfile.Write("子进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}

  注意,跟踪日志的 linux 语句为,

tail -f /tmp/ExitAndFreeServer.log

  运行结果如下,

2.4 增加业务逻辑

  增加业务逻辑能够给客户端身份验证。业务报文的格式自定义,客户端与服务端协商好就可以,使用 xml ,增加可读性、可扩展性、容错性。举个例子,

客户端请求报文示例:
<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>
服务端回应报文示例:
<retcode>0</retcode><message>成功。</message>
<retcode>-1</retcode><message>用户名或密码不正确。</message>

  下面例子要建立一个服务端设置密码,客户端要输入密码的方式,用XML来实现。其中客户端程序如下,

/** 程序功能:* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"CLogFile logfile;
CTcpServer TcpServer;  // 创建服务端对象// 程序退出时调用的函数
void FathEXIT(int sig);  // 父进程退出函数
void ChldEXIT(int sig);  // 子进程退出函数//  处理业务的主函数
bool _main(const char *strrecvbuffer, char *strsendbuffer);
//  身份验证业务处理函数
bool biz001(const char *strrecvbuffer, char *strsendbuffer);int main(int argc, char *argv[]){if(argc != 3){printf("Using:./ExitAndFreeServer port logfile\nExample:./ExitAndFreeServer 5005 /tmp/ExitAndFreeServer.log\n"); return -1;}// 关闭全部的信号,也把僵尸进程关闭for(int ii = 0; ii < 100; ii++) signal(ii, SIG_IGN);// 打开日志文件if(logfile.Open(argv[2], "a+") == false){printf("logfile.Open(%s) failed.\n", argv[2]); return -1;}// 设置信号,在 shell 状态下可用 “kill + 进程号”正常终止些进程 Ctrl + c// 但请不要用 “kill -9 + 进程号”signal(SIGINT, FathEXIT); signal(SIGTERM, FathEXIT);// 初始化 TcpServer 的通信端口if(TcpServer.InitServer(5005) == false){logfile.Write("TcpServer.InitServer(5005) failed. \n"); FathEXIT(-1);}while(true){if(TcpServer.Accept() == false){  // 等待客户端连接logfile.Write("TcpServer.Accept() failed. \n"); continue;}// 父进程返回到循环首部if(fork() > 0){TcpServer.CloseClient(); continue; }// 子进程重新设置退出信号signal(SIGINT, ChldEXIT); signal(SIGTERM, ChldEXIT);TcpServer.CloseListen();// 以下是子进程,负责与客户端通信logfile.Write("客户端(%s)已连接。 \n", TcpServer.GetIP());char strrecvbuffer[1024], strsendbuffer[1024];  //  存放数据的缓冲区while(true){memset(strrecvbuffer, 0, sizeof(strrecvbuffer));memset(strsendbuffer, 0, sizeof(strsendbuffer));// 接收客户端发过来的请求报文if(TcpServer.Read(strrecvbuffer, 80) == false) break;logfile.Write("接收:%s \n", strrecvbuffer);// 处理业务的主函数if(_main(strrecvbuffer, strsendbuffer) == false) ChldEXIT(-1);logfile.Write("发送:%s \n", strsendbuffer);if(TcpServer.Write(strsendbuffer) == false) break; // 向客户端回应报文}logfile.Write("客户端已断开。 \n");  // 程序直接退出,析构函数会释放资源ChldEXIT(-1); // 通信完成后,子进程退出。}
}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("父进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}void ChldEXIT(int sig){ // 子进程退出函数if(sig > 0){signal(sig, SIG_IGN);signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);}logfile.Write("子进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}bool _main(const char * strrecvbuffer, char * strsendbuffer){ // 处理业务的主函数int ibizcode = -1;GetXMLBuffer(strrecvbuffer, "bizcode", &ibizcode);switch(ibizcode){case 1: // 身份验证业务处理函数biz001(strrecvbuffer, strsendbuffer); break;/*case 2: // 余额查询biz002(strrecvbuffer, strsendbuffer); break;*/ default:logfile.Write("非法报文:%s\n", strrecvbuffer); return false;}return true;
}//  身份验证业务处理函数
bool biz001(const char * strrecvbuffer, char * strsendbuffer){char username[51], password[51];memset(username, 0, sizeof(username));memset(password, 0, sizeof(password));GetXMLBuffer(strrecvbuffer, "username", username, 50);GetXMLBuffer(strrecvbuffer, "password", password, 50);if( (strcmp(username, "wucz") == 0) && (strcmp(password, "p@ssw0rd") == 0) )sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");elsesprintf(strsendbuffer, "<retcode>-1</retcode><message>用户名或密码不正确。</message>");return true;
}

  服务端运行如下,

/** 程序功能:* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"CTcpClient TcpClient;  // 创建服务端对象bool biz001();  // 身份验证int main(int argc, char *argv[]){if(TcpClient.ConnectToServer("127.0.0.1", 5005) == false){ // 向服务端发起连接请求printf("TcpClient.ConnectToServer(\"127.0.0.1\", 5005) failed.\n"); return -1;}// 身份验证if(biz001() == false){printf("biz001() failed.\n"); return -1; }
/*sleep(10); biz002();  // 余额查询sleep(5);biz002();  // 余额查询for(int ii = 0; ii < 3; ii++){if(biz000() == false) break;sleep(31);}*///  程序直接退出,析构函数会释放资源
}bool biz001(){  char strbuffer[1024]; // 存放数据的缓存区memset(strbuffer, 0, sizeof(strbuffer));snprintf(strbuffer, 1000, "<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>");printf("发送:%s\n",strbuffer);if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文memset(strbuffer,0,sizeof(strbuffer));if (TcpClient.Read(strbuffer,20) == false) return false; // 接收服务端的回应报文printf("接收:%s\n", strbuffer);int iretcode = -1;GetXMLBuffer(strbuffer, "retcode", &iretcode);if(iretcode == 0){printf("身份验证成功。\n"); return true;}printf("身份验证失败。\n");return false;
}

  运行结果如下,可以看到已经建立好了连接,

  运行结果如下,可以看到已经建立好了连接。那样,框架就已经搭建好,然后在里面写业务就可以了,在 _main 中来实现,之用增加希望的业务就可以。

2.5 TCP 短连接和长连接

  client 与 server 建立连接进行通信,通信完成后释放连接,建立连接时需要 3 次握手,释放需要 4 次挥手,连接的建立和释放都需要时间, server 还有创建新进程或线程的开销。
  短连接:client/server 间只需要进行一次或连续多次通信,通信完成后马上断开了。管理起来比较简单,不需要额外的控制手段。
  长连接:client/server 间需要多次通信,通信的频率和次数不确定,所以 client 和 server 需要保持这个连接,常用的方法就是采用心跳机制。根据不同的应用场景采取不同的策略,选择合适的方法。

2.6 长连接的心跳机制

  采用 client 与 server 采用长连接,在连接空闲时,client 没若干秒向 server 发送一个心跳报文,server 也回复一个心跳保温,确认连接继续生效中。
  如果 server 在约定的时间内没有收到 client 的任何报文,则认为客户端已掉线,就主动断开连接,释放资源。心跳报文在 60 s 之内,不要超过 120 s;
  客户端程序如下,

/** 程序功能:* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"CLogFile logfile;
CTcpServer TcpServer;  // 创建服务端对象// 程序退出时调用的函数
void FathEXIT(int sig);  // 父进程退出函数
void ChldEXIT(int sig);  // 子进程退出函数//  处理业务的主函数
bool _main(const char *strrecvbuffer, char *strsendbuffer);
//  心跳报文
bool biz000(const char *strrecvbuffer, char *strsendbuffer);
//  身份验证业务处理函数
bool biz001(const char *strrecvbuffer, char *strsendbuffer);
//  查询余业务处理函数
bool biz002(const char *strrecvbuffer, char *strsendbuffer);int main(int argc, char *argv[]){if(argc != 3){printf("Using:./ExitAndFreeServer port logfile\nExample:./ExitAndFreeServer 5005 /tmp/ExitAndFreeServer.log\n\n"); return -1;}// 关闭全部的信号,也把僵尸进程关闭for(int ii = 0; ii <= 64; ii++) signal(ii, SIG_IGN);// 打开日志文件if(logfile.Open(argv[2], "a+") == false){printf("logfile.OPen(%s) failed.\n", argv[2]); return -1;}// 设置信号,在 shell 状态下可用 “kill + 进程号”正常终止些进程 Ctrl + c// 但请不要用 “kill -9 + 进程号”signal(SIGINT, FathEXIT); signal(SIGTERM, FathEXIT);// 初始化 TcpServer 的通信端口if(TcpServer.InitServer(5005) == false){logfile.Write("TcpServer.InitServer(5005) failed. \n"); FathEXIT(-1);}while(true){if(TcpServer.Accept() == false){  // 等待客户端连接logfile.Write("TcpServer.Accept() failed. \n"); continue;}// 父进程返回到循环首部if(fork() > 0){TcpServer.CloseClient(); continue; }// 子进程重新设置退出信号signal(SIGINT, ChldEXIT); signal(SIGTERM, ChldEXIT);TcpServer.CloseListen();// 以下是子进程,负责与客户端通信logfile.Write("客户端(%s)已连接。 \n", TcpServer.GetIP());char strrecvbuffer[1024], strsendbuffer[1024];  //  存放数据的缓冲区while(true){memset(strrecvbuffer, 0, sizeof(strrecvbuffer));memset(strsendbuffer, 0, sizeof(strsendbuffer));// 接收客户端发过来的请求报文if(TcpServer.Read(strrecvbuffer, 30) == false) break;logfile.Write("接收:%s \n", strrecvbuffer);// 处理业务的主函数if(_main(strrecvbuffer, strsendbuffer) == false) ChldEXIT(-1);logfile.Write("发送:%s \n", strsendbuffer);if(TcpServer.Write(strsendbuffer) == false)break; // 向客户端回应报文}logfile.Write("客户端已断开。 \n");  // 程序直接退出,析构函数会释放资源ChldEXIT(-1); // 通信完成后,子进程退出。}
}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("父进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}void ChldEXIT(int sig){ // 子进程退出函数if(sig > 0){signal(sig, SIG_IGN);signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);}logfile.Write("子进程退出。 \n");// 编写善后代码(释放资源、提交或回滚事务)TcpServer.CloseClient();exit(0);
}bool _main(const char * strrecvbuffer, char * strsendbuffer){ // 处理业务的主函数int ibizcode = -1;GetXMLBuffer(strrecvbuffer, "bizcode", &ibizcode);switch(ibizcode){case 0: // 心跳biz000(strrecvbuffer, strsendbuffer); break;case 1: // 身份验证biz001(strrecvbuffer, strsendbuffer); break;case 2: // 余额查询biz002(strrecvbuffer, strsendbuffer); break;default:logfile.Write("非法报文:%s\n", strrecvbuffer); return false;}return true;
}//  身份验证业务处理函数
bool biz001(const char * strrecvbuffer, char * strsendbuffer){char username[51], password[51];memset(username, 0, sizeof(username));memset(password, 0, sizeof(password));GetXMLBuffer(strrecvbuffer, "username", username, 50);GetXMLBuffer(strrecvbuffer, "password", password, 50);if( (strcmp(username, "wucz") == 0) && (strcmp(password, "p@ssw0rd") == 0) )sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");elsesprintf(strsendbuffer, "<retcode>-1</retcode><message>用户名或密码不正确。</message>");return true;
}bool biz002(const char *strrecvbuffer, char *strsendbuffer){char cardid[51];memset(cardid, 0, sizeof(cardid));GetXMLBuffer(strrecvbuffer, "cardid", cardid, 50);if(strcmp(cardid, "62620000000001") == 0)sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message><ye>100.50</ye>");elsesprintf(strsendbuffer, "<retcode>-1</retcode><message>卡号不存在。</message>");return true;
}bool biz000(const char *strrecvbuffer, char *strsendbuffer){sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");return true;
}

  客户端程序如下,

/** 程序功能:* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include "_freecplus.h"CTcpClient TcpClient;  // 创建服务端对象bool biz000();  // 发送心跳报文
bool biz001();  // 身份验证
bool biz002();  // 余额查询int main(int argc, char *argv[]){if(argc != 3){printf("Using:./client ip port\n Example:./client 127.0.0.1 5005\n\n"); return -1;}if(TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false){ //  向服务端发起连接请求printf("TcpClient.ConnectToServer(\"%s\", %s) failed.\n", argv[1], argv[2]); return -1;}// 身份验证if(biz001() == false){printf("biz001() failed.\n"); return -1; }sleep(10); biz002();  // 余额查询sleep(5);biz002();  // 余额查询for(int ii = 0; ii < 3; ii++){if(biz000() == false) break;sleep(31);}//  程序直接退出,析构函数会释放资源
}bool biz001(){  // char strbuffer[1024]; // 存放数据的缓存区memset(strbuffer,0,sizeof(strbuffer));snprintf(strbuffer, 1000, "<bizcode>1</bizcode><username>wucz</username><password>p@ssw0rd</password>");printf("发送:%s\n",strbuffer);if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文memset(strbuffer,0,sizeof(strbuffer));if (TcpClient.Read(strbuffer,20) == false) return false; // 接收服务端的回应报文printf("接收:%s\n",strbuffer);int iretcode = -1;GetXMLBuffer(strbuffer, "retcode", &iretcode);if(iretcode == 0){printf("身份验证成功。\n"); return true;}printf("身份验证失败。\n");return false;
}bool biz002(){char strbuffer[1024];  // 存放数据的缓冲区snprintf(strbuffer, 1000, "<bizcode>2</bizcode><cardid>62620000000001</cardid>");printf("发送:%s\n", strbuffer);if(TcpClient.Write(strbuffer) == false) return false;  // 向服务端发送请求报文memset(strbuffer, 0, sizeof(strbuffer));if(TcpClient.Read(strbuffer, 20) == false) return false;  // 接收服务端的回应报文printf("接收:%s\n", strbuffer);int iretcode = -1;GetXMLBuffer(strbuffer, "retcode", &iretcode);if(iretcode == 0) {printf("查询余额成功。\n"); return true; }printf("查询余额成功。\n"); return true;
}bool biz000(){  // 发送心跳报文char strbuffer[1024]; // 存放数据的缓存区memset(strbuffer,0,sizeof(strbuffer));snprintf(strbuffer, 1000, "<bizcode>0</bizcode>");printf("发送:%s\n",strbuffer);if (TcpClient.Write(strbuffer) == false) return false; // 向服务端发送请求报文memset(strbuffer,0,sizeof(strbuffer));if (TcpClient.Read(strbuffer,20) == false) return false; // 接收服务端的回应报文printf("接收:%s\n",strbuffer);return true;
}

  运行结果如下,

  通过每隔一段时间同客户端和服务端进行一次通信,这样检测是否还在连接。在运行完成之后,客户端断开了连接。

三、总结

  最近事情比较多,真的比较忙,不过也还在认真的学习网络编程基本知识,现在感觉学习还是比较囫囵吞枣的,只是调试运行成功,然后理解每个语句功能,在 freecplus 框架里的内容还没有认真的理解,后面过程继续深入理解。

学习C++项目—— 搭建多进程网络服务框架,增加业务和日志,心跳机制相关推荐

  1. 学习C++项目—— 搭建多线程网络服务框架,性能测试(并发性能测试,业务性能测试,客户端响应时间测试,网络带宽测试)

    学习计算机网络编程 一.思路和学习方法   本文学习于:C语言技术网(www.freecplus.net),在 b 站学习于 C 语言技术网,并加以自己的一些理解和复现,如有侵权会删除.   接下来对 ...

  2. No6-6.从零搭建spring-cloud-alibaba微服务框架,添加用户鉴权逻辑,动态数据权限(使用AOP实现)等(六,no6-6)

    代码地址与接口看总目录:[学习笔记]记录冷冷-pig项目的学习过程,大概包括Authorization Server.springcloud.Mybatis Plus~~~_清晨敲代码的博客-CSDN ...

  3. No6-3.从零搭建spring-cloud-alibaba微服务框架,实现资源端用户认证与授权等(三,no6-3)

    代码地址与接口看总目录:[学习笔记]记录冷冷-pig项目的学习过程,大概包括Authorization Server.springcloud.Mybatis Plus~~~_清晨敲代码的博客-CSDN ...

  4. 开源的网络服务框架:Apache Etch 1.4.0 发布

    Apache Etch是一个开源.跨平台.语言和传输方式独立的网络服务框架,可用来构建和使用网络服务.Etch提供的工具集包括一个网络服务描述语言.一个编译器和与各种语言进行绑定的库.它具有传输方式独 ...

  5. python网络开发框架_greenev首页、文档和下载 - Python网络服务框架 - OSCHINA - 中文开源技术交流社区...

    greenev是一个基于greenlet协程,事件驱动,非阻塞socket模型的Python网络服务框架,它使得可以编写同步的代码,却得到异步执行的优点.reactor模式采用基于epoll, kqu ...

  6. BT源代码学习心得(五):统一网络服务接口--RawServer -- 转贴自 wolfenstein (NeverSayNever)

    BT源代码学习心得(五):统一网络服务接口--RawServer author:wolfenstein 以后的部分都需要网络服务(种子文件的生成在本地就可以完成,但是通过这些种子文件下载实际的内容和提 ...

  7. python模块介绍- SocketServer 网络服务框架

    转载自http://my.oschina.net/u/1433482/blog/190612 摘要 SocketServer简化了网络服务器的编写.它有4个类:TCPServer,UDPServer, ...

  8. 分布式服务框架学习笔记2 常用的分布式服务框架 与 通信框架选择

    传统垂直架构改造的核心就是要对应用进行服务化,服务化改造使用到的核心技术就是分布式服务框架. 分布式服务框架演进 应用从集中式走向分布式 大规模系统架构的设计一般原则就是尽可能地拆分,以达到更好的独立 ...

  9. java微服务项目简历_微服务框架-SpringCloud简介

    前面一篇文章谈到微服务基础框架,而Netflix的多个开源组件一起正好可以提供完整的分布式微服务基础架构环境,而对于Spring Cloud正是对Netflix的多个开源组件进一步的封装而成,同时又实 ...

最新文章

  1. cf两边黑屏怎么解决win10_Win10电脑开机黑屏只有鼠标指针无法进入桌面的解决方法...
  2. delegate,event, lambda,Func,Action以及Predicate
  3. java 求集合平均数_图像二值化方法介绍(转载学习)
  4. HDLBits答案(12)_Verilog移位寄存器
  5. [html] 如何使用纯html制作一个进度条?
  6. permutation 1(HDU-6628)
  7. 【报告分享】2021年品牌CDP与营销数字化转型报告.pdf(附下载链接)
  8. double四舍五入
  9. TLS协议簇加解密流程
  10. ubuntu安装ROBOWARE
  11. matlab 自写 自适应中值滤波 matlab自适应中值滤波算法
  12. 综合实践活动信息技术小学版第三册电子课本_摆事实,讲道理!电子商务讲师证报名入口和费用...
  13. ubuntu 18.04 安装NFS 共享文件夹,Linux挂载,Mac 挂载
  14. AES200 软件用户手册
  15. 播放index.m3u8切片文件显示为直播问题
  16. 谁来救救过拟合?透过现象看本质,如何利用正则化方法解决过拟合问题
  17. Hystrix断路器执行原理
  18. [转] 能不吃最好别吃:一个食品专业本科生的自白
  19. C语言 球弹跳高度的计算
  20. javascript网页特效——按钮特效

热门文章

  1. Python(42)_文件操作
  2. 【Python3_进阶系列_013】Python3-实现文件夹文件的过滤
  3. java8的新特性详解-----------Lamda表达式
  4. Openstack+Kubernetes+Docker微服务实践之路--基础设施
  5. ZooKeeper之(一)ZooKeeper是什么
  6. DataGridView导出到Excel的三个方法
  7. svn的使用--解决commit冲突问题
  8. Java:多线程,线程池,用Executors静态工厂生成常用线程池
  9. Servlet学习-MVC开发模式
  10. iOS开发之注册推送通知权限