1. 程式人生 > >多程序版本TCP聊天程式服務端

多程序版本TCP聊天程式服務端

  我們上次寫了利用TCP協議來實現的簡單的網路聊天程式,我們實現的是一對一的聊天, 但是如果我們通過上次的程式來實現多對一呢?

   

我們可以看出來其中一個客戶端可以和服務端正常交流,但是另一個客戶端是連結不上服務端的,即使原來的客戶端退出了,新的客戶端也是連結不上的。因為我們的程式當連結成功之後會通過while迴圈來一直呼叫read函式,並沒有再去呼叫我們的accept函式,所以沒辦法再接受其他請求。所以我們今天就要實現一個多程序的TCP程式可以讓其他客戶端也連結進來。

//這是一個實現多程序的tcp聊天程式服務端

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<string.h>
#include<error.h>
#include<signal.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int  create_work(int cli_fd)
{
    int pid=fork();
    if(pid<0)
    {
        perror("fork error\n");
        return -1;
    }
    else
    {
      if(pid==0)
      {
          //這裡是新建立的子程序
		  //用建立的子程序來獲取資料
          while(1)
          {
              char buff[1024]={0};
              int ret=recv(cli_fd,buff,1024,0);
              if(ret<=0)
              {
                  close(cli_fd);
                  exit(-1);
              }
              printf("client say:%s\n",buff);
              send(cli_fd,"what\?\?!!",8,0);

          }
      }else
      {
          close(cli_fd);
      }
      return 0;
    }
}
void sigcb(int signo)
{
    while(waitpid(-1,NULL,WNOHANG)>0);
    //當呼叫這個函式的時候我們會一直呼叫waitpid如果有任何子程序結束了,我們就釋放他的資源
	//waitpid如果子程序結束會返回子程序結束的狀態值
	//如果不在意子程序的返回值可以讓第二個引數設定為NULL,我們這裡並不在意
	//第一個引數
	//pid<-1 等待程序組識別碼為 pid 絕對值的任何子程序。
	//pid = -1 等待任何子程序, 相當於 wait()。
	//pid = 0 等待程序組識別碼與目前程序相同的任何子程序。
	//pid>0 等待任何子程序識別碼為 pid 的子程序。
	//WNOHANG 若pid指定的子程序沒有結束,則waitpid()函式返回0,不予以等待。若結束,則返回該子程序的ID。
}

int main(int argc,char* argv[])
{
   signal(SIGCHLD,sigcb);
   //第一個引數我們要處理的訊號
   //第二個引數是我們的處理方式一般有捕獲或者忽略
   //這裡我們呼叫signal是告訴作業系統當我們收到SIGCHLD這個訊號的時候處理方式為sigcb這個函式
   //SIGCHLD訊號是當一個程序終止或者停止的時候會發送這個訊號給父程序一般作業系統會自動忽略這個訊號
   //為了防止我們建立的子程序產生殭屍程序,所以我們提前做好準備如果有這個訊號就呼叫sigcb函式
   int lst_fd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
   //建立一個監聽socket這個socket僅僅用於接受客戶端的連結請求
   if(lst_fd<0)
   {
       perror("socket error\n");
       return -1;
   }
   struct sockaddr_in lst_addr;
   lst_addr.sin_family=AF_INET;
   lst_addr.sin_port=htons(9000);
   //htons 將兩個位元組的資料轉換成網路位元組序的資料
   lst_addr.sin_addr.s_addr=inet_addr("192.168.76.130");
   //htonl 將連個位元組的資料轉換層網路位元組序的資料
   //ntohs 將兩個位元組的網路位元組序資料轉換為當前的主機位元組序資料
   //ntohl 將四個位元組的網路位元組序資料轉換為當前的主機位元組序資料
   socklen_t len=sizeof(struct sockaddr_in);
   int ret=bind(lst_fd,(struct sockaddr*)&lst_addr,len);
   if(ret<0)
   {
       perror("bind error\n");
       return -1;
   }
   //開始監聽客戶端
   if(listen(lst_fd,5)<0)
   {
       perror("listen error\n");
       return -1;
   }
   //listen會在系統中開闢一個佇列,第二個引數5
   //就是定義這個佇列的最大結點數佇列名稱叫叫做連結成功佇列
   //第二個引數的功能:同一時間socket的最大併發連線數
   //一旦socket開始監聽,那麼就可以開始接受客戶端的請求
   //如果客戶端的請求過來,作業系統回味這個客戶端新建一個socket
   //連結成功之後會把這個socket放入連結成功佇列
   //接下來如果想要和這個客戶端通訊,就把這個socket從佇列中取出來進行操作
     while(1)
     {
         struct sockaddr_in cli_addr;
         int cli_fd=accept(lst_fd,(struct sockaddr*)&cli_addr,&len);
         if(cli_fd<0)
         {
             perror("accept error");
             continue;
         }
         create_work(cli_fd);
		 //每當我們的lst_fd這個socket接收到一個客戶端的訊號之後,就呼叫ceate_work來建立一個子程序接受資料
     }
     close(lst_fd);
     return 0;
}


這時候就可以看到兩個程序都可以向服務端傳送資料,並且也能接收到服務端返回的內容。

這裡的客戶端我們直接使用的以前的客戶端,這裡沒必要再寫一個新的客戶端。