程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 多進程的簡單網絡聊天程序-源代碼。

多進程的簡單網絡聊天程序-源代碼。

編輯:關於C語言


這個是偶在unix下編寫的多進程網絡通訊程序,不能算是個聊天程序,因為它只實現了單向信息傳送功能,使用了最常見的fork產生多進程避免了阻塞。偶的練筆作,bug太多,不過還好編譯運行成功。裡面偶花了點時間用中文注釋了下,供以後自己參考下。有朋友願意探討的可互相聯系。廢話少說,源代碼供上,批注:服務端偶中文注釋了,客戶端就算了,大同小異。只是少了listen和accept,多了個connect而已。

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//這是個用fork多進程的網絡服務端程序,使用了標准的編寫模式流程,我用中文詳細標注下解釋,供個人與大眾參考,有bug地方,願大
//家供探討,參考steven的UNIX環境高級編程和UNIX網絡編程。
//MSN: [email protected] E-mail: [email protected]
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////
//頭文件
//////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>

//////////////////////////////////////////////////
//全局定義。
//////////////////////////////////////////////////
#define LISTENQ 5
#define BUFFER_SIZE 1024

int child_process(int fd);

//////////////////////////////////////////////////
// 主函數
//////////////////////////////////////////////////
int main (int argc, char **argv)
{
    int global_connect_number = 0; //用於檢測連接服務端的客戶端個數。在父進程中獲得數量。
    //XXX: step 1: check the argc
    if (argc < 3) //用於檢測程序後的參數個數,沒有IP和port就會出錯退出。
    {
  fprintf(stderr, "Usage: %s<ipaddress><port>\n", strerror(errno));
  exit(1);
    }
    //XXX: step 2: create a socket
    int fd; //定義文件描述符。

    if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) //socket程序,成功返回文件描述符。你不知道什麼是文件描述符?看UNIX網絡編程去哦。
    {
  fprintf(stderr, "socket failed:%s\n", strerror(errno)); //錯誤處理,獲得errno。
  exit(1); //確實退出不是好辦法,不過這只是學習程序,原諒下。
    }

    //XXX: step 3: create a bind
    struct sockaddr_in server_address; //定義bind使用的IP 地址結構。為什麼用sockaddr_in而不是socketaddr?
    memset(&server_address, 0, sizeof(server_address)); //初始化這個IP地址結構,用0填充。

    server_address.sin_family = PF_INET; //IPV4 address,
    server_address.sin_port = htons(atoi(argv[2]));//因為字節序問題,需要用htons和atoi轉換下端口。
    server_address.sin_addr.s_addr = inet_addr(argv[1]);//同理。

    if (bind(fd, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) //bind程序,注意類型轉換成sockaddr
    {
  fprintf(stderr, "bind failed:%s\n", strerror(errno)); //出錯處理。
  exit(1);// 不推薦這麼使用。
    }
      
    //XXX: step 4: create a listen
    if (listen(fd, LISTENQ) < 0)//listen函數,監聽此文件描述符對應的socket綁定IP和port。
    {
  fprintf(stderr,"listen failed:%s\n", strerror(errno));
  exit(1);
    }
    else
    {
  fprintf(stdout, "socket is listening the port:%s:%d\n", argv[1],atoi(argv[2]));// 顯示成功。
    }
    //XXX: step 5: create a accept
    int new_fd;
    socklen_t address_len; //定義地址大小,socklen_t類型。
    struct sockaddr_in remote_address; //定義遠程客戶端的ip地址
    address_len = sizeof(remote_address); //為什麼這麼用?後面可以看到。

    for(;;) //for循環,
    {    
    if ((new_fd = accept(fd, (struct sockaddr *)&remote_address,&address_len)) < 0) //accept語句,接收遠端地址。
    {
  fprintf(stderr, "accept failed():%s\n", strerror(errno));
  exit(1);
    }
    else
    {
  fprintf(stdout, "accept a new TCP connect %d from %s:%d\n", new_fd, inet_ntoa(remote_address.sin_addr), ntohs(remote_address.sin_port)); //成功顯示的信息,注意字節序問題。

    }    
    //XXX: step 6: create a fork
    pid_t pid;

    if ((pid = fork()) < 0) //用fork新建立子進程,用子進程處理讀和寫的問題。
    {
  fprintf(stderr, "fork() failed:%s\n", strerror(errno));
  exit(1);
    }
    else if (pid == 0) //子進程,
    {
  //CHILD PROCESS
  child_process(new_fd);// 處理函數,下面有,也可以不寫成函數,但哪個好?不要偶說了。
  exit(0);
    }
    else //父進程。
    {
  //PARENT PROCESS
  fprintf(stdout, "%d have connection of %d\n", getpid(), global_connect_number++);//看到了吧,顯示連接數量。

    }
    }

    close(fd); //記得關閉描述符哦。
    close(new_fd);
    return 0;
}


int child_process(int fd) //子程序的讀寫處理函數。
{
    ssize_t written; //寫程序返回的寫詞的個數。
    char *request = "Hello, welcome to my char program!\n";//算是給客戶端的返回信息了。
again:
    if ((written = write (fd, request, strlen(request))) < 0)// 寫程序,也可以用send()代替.
    
    {
  if (errno == EINTR) //碰到信號中斷,執行again工作。
  {
      fprintf(stderr, "write was closed by signal\n");
      goto again;
  }
  else
  {
      fprintf(stderr, "write failed:%s\n", strerror(errno)); //出錯信息。
      exit(1);
  }
    }
    else
    {
  fprintf(stdout, "write successd!\n"); //成功信息,這比較簡單,能寫多點功能更好。我懶。
    }
    
    char buffer[BUFFER_SIZE]; //從監控端口讀到的信息存放點。
    ssize_t n; //讀到的數。
    
    for(;;) //又一個for循環,
    {

    if ((n = read (fd, buffer, BUFFER_SIZE)) < 0) //讀程序,從fd文件描述符中讀數據,存到buffer中。
    {
  fprintf(stderr, "recv() failed:%s\n", strerror(errno));
  exit(1);
    }
    else if (n == 0) //讀完了的處理。
    {
  fprintf(stderr, "connection was closed by peer\n");
  close(fd);
  break;
    }
    else

    {
  buffer[n] = '\0';//有這個才不會出現下次讀信息時出現數據不匹配的問題。
  struct sockaddr_in peer_address;
  socklen_t peer_len;
  peer_len = sizeof(peer_address);

  if (getpeername(fd, (struct sockaddr *)&peer_address, &peer_len) < 0) //獲得ip結構信息。
  {
      fprintf(stderr, "getpeername() failed:%s\n", strerror(errno));
      exit(1);
  }
  else
  {
      fprintf(stdout, "On socket %d(%s:%d):%s\n", fd,inet_ntoa(peer_address.sin_addr),ntohs(peer_address.sin_port), buffer); //讀成功時的輸出,顯示遠程IP地址和端口。
  }


    }
    }
    
    close(fd);//關閉文件描述符。
    free(buffer);//清空緩存。避免出現內存洩漏。
    return 0;
}

// 客戶端程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>

#define BUFFER_SIZE 1024

int
main (int argc, char **argv)
{
  //XXX: step 1: check the argc
  if (argc < 3)
    {
      fprintf (stderr, "Usage: %s<ipaddress><port>\n", strerror (errno));
      exit (1);
    }

  //XXX: step 2: create a socket
  int fd;

  if ((fd = socket (PF_INET, SOCK_STREAM, 0)) < 0)
    {
      fprintf (stderr, "socket failed():%s\n", strerror (errno));
      exit (1);
    }

  fprintf (stdout, "successd create a socket %d\n", fd);

  //XXX: step 3: create a bind (optinal)
  struct sockaddr_in remote_address;
  memset (&remote_address, 0, sizeof (remote_address));

  remote_address.sin_family = PF_INET;
  remote_address.sin_port = htons (atoi (argv[2]));
  remote_address.sin_addr.s_addr = inet_addr (argv[1]);


  //XXX: step 4: create a connect
  if (connect
      (fd, (struct sockaddr *) &remote_address, sizeof (remote_address)) < 0)
    {
      fprintf (stderr, "connect failed:%s\n", strerror (errno));
      exit (1);
    }

  fprintf (stdout, "connect (%s:%d) successd\n", argv[1], atoi (argv[2]));

  char buffer[BUFFER_SIZE];
  ssize_t n;
  ssize_t written;

  for (;;)
    {
      if ((n = read (fd, buffer, BUFFER_SIZE)) < 0)
    {
   fprintf (stderr, "read failed:%s\n", strerror (errno));
   exit (1);

    }
      else if (n == 0)
    {
   break;
    }
      else
    {
   buffer[n] = '\0';
   fprintf (stdout, "%s\n", buffer);
   fflush (stdout);
    }


      while (fgets (buffer, BUFFER_SIZE, stdin) != NULL)
    {
    again:
   if ((written = write (fd, buffer, strlen(buffer))) < 0)
     {
       if (errno == EINTR)
  {
    fprintf (stderr, "write was closed by signal\n");
    goto again;
  }
       else
  {
    fprintf (stderr, "write failed:%s\n", strerror (errno));
    exit (1);
  }
     }
   else
     {
       fprintf (stdout, "write %d bytes to remote address:(%s:%d)\n",
         written, argv[1], atoi (argv[2]));
     }

   //XXX: step 7: create a read
    }
    }
  close (fd);
  return 0;
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved