网络编程-多进程

83 阅读6分钟

socket通讯的客户端。

 * 此程序用于演示socket通讯的客户端。
 * 
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

int main(int argc,char *argv[])
{
  if (argc!=3)
  {
    printf("Using:./demo01 ip port\nExample:./demo01 127.0.0.1 5005\n\n"); return -1;
  }

  // 第1步:创建客户端的socket。
  int sockfd;
  if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
 
  // 第2步:向服务器发起连接请求。
  struct hostent* h;
  if ( (h = gethostbyname(argv[1])) == 0 )   // 指定服务端的ip地址。
  { printf("gethostbyname failed.\n"); close(sockfd); return -1; }
  struct sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(atoi(argv[2])); // 指定服务端的通讯端口。
  memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);

  //把socket连接设置为非阻塞,此时不等待3次握手完成connect就直接返回,
  fcntl(sockfd, F_SETFL, O_NONBLOCK);   
  if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)  // 向服务端发起连接清求。
  { 
    printf("errno=%d, EINPROGRESS=%d\n", errno,EINPROGRESS);  //非阻塞socket连接,立即返回的错误码
    if( errno != EINPROGRESS)
    {
      perror("connect"); close(sockfd); return -1; 
    }
  }

  /*
    int bufsize= 0;
    socklent_t optlen=sizeof(bufsize);
    getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &bufsize, &optlen); //获取接收缓存区的大小
    printf("recv bufsize=%d\n", bufsize);
    getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &bufsize, &optlen); //获取发送缓存区的大小
    printf("recv bufsize=%d\n", bufsize);
  */

  int iret;
  char buffer[102400];
 
  // 第3步:与服务端通讯,发送一个报文后等待回复,然后再发下一个报文。
  for (int ii=0;ii<10;ii++)
  {
    memset(buffer,0,sizeof(buffer));
    sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);
    if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0) // 向服务端发送请求报文。
    { perror("send"); break; }
    printf("发送:%s\n",buffer);

    //因为上面已经将socket设置为非阻塞,所以当服务端的报文没来得及发回来时,这里接收就失败了,返回-1
    //这种失败不是socket失败,而是没有数据可读。返回了EAGAIN的错误码

    //非阻塞的IO一般是在IO复用的场景下,所以需要通过select, poll, epoll方式处理一下
    struct pollfd fds;
    fds.fd = sockfd;
    fds.events=POLLIN;
    poll(&fds, 1, -1);  //超时时间设置-1,直到有事件发生才返回。
    memset(buffer,0,sizeof(buffer));
    if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0) // 接收服务端的回应报文。
    {
      printf("iret=%d, erron=%d, EAGAIN=%d\n",iret, errno, EAGAIN); break;
    }
    printf("接收:%s\n",buffer);

    sleep(1);  // 每隔一秒后再次发送报文。
  }
 
  // 第4步:关闭socket,释放资源。
  close(sockfd);
}

socket通讯的服务端

 * 此程序用于演示socket通讯的服务端。
 * 
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
 
int main(int argc,char *argv[])
{
  if (argc!=2)
  {
    printf("Using:./demo02 port\nExample:./demo02 5005\n\n"); return -1;
  }

  // 第1步:创建服务端的socket。
  int listenfd;
  if ( (listenfd = socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket"); return -1; }
  
  // 第2步:把服务端用于通讯的地址和端口绑定到socket上。
  struct sockaddr_in servaddr;    // 服务端地址信息的数据结构。
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;  // 协议族,在socket编程中只能是AF_INET。
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);          // 任意ip地址。
  servaddr.sin_port = htons(atoi(argv[1]));  // 指定通讯端口。
  if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 )
  { perror("bind"); close(listenfd); return -1; }

  int opt = 1; unsigned int len = sizeof(opt);
  setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
 
  // 第3步:把socket设置为监听模式。
  if (listen(listenfd,5) != 0 ) { perror("listen"); close(listenfd); return -1; }

  // 第4步:接受客户端的连接。
  int  clientfd;                  // 客户端的socket。
  int  socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
  struct sockaddr_in clientaddr;  // 客户端的地址信息。
  clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
  printf("客户端(%s)已连接。\n",inet_ntoa(clientaddr.sin_addr));
 
  int iret;
  char buffer[102400];

  // 第5步:与客户端通讯,接收客户端发过来的报文后,回复ok。
  while (1)
  {
    memset(buffer,0,sizeof(buffer));
    if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0) // 接收客户端的请求报文。
    {
       printf("iret=%d\n",iret); break;   
    }
    printf("接收:%s\n",buffer);

    strcpy(buffer,"ok");
    if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0) // 向客户端发送响应结果。
    { perror("send"); break; }
    printf("发送:%s\n",buffer);
  }
 
  // 第6步:关闭socket,释放资源。
  close(listenfd); close(clientfd); 
}

TcpClient类

class CTcpClient
{
public:
  int  m_connfd;    // 客户端的socket.
  char m_ip[21];    // 服务端的ip地址。
  int  m_port;      // 与服务端通讯的端口。
  bool m_btimeout;  // 调用Read方法时,失败的原因是否是超时:true-超时,false-未超时。
  int  m_buflen;    // 调用Read方法后,接收到的报文的大小,单位:字节。

  CTcpClient();  // 构造函数。

  // 向服务端发起连接请求。
  // ip:服务端的ip地址。
  // port:服务端监听的端口。
  // 返回值:true-成功;false-失败。
  bool ConnectToServer(const char *ip,const int port);

  // 接收服务端发送过来的数据。
  // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
  // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
  // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
  bool Read(char *buffer,const int itimeout=0);

  // 向服务端发送数据。
  // buffer:待发送数据缓冲区的地址。
  // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
  // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
  bool Write(const char *buffer,const int ibuflen=0);

  // 断开与服务端的连接
  void Close();

  ~CTcpClient();  // 析构函数自动关闭socket,释放资源。
};

----实现
CTcpClient::CTcpClient()
{
  m_connfd=-1;
  memset(m_ip,0,sizeof(m_ip));
  m_port=0;
  m_btimeout=false;
}

bool CTcpClient::ConnectToServer(const char *ip,const int port)
{
  // 如果已连接到服务端,则断开,这种处理方法没有特别的原因,不要纠结。
  if (m_connfd!=-1) { close(m_connfd); m_connfd=-1; }
 
  // 忽略SIGPIPE信号,防止程序异常退出。
  // 如果send到一个disconnected socket上,内核就会发出SIGPIPE信号。这个信号
  // 的缺省处理方法是终止进程,大多数时候这都不是我们期望的。我们重新定义这
  // 个信号的处理方法,大多数情况是直接屏蔽它。
  signal(SIGPIPE,SIG_IGN);   

  STRCPY(m_ip,sizeof(m_ip),ip);
  m_port=port;

  struct hostent* h;
  struct sockaddr_in servaddr;

  if ( (m_connfd = socket(AF_INET,SOCK_STREAM,0) ) < 0) return false;

  if ( !(h = gethostbyname(m_ip)) )
  {
    close(m_connfd);  m_connfd=-1; return false;
  }

  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(m_port);  // 指定服务端的通讯端口
  memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);

  if (connect(m_connfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
  {
    close(m_connfd);  m_connfd=-1; return false;
  }

  return true;
}

// 接收服务端发送过来的数据。
// buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
// itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
bool CTcpClient::Read(char *buffer,const int itimeout)
{
  if (m_connfd==-1) return false;

  // 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
  if (itimeout>0)
  {
    struct pollfd fds;
    fds.fd=m_connfd;
    fds.events=POLLIN;  
    int iret;
    m_btimeout=false;
    if ( (iret=poll(&fds,1,itimeout*1000)) <= 0 )
    {
      if (iret==0) m_btimeout = true;
      return false;
    }
  }

  m_buflen = 0;
  return (TcpRead(m_connfd,buffer,&m_buflen));
}

bool CTcpClient::Write(const char *buffer,const int ibuflen)
{
  if (m_connfd==-1) return false;

  int ilen=ibuflen;

  if (ibuflen==0) ilen=strlen(buffer);

  return(TcpWrite(m_connfd,buffer,ilen));
}

void CTcpClient::Close()
{
  if (m_connfd > 0) close(m_connfd); 

  m_connfd=-1;
  memset(m_ip,0,sizeof(m_ip));
  m_port=0;
  m_btimeout=false;
}

CTcpClient::~CTcpClient()
{
  Close();
}

socket通讯的服务端类

{
private:
  int m_socklen;                    // 结构体struct sockaddr_in的大小。
  struct sockaddr_in m_clientaddr;  // 客户端的地址信息。
  struct sockaddr_in m_servaddr;    // 服务端的地址信息。
public:
  int  m_listenfd;   // 服务端用于监听的socket。
  int  m_connfd;     // 客户端连接上来的socket。
  bool m_btimeout;   // 调用Read方法时,失败的原因是否是超时:true-超时,false-未超时。
  int  m_buflen;     // 调用Read方法后,接收到的报文的大小,单位:字节。

  CTcpServer();  // 构造函数。

  // 服务端初始化。
  // port:指定服务端用于监听的端口。
  // 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
  bool InitServer(const unsigned int port,const int backlog=5); 

  // 阻塞等待客户端的连接请求。
  // 返回值:true-有新的客户端已连接上来,false-失败,Accept被中断,如果Accept失败,可以重新Accept。
  bool Accept();

  // 获取客户端的ip地址。
  // 返回值:客户端的ip地址,如"192.168.1.100"。
  char *GetIP();

  // 接收客户端发送过来的数据。
  // buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
  // itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
  // 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
  bool Read(char *buffer,const int itimeout=0);

  // 向客户端发送数据。
  // buffer:待发送数据缓冲区的地址。
  // ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
  // 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
  bool Write(const char *buffer,const int ibuflen=0);

  // 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。
  void CloseListen();

  // 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。
  void CloseClient();

  ~CTcpServer();  // 析构函数自动关闭socket,释放资源。
};

-----实现
CTcpServer::CTcpServer()
{
  m_listenfd=-1;
  m_connfd=-1;
  m_socklen=0;
  m_btimeout=false;
}

bool CTcpServer::InitServer(const unsigned int port,const int backlog)
{
  // 如果服务端的socket>0,关掉它,这种处理方法没有特别的原因,不要纠结。
  if (m_listenfd > 0) { close(m_listenfd); m_listenfd=-1; }

  if ( (m_listenfd = socket(AF_INET,SOCK_STREAM,0))<=0) return false;

  // 忽略SIGPIPE信号,防止程序异常退出。
  signal(SIGPIPE,SIG_IGN);   

  // 打开SO_REUSEADDR选项,当服务端连接处于TIME_WAIT状态时可以再次启动服务器,
  // 否则bind()可能会不成功,报:Address already in use。
  //char opt = 1; unsigned int len = sizeof(opt);
  int opt = 1; unsigned int len = sizeof(opt);
  setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);    

  memset(&m_servaddr,0,sizeof(m_servaddr));
  m_servaddr.sin_family = AF_INET;
  m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   // 任意ip地址。
  m_servaddr.sin_port = htons(port);
  if (bind(m_listenfd,(struct sockaddr *)&m_servaddr,sizeof(m_servaddr)) != 0 )
  {
    CloseListen(); return false;
  }

  if (listen(m_listenfd,backlog) != 0 )
  {
    CloseListen(); return false;
  }

  return true;
}

bool CTcpServer::Accept()
{
  if (m_listenfd==-1) return false;

  m_socklen = sizeof(struct sockaddr_in);

  if ((m_connfd=accept(m_listenfd,(struct sockaddr *)&m_clientaddr,(socklen_t*)&m_socklen)) < 0)
      return false;

  return true;
}

char *CTcpServer::GetIP()
{
  return(inet_ntoa(m_clientaddr.sin_addr));
}

bool CTcpServer::Read(char *buffer,const int itimeout)
{
  if (m_connfd==-1) return false;

  // 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
  if (itimeout>0)
  {
    struct pollfd fds;
    fds.fd=m_connfd;
    fds.events=POLLIN;
    m_btimeout=false;
    int iret;
    if ( (iret=poll(&fds,1,itimeout*1000)) <= 0 )
    {
      if (iret==0) m_btimeout = true;
      return false;
    }
  }

  m_buflen = 0;
  return(TcpRead(m_connfd,buffer,&m_buflen));
}

bool CTcpServer::Write(const char *buffer,const int ibuflen)
{
  if (m_connfd==-1) return false;

  int ilen = ibuflen;
  if (ilen==0) ilen=strlen(buffer);

  return(TcpWrite(m_connfd,buffer,ilen));
}

void CTcpServer::CloseListen()
{
  if (m_listenfd > 0)
  {
    close(m_listenfd); m_listenfd=-1;
  }
}

void CTcpServer::CloseClient()
{
  if (m_connfd > 0)
  {
    close(m_connfd); m_connfd=-1; 
  }
}

CTcpServer::~CTcpServer()
{
  CloseListen(); CloseClient();
}

接收socket的对端发送过来的数据。

// sockfd:可用的socket连接。
// buffer:接收数据缓冲区的地址。
// ibuflen:本次成功接收数据的字节数。
// itimeout:接收等待超时的时间,单位:秒,-1-不等待;0-无限等待;>0-等待的秒数。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。
bool TcpRead(const int sockfd,char *buffer,int *ibuflen,const int itimeout)
{
  if (sockfd==-1) return false;

  // 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
  if (itimeout>0)
  {
    struct pollfd fds;
    fds.fd=sockfd;
    fds.events=POLLIN;
    if ( poll(&fds,1,itimeout*1000) <= 0 ) return false;
  }

  // 如果itimeout==-1,表示不等待,立即判断socket的缓冲区中是否有数据,如果没有,返回false。
  if (itimeout==-1)
  {
    struct pollfd fds;
    fds.fd=sockfd;
    fds.events=POLLIN;
    if ( poll(&fds,1,0) <= 0 ) return false;
  }

  (*ibuflen) = 0;  // 报文长度变量初始化为0。

  // 先读取报文长度,4个字节。
  if (Readn(sockfd,(char*)ibuflen,4) == false) return false;

  (*ibuflen)=ntohl(*ibuflen);  // 把报文长度由网络字节序转换为主机字节序。

  // 再读取报文内容。
  if (Readn(sockfd,buffer,(*ibuflen)) == false) return false;

  return true;
}

向socket的对端发送数据。

// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的字节数,如果发送的是ascii字符串,ibuflen填0或字符串的长度,
//          如果是二进制流数据,ibuflen为二进制数据块的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflen)
{
  if (sockfd==-1) return false;

  int ilen=0;  // 报文长度。

  // 如果ibuflen==0,就认为需要发送的是字符串,报文长度为字符串的长度。
  if (ibuflen==0) ilen=strlen(buffer);
  else ilen=ibuflen;

  int ilenn=htonl(ilen);    // 把报文长度转换为网络字节序。

  char TBuffer[ilen+4];     // 发送缓冲区。
  memset(TBuffer,0,sizeof(TBuffer));  // 清区发送缓冲区。
  memcpy(TBuffer,&ilenn,4);           // 把报文长度拷贝到缓冲区。
  memcpy(TBuffer+4,buffer,ilen);      // 把报文内容拷贝到缓冲区。
  
  // 发送缓冲区中的数据。
  if (Writen(sockfd,TBuffer,ilen+4) == false) return false;

  return true;
}

从已经准备好的socket中读取数据。

// sockfd:已经准备好的socket连接。
// buffer:接收数据缓冲区的地址。
// n:本次接收数据的字节数。
// 返回值:成功接收到n字节的数据后返回true,socket连接不可用返回false。
bool Readn(const int sockfd,char *buffer,const size_t n)
{
  int nLeft=n;  // 剩余需要读取的字节数。
  int idx=0;    // 已成功读取的字节数。
  int nread;    // 每次调用recv()函数读到的字节数。

  while(nLeft > 0)
  {
    if ( (nread=recv(sockfd,buffer+idx,nLeft,0)) <= 0) return false;

    idx=idx+nread;
    nLeft=nLeft-nread;
  }

  return true;
}

向已经准备好的socket中写入数据。

// sockfd:已经准备好的socket连接。
// buffer:待发送数据缓冲区的地址。
// n:待发送数据的字节数。
// 返回值:成功发送完n字节的数据后返回true,socket连接不可用返回false。
bool Writen(const int sockfd,const char *buffer,const size_t n)
{
  int nLeft=n;  // 剩余需要写入的字节数。
  int idx=0;    // 已成功写入的字节数。
  int nwritten; // 每次调用send()函数写入的字节数。
  
  while(nLeft > 0 )
  {    
    if ( (nwritten=send(sockfd,buffer+idx,nLeft,0)) <= 0) return false;      
    
    nLeft=nLeft-nwritten;
    idx=idx+nwritten;
  }

  return true;
}

采用TcpClient类实现socket通讯的客户端

#include "../_public.h"
 
int main(int argc,char *argv[])
{
  if (argc!=3)
  {
    printf("Using:./demo07 ip port\nExample:./demo07 127.0.0.1 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 buffer[102400];
 
  // 与服务端通讯,发送一个报文后等待回复,然后再发下一个报文。
  for (int ii=0;ii<100000;ii++)
  {
    SPRINTF(buffer,sizeof(buffer),"这是第%d个超级女生,编号%03d。",ii+1,ii+1);
    if (TcpClient.Write(buffer)==false) break; // 向服务端发送请求报文。
    printf("发送:%s\n",buffer);

    memset(buffer,0,sizeof(buffer));
    if (TcpClient.Read(buffer)==false) break; // 接收服务端的回应报文。
    printf("接收:%s\n",buffer);

    sleep(1);  // 每隔一秒后再次发送报文。
  }
}

采用TcpServer类实现socket通讯的服务端

#include "../_public.h"
 
int main(int argc,char *argv[])
{
  if (argc!=2)
  {
    printf("Using:./demo08 port\nExample:./demo08 5005\n\n"); return -1;
  }

  CTcpServer TcpServer;

  // 服务端初始化。
  if (TcpServer.InitServer(atoi(argv[1]))==false)
  {
    printf("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
  }

  // 等待客户端的连接请求。
  if (TcpServer.Accept()==false)
  {
    printf("TcpServer.Accept() failed.\n"); return -1;
  }

  printf("客户端(%s)已连接。\n",TcpServer.GetIP());

  char buffer[102400];

  // 与客户端通讯,接收客户端发过来的报文后,回复ok。
  while (1)
  {
    memset(buffer,0,sizeof(buffer));
    if (TcpServer.Read(buffer)==false) break; // 接收客户端的请求报文。
    printf("接收:%s\n",buffer);

    strcpy(buffer,"ok");
    if (TcpServer.Write(buffer)==false) break; // 向客户端发送响应结果。
    printf("发送:%s\n",buffer);
  }
}

CTcpServer类实现socket通讯多进程的服务端

  • 1)在多进程的服务程序中,如果杀掉一个子进程,和这个子进程通讯的客户端会断开,但是,不 会影响其它的子进程和客户端,也不会影响父进程。
  • 2)如果杀掉父进程,不会影响正在通讯中的子进程,但是,新的客户端无法建立连接。
  • 3)如果用killall+程序名,可以杀掉父进程和全部的子进程。
  • 多进程网络服务端程序退出的三种情况:
  • 1)如果是子进程收到退出信号,该子进程断开与客户端连接的socket,然后退出。
  • 2)如果是父进程收到退出信号,父进程先关闭监听的socket,然后向全部的子进程发出退出信号。
  • 3)如果父子进程都收到退出信号,本质上与第2种情况相同。
#include "../_public.h"

CLogFile logfile;      // 服务程序的运行日志。
CTcpServer TcpServer;  // 创建服务端对象。

void FathEXIT(int sig);  // 父进程退出函数。
void ChldEXIT(int sig);  // 子进程退出函数。
 
int main(int argc,char *argv[])
{
  if (argc!=3)
  {
    printf("Using:./demo10 port logfile\nExample:./demo10 5005 /tmp/demo10.log\n\n"); return -1;
  }

  // 关闭全部的信号和输入输出。
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
  // 但请不要用 "kill -9 +进程号" 强行终止
  CloseIOAndSignal(); signal(SIGINT,FathEXIT); signal(SIGTERM,FathEXIT);

  if (logfile.Open(argv[2],"a+")==false) { printf("logfile.Open(%s) failed.\n",argv[2]); return -1; }

  // 服务端初始化。
  if (TcpServer.InitServer(atoi(argv[1]))==false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.\n",argv[1]); return -1;
  }

  while (true)
  {
    // 等待客户端的连接请求。
    if (TcpServer.Accept()==false)
    {
      logfile.Write("TcpServer.Accept() failed.\n"); FathEXIT(-1);
    }

    logfile.Write("客户端(%s)已连接。\n",TcpServer.GetIP());

    if (fork()>0) { TcpServer.CloseClient(); continue; }  // 父进程继续回到Accept()。
   
    // 子进程重新设置退出信号。
    signal(SIGINT,ChldEXIT); signal(SIGTERM,ChldEXIT);

    TcpServer.CloseListen();

    // 子进程与客户端进行通讯,处理业务。
    char buffer[102400];

    // 与客户端通讯,接收客户端发过来的报文后,回复ok。
    while (1)
    {
      memset(buffer,0,sizeof(buffer));
      if (TcpServer.Read(buffer)==false) break; // 接收客户端的请求报文。
      logfile.Write("接收:%s\n",buffer);

      strcpy(buffer,"ok");
      if (TcpServer.Write(buffer)==false) break; // 向客户端发送响应结果。
      logfile.Write("发送:%s\n",buffer);
    }

    ChldEXIT(0);
  }
}

// 父进程退出函数。
void FathEXIT(int sig)  
{
  // 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

  logfile.Write("父进程退出,sig=%d。\n",sig);

  TcpServer.CloseListen();    // 关闭监听的socket。

  kill(0,15);     // 通知全部的子进程退出。

  exit(0);
}

// 子进程退出函数。
void ChldEXIT(int sig)  
{
  // 以下代码是为了防止信号处理函数在执行的过程中被信号中断。
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

  logfile.Write("子进程退出,sig=%d。\n",sig);

  TcpServer.CloseClient();    // 关闭客户端的socket。

  exit(0);
}