Socket網絡編程--聊天程序(3)


  上一小節,已經講到可以每個人多說話,而且還沒有限制,簡單的來說,我們已經完成了聊天的功能了,那么接下來我們要實現什么功能呢?一個聊天程序至少應該支持一對多的通訊吧,接下來就實現多個客戶端往服務器發送數據,和服務器向多個客戶端發送數據。

  多對一,單向,各個客戶端都可以向服務器發送數據

  close函數

  #include <unistd.h>

  int close(int sockfd); //用於關閉所打開的Socket套接字 返回值:如果為0表示成功,-1表示失敗

  處理的辦法是每一個客戶端連接到服務器,此時服務器每個客戶端對伊一個進程進行處理。

  client.c

  這里的client.c總體沒有修改太多,就修改了幾個bug而已。

  server.c

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 #include <netdb.h>
  6 #include <sys/types.h>
  7 #include <sys/socket.h>
  8 #include <sys/time.h>
  9 #include <sys/un.h>
 10 #include <sys/ioctl.h>
 11 #include <sys/wait.h>
 12 #include <netinet/in.h>
 13 #include <arpa/inet.h>
 14 
 15 
 16 #define SERVER_PORT 12138
 17 #define BACKLOG 20
 18 #define MAX_CON_NO 10
 19 #define MAX_DATA_SIZE 4096
 20 
 21 int main(int argc,char *argv[])
 22 {
 23     struct sockaddr_in serverSockaddr,clientSockaddr;
 24     char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE];
 25     int sendSize,recvSize;
 26     int sockfd,clientfd;
 27     int on=1;
 28     socklen_t sinSize=0;
 29     char username[32];
 30     int pid;
 31 
 32     if(argc != 2)
 33     {
 34         printf("usage: ./server [username]\n");
 35         exit(1);
 36     }
 37     strcpy(username,argv[1]);
 38     printf("username:%s\n",username);
 39 
 40     /*establish a socket*/
 41     if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1)
 42     {
 43         perror("fail to establish a socket");
 44         exit(1);
 45     }
 46     printf("Success to establish a socket...\n");
 47 
 48     /*init sockaddr_in*/
 49     serverSockaddr.sin_family=AF_INET;
 50     serverSockaddr.sin_port=htons(SERVER_PORT);
 51     serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
 52     bzero(&(serverSockaddr.sin_zero),8);
 53 
 54     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
 55 
 56     /*bind socket*/
 57     if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-1)
 58     {
 59         perror("fail to bind");
 60         exit(1);
 61     }
 62     printf("Success to bind the socket...\n");
 63 
 64     /*listen on the socket*/
 65     if(listen(sockfd,BACKLOG)==-1)
 66     {
 67         perror("fail to listen");
 68         exit(1);
 69     }
 70 
 71     sinSize=sizeof(clientSockaddr);//注意要寫上,否則獲取不了IP和端口
 72     while(1)//多次accept
 73     {
 74         /*accept a client's request*/
 75         if((clientfd=accept(sockfd,(struct sockaddr  *)&clientSockaddr, &sinSize))==-1)
 76         {
 77             perror("fail to accept");
 78             exit(1);
 79         }
 80         printf("Success to accpet a connection request...\n");
 81         printf(" %s:%d join in!\n",inet_ntoa(clientSockaddr.sin_addr),ntohs(clientSockaddr.sin_port));
 82 
 83         if((pid=fork())<0)
 84         {
 85             perror("fork error\n");
 86         }
 87         else if(pid==0)/*child*/
 88         {
 89             while(1)
 90             {
 91                 /*receive datas from client*/
 92                 if((recvSize=recv(clientfd,recvBuf,MAX_DATA_SIZE,0))==-1)
 93                 {
 94                     perror("fail to receive datas");
 95                     exit(1);
 96                 }
 97                 printf("Client:%s\n",recvBuf);
 98                 memset(recvBuf,0,MAX_DATA_SIZE);
 99             }
100         }
101         /*send datas to client*/
102         /* 本程序不發送
103         while(1)
104         {
105             fgets(sendBuf,MAX_DATA_SIZE,stdin);
106             if((sendSize=send(clientfd,sendBuf,strlen(sendBuf),0))!=strlen(sendBuf))
107             {
108                 perror("fail to send datas");
109                 exit(1);
110             }
111             printf("Success to send datas\n");
112             memset(sendBuf,0,MAX_DATA_SIZE);
113         }
114         */
115     }
116     close(sockfd);
117 
118     return 0;
119 }

  下面截取運行結果

  

  一對多,server端向各個客戶端發送數據(廣播)

  接下來就趁熱打鐵吧,隨便完成服務器向各個客戶端發送數據,進而實現相互通訊。實現的技術細節是使用一個數組保存每次客戶端連接的套接字。然后如果要服務器發送數據,就遍歷數組中的所有客戶端套接字,然后對每個套接字進行send數據。

  出現的問題,由於使用的是阻塞方式,所以要創建多進程,然而使用fork創建的進程是完整的拷貝父進程,所以其他進程accept一個新的連接后修改保存套接字的數組是不影響其他進程的數據的。查了一下,說有個vfork函數可以是父進程和子進程共享數據,但是最后發現,子進程是優先於父進程執行的,而且要子進程執行完后才會執行父進程。所以這個辦法不行。一想進程間通信那么多辦法總有可以的。就想到了使用信號處理。

  signal函數

  #include <signal.h>

  void (*signal(int signo, void (*func)(int)))(int); //signo是信號,func是捕獲該信號后要處理的函數,稱為“信號處理程序 signal handler” 我習慣叫這個函數為 注冊函數,它就好像是對signo定義一個處理函數

  kill函數(kill函數是將信號發送給進程或進程組,一個相似的函數raise是給進程本身發送信號的)

  #include <signal.h>

  int kill(pid_t pid, int signo);    //成功返回0,出錯返回-1 。pid>0 將該信號發送給進程ID為pid的進程, pid==0 將信號發給與本身進程屬於同一個進程組的所有進程。 pid<0 將信號發送給其進程組ID等於pid的絕對值。

  int raise(int signo);  //成功返回0,出錯返回-1

  好了,廢話不說了,直接給代碼。

  client.c

  這里的client代碼與前面的基本相同。

  server.c

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <errno.h>
  4 #include <string.h>
  5 #include <netdb.h>
  6 #include <sys/types.h>
  7 #include <sys/socket.h>
  8 #include <sys/time.h>
  9 #include <sys/un.h>
 10 #include <sys/ioctl.h>
 11 #include <sys/wait.h>
 12 #include <netinet/in.h>
 13 #include <arpa/inet.h>
 14 
 15 
 16 #define SERVER_PORT 12138
 17 #define BACKLOG 20
 18 #define MAX_CON_NO 10
 19 #define MAX_DATA_SIZE 4096
 20 
 21 static void sig_usr1(int singno)
 22 {
 23     exit(1);
 24 }
 25 
 26 int main(int argc,char *argv[])
 27 {
 28     struct sockaddr_in serverSockaddr,clientSockaddr;
 29     char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE];
 30     int sendSize,recvSize;
 31     int sockfd,clientfd;
 32     int on=1;
 33     socklen_t sinSize=0;
 34     char username[32];
 35     int pid;
 36     int Queue[MAX_CON_NO+1];
 37     int queue_ptr;
 38     int i;
 39 
 40     if(argc != 2)
 41     {
 42         printf("usage: ./server [username]\n");
 43         exit(1);
 44     }
 45     strcpy(username,argv[1]);
 46     printf("username:%s\n",username);
 47 
 48     /*establish a socket*/
 49     if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1)
 50     {
 51         perror("fail to establish a socket");
 52         exit(1);
 53     }
 54     printf("Success to establish a socket...\n");
 55 
 56     /*init sockaddr_in*/
 57     serverSockaddr.sin_family=AF_INET;
 58     serverSockaddr.sin_port=htons(SERVER_PORT);
 59     serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY);
 60     bzero(&(serverSockaddr.sin_zero),8);
 61 
 62     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
 63 
 64     /*bind socket*/
 65     if(bind(sockfd,(struct sockaddr *)&serverSockaddr,sizeof(struct sockaddr))==-1)
 66     {
 67         perror("fail to bind");
 68         exit(1);
 69     }
 70     printf("Success to bind the socket...\n");
 71 
 72     /*listen on the socket*/
 73     if(listen(sockfd,BACKLOG)==-1)
 74     {
 75         perror("fail to listen");
 76         exit(1);
 77     }
 78 
 79     sinSize=sizeof(clientSockaddr);//注意要寫上,否則獲取不了IP和端口
 80     queue_ptr=0;
 81     while(1)//多次accept
 82     {
 83         /*accept a client's request*/
 84         if((clientfd=accept(sockfd,(struct sockaddr  *)&clientSockaddr, &sinSize))==-1)
 85         {
 86             perror("fail to accept");
 87             exit(1);
 88         }
 89         printf("Success to accpet a connection request...\n");
 90         printf(">>>>>> %s:%d join in!\n",inet_ntoa(clientSockaddr.sin_addr),ntohs(clientSockaddr.sin_port));
 91         Queue[queue_ptr++]=clientfd;
 92 
 93         if(pid!=0)
 94         {
 95             kill(pid,SIGUSR1);
 96         }
 97 
 98         if((pid=fork())<0)
 99         {
100             perror("fork error\n");
101         }
102         else if(pid==0)/*child*/
103         {
104             while(1)
105             {
106                 /*receive datas from client*/
107                 if((recvSize=recv(clientfd,recvBuf,MAX_DATA_SIZE,0))==-1)
108                 {
109                     perror("fail to receive datas");
110                     exit(1);
111                 }
112                 printf("Client:%s\n",recvBuf);
113                 memset(recvBuf,0,MAX_DATA_SIZE);
114             }
115         }
116 
117         if((pid=fork())<0)
118         {
119             perror("fork error");
120         }
121         else if(pid==0)//child
122         {
123             /*send datas to client*/
124             signal(SIGUSR1,sig_usr1);
125             printf("現在有%d個人\n",queue_ptr);//沒有考慮斷開的問題
126             while(1)
127             {
128                 fgets(sendBuf,MAX_DATA_SIZE,stdin);
129                 for(i=0;i<queue_ptr;i++)
130                 {
131                     if((sendSize=send(Queue[i],sendBuf,strlen(sendBuf),0))!=strlen(sendBuf))
132                     {
133                         perror("fail to send datas");
134                         exit(1);
135                     }
136                     else
137                     {
138                         printf("Success to send datas\n");
139                     }
140 
141                 }
142                 memset(sendBuf,0,MAX_DATA_SIZE);
143             }
144 
145         }
146     }
147     close(sockfd);
148 
149     return 0;
150 }

  貼一張運行時的截圖

  一些關閉的操作沒有處理好,反正功能是實現了,容錯處理以后再慢慢改吧,下一節將要講用select來代替這個處理操作。  

 

  本文地址: http://www.cnblogs.com/wunaozai/p/3870258.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM