对父进程来说,不需要客户端连接的socket,对子进程来说,不需要监听的socket。代码中标出了两行关闭对应socket的函数
上图可见,对应的关闭了以后(3是服务端监听的socket,4是客户端连上来的socket)
通过上图测试,如果进行对应的关闭,当同时运行多个客户端(测试了3个)时,就会一直占用id。
// 演示多进程服务端程序
#include <iostream>
#include <signal.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
using namespace std;
class ctcpserver
{
private:
int m_listenfd; // 监听的socket,-1表示未初始化
int m_clientfd; // 客户端连接上来的socket,-1表示客户端未连接
string m_clientip; // 客户端的IP
unsigned short m_port; // 服务端用于通讯的端口
public:
ctcpserver():m_listenfd(-1), m_clientfd(-1) {}
// 初始化服务端用于监听的socket
bool initserver(const unsigned short in_port)
{ // 1 创建服务端socket
if((m_listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return false;
m_port = in_port;
// 2 把服务端用于通信的IP和端口绑定到socket上
struct sockaddr_in servaddr; // 用于存放协议、端口和IP地址的结构体
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议族,固定填AF_INET
servaddr.sin_port = htons(m_port); // 指定服务端的通信端口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 如果操作系统有多个IP,全部的IP都可以用于通讯
// 绑定服务端的IP和端口(为socket分配IP和端口)
if(bind(m_listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) // 绑定服务端的IP和端口
{
close(m_listenfd);
m_listenfd = -1;
return false;
}
// 3 把socket设置可为连接(监听)的状态
if(listen(m_listenfd, 5) == -1)
{
close(m_listenfd);
m_listenfd = -1;
return false;
}
return true;
}
// 受理客户端的连接(从已连接的客户端中取出一个客户端)
// 如果没有已连接的客户端,accept()函数将阻塞等待
bool accept()
{
struct sockaddr_in caddr;
socklen_t addrlen = sizeof(caddr);
if((m_clientfd = ::accept(m_listenfd, (struct sockaddr *)&caddr, &addrlen)) == -1)
return false;
m_clientip = inet_ntoa(caddr.sin_addr);
return true;
}
// 获取客户端的IP(字符串格式)
const string & clientip() const
{
return m_clientip;
}
// 向客户端发送报文,成功返回true,失败返回false
bool send(const string &buffer)
{
if(m_clientfd == -1)
return false;
if((::send(m_clientfd, buffer.data(), buffer.size(), 0)) <= 0)
return false;
return true;
}
// 接受对端的报文,成功返回true,失败返回false
// buffer存放接收到的报文内容,maxlen本次接收报文的最大长度
bool recv(string &buffer, const size_t maxlen)
{ // 如果直接操作string对象的内存,必须保证:1)不能越界;2)操作后手动设置数据的大小
buffer.clear();
buffer.resize(maxlen);
int readn = ::recv(m_clientfd, &buffer[0], buffer.size(), 0); // 直接操作buffer的内存
//readn表示接受到的数据大小,-1失败,0 socket连接已断开,>0 成功接收到数据
if(readn <= 0)
{
buffer.clear();
return false;
}
buffer.resize(readn); // 重置buffer的实际大小
return true;
}
//关闭监听的socket
bool closelisten()
{
if(m_listenfd == -1)
return false;
::close(m_listenfd);
m_listenfd = -1;
return true;
}
// 关闭客户端连上来的socket
bool closeclient()
{
if(m_clientfd == -1)
return false;
::close(m_clientfd);
m_clientfd = -1;
return true;
}
~ctcpserver()
{
closelisten();
closeclient();
}
};
void FathEXIT(int sig);
void ChldEXIT(int sig);
ctcpserver tcpserver; // 定义为全局对象,方便在父进程子进程函数中使用。
int main (int argc, char *argv[])
{
if(argc != 2)
{
cout << "Using:./demo_server 通讯宽口\nExample:./demo_server 5005\n\n";
cout << "注意:运行服务端程序的Linux系统的防火墙必须要开通5005端口。\n";
cout << "如果是云服务器,还要开通云平台访问策略。\n\n";
return -1;
}
// 忽略全部信号,不希望被打扰
for(int ii = 1; ii <= 64; ii ++ )
signal(ii, SIG_IGN);
signal(SIGTERM, FathEXIT); // 15
signal(SIGINT, FathEXIT); // 2
if(tcpserver.initserver(atoi(argv[1])) == false)
{
perror("initserver()");
return -1;
}
while(true)
{
// 受理客户端的连接(从已连接的客户端中取出一个客户端)
// 如果没有已连接的客户端,accept()函数将阻塞等待
if(tcpserver.accept() == false)
{
perror("accept()");
return -1;
}
int pid = fork();
if(pid == -1) // 系统资源不足
{
perror("accept()");
return -1;
}
if(pid > 0)
{ // 父进程
tcpserver.closeclient(); // 关闭客户端连接的socket(父进程不需要了)
continue; // 父进程返回到循环开始的位置,继续受理客户端的连接
}
tcpserver.closelisten(); // 子进程也不需要监听的socket了
//子进程需要重新设置信号
signal(SIGTERM, ChldEXIT); // 子进程的退出函数与父进程不一样
signal(SIGINT, SIG_IGN); // 子进程不需要捕获SIGINT信号
// 子进程负责与客户端进行通讯
cout << "客户端已连接(" << tcpserver.clientip() << ")。\n";
string buffer;
while(true)
{
// 接收客户端请求报文,如果客户端没有发送请求报文,recv()函数将阻塞等待
// 如果客户端已断开连接,recv()函数将返回0
if((tcpserver.recv(buffer, 1024)) == false)
{
perror("recv()");
break;
}
cout << "接收:" << buffer << endl;
buffer = "OK";
if(tcpserver.send(buffer) == false) // 向客户端发送回应报文
{
perror("send");
break;
}
cout << "发送:" << buffer << endl;
}
return 0; // 子进程一定要退出,不然又会回到accept()函数的位置
}
}
void FathEXIT(int sig) // 父进程处理信号
{
// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
cout << "父进程退出,sig = " << sig << endl;
kill(0, SIGTERM); // 向全部的子进程发送15的信号,通知他们退出
// 增加释放资源的代码
tcpserver.closelisten(); // 父进程退出时关闭监听的socket
exit(0);
}
void ChldEXIT(int sig)
{
// 以下代码是为了防止信号处理函数在执行的过程中再次被信号中断
signal(SIGINT, SIG_IGN);
signal(SIGTERM, SIG_IGN);
cout << "子进程" << getpid() << "退出,sig = " << sig << endl;
tcpserver.closeclient(); // 子进程退出时关闭连接上来的客户端的socket
exit(0);
}