datetime:2022/12/29 14:37
author:nzb
多进程网络服务端
需要有
信号
和多进程
相关知识利用信号防止产生僵尸进程
- 僵尸进程: 进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 获 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。 避免僵尸进程的方法:
- 1.fork 两次用孙子进程去完成子进程的任务
- 2.用 wait() 函数使父进程阻塞
- 3.使用信号量,在 signal handler 中调用 waitpid , 这样父进程不用阻塞
server.cpp
#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:./mpserver port logfile\nExample:./mpserver 5005 /tmp/mpserver.log\n\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 + 进程号" 正常终止些进程
// 但请不要用 "kill -9 +进程号" 强行终止
signal(SIGINT, FathEXIT);
signal(SIGTERM, FathEXIT);
if (TcpServer.InitServer(atoi(argv[1])) == false) // 初始化TcpServer的通信端口。
{
logfile.Write("TcpServer.InitServer(%s) failed.\n", argv[1]);
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 strbuffer[1024]; // 存放数据的缓冲区。
while (true) {
memset(strbuffer, 0, sizeof(strbuffer));
if (TcpServer.Read(strbuffer, 50) == false) break; // 接收客户端发过来的请求报文。
logfile.Write("接收:%s\n", strbuffer);
strcat(strbuffer, "ok"); // 在客户端的报文后加上"ok"。
logfile.Write("发送:%s\n", strbuffer);
if (TcpServer.Write(strbuffer) == 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) {
// 为什么大于0,因为可以这样使用 ChldEXIT(-1); // 通信完成后,子进程退出。
if (sig > 0) {
signal(sig, SIG_IGN);
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
}
logfile.Write("子进程退出。\n");
// 编写善后代码(释放资源、提交或回滚事务)
TcpServer.CloseClient();
exit(0);
}
client.cpp
#include "_freecplus.h"
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("Using:./demo47 ip port\nExample:./demo47 172.21.0.3 5005\n\n");
return -1;
}
CTcpClient TcpClient; // 创建客户端的对象。
if (TcpClient.ConnectToServer(argv[1], atoi(argv[2])) == false) // 向服务端发起连接请求。
{
printf("TcpClient.ConnectToServer(\"%s\",%s) failed.\n", argv[1], argv[2]);
return -1;
}
char strbuffer[1024]; // 存放数据的缓冲区。
for (int ii = 0; ii < 30; ii++) // 利用循环,与服务端进行5次交互。
{
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);
}
// 程序直接退出,析构函数会释放资源。
}
增加业务逻辑
server.cpp
#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:./mpserver_biz port logfile\nExample:./mpserver_biz 5005 /tmp/mpserver_biz.log\n\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 + 进程号" 正常终止些进程
// 但请不要用 "kill -9 +进程号" 强行终止
signal(SIGINT, FathEXIT);
signal(SIGTERM, FathEXIT);
if (TcpServer.InitServer(atoi(argv[1])) == false) // 初始化TcpServer的通信端口。
{
logfile.Write("TcpServer.InitServer(%s) failed.\n", argv[1]);
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>");
else
sprintf(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>");
else
sprintf(strsendbuffer, "<retcode>-1</retcode><message>卡号不存在。</message>");
return true;
}
// 心跳报文
bool biz000(const char *strrecvbuffer, char *strsendbuffer) {
sprintf(strsendbuffer, "<retcode>0</retcode><message>成功。</message>");
return true;
}
client.cpp
#include "_freecplus.h"
CTcpClient TcpClient; // 创建客户端的对象。
bool biz000(); // 发送心跳报文。
bool biz001(); // 身份验证
bool biz002(); // 余额查询
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("Using:./demo47_biz ip port\nExample:./demo47_biz 172.21.0.3 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 < 10; ii++) {
if (biz000() == false) break;
sleep(10);
}
// 程序直接退出,析构函数会释放资源。
}
// 身份验证。
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]; // 存放数据的缓冲区。
memset(strbuffer, 0, sizeof(strbuffer));
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 false;
}
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;
}