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