在上一篇文章的最後咱們貼出了一個簡單的C/S通訊的例程。在該例程序中,使用"Ctrl+c"結束通訊後,服務器是沒法當即重啓的,若是嘗試重啓服務器,將被告知:服務器
bind: Address already in use網絡
緣由在於服務器從新啓動時須要綁定地址:併發
bind (listenfd , (struct sockaddr*)&servaddr, sizeof(servaddr));
而這個時候網絡正處於TIME_WAIT的狀態,只有在TIME_WAIT狀態退出後,套接字被刪除,該地址才能被從新綁定。TIME_WAIT的時間是兩個MSL,大約是1~4分鐘。若每次服務器重啓都須要等待TIME_WAIT結束那就太不合理了,好在選項SO_REUSEADDR可以解決這個問題。
服務器端儘量使用REUSEADD,在bind()以前調用setsockopt來設置SO_REUSEADDR套接字選項,使用SO_REUSEADDR選項能夠使沒必要等待TIME_WAIT狀態消失就能夠重啓服務器。socket
/*設置地址重複使用*/ int on = 1; //on爲1表示開啓 if(setsockopt(listenfp ,SOL_SOCKET,SO_REUSEADDR,&on,sieof(on))<0) ERR_EXIT("setsockopt error");
在上一篇文章例程中,服務器端只可以鏈接一個客戶端,並不能處理多個客戶端的鏈接。緣由在於服務器使用accept從已鏈接隊列中獲取一個鏈接後,便進入了對該鏈接的服務中,處於while循環狀態。當一個新的客戶端鏈接已經放入已鏈接隊列時,服務器並不能執行到accpet的代碼去獲取隊列中的鏈接。
爲了解決這個問題,咱們能夠fork()一個子進程,讓子進程來處理一個客戶端的鏈接,而父進程循環執行accept的代碼,獲取新的鏈接:函數
int conn ; while(1) { conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn <0) ERR_EXIT("accept error"); else printf("鏈接到服務器的客戶端的IP地址是:%s,端口號是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port)); pid_t pid ; pid = fork();//建立一個新進程 if (pid ==0) //子進程 { close(listenfd); //子進程不須要監聽套接字,將其關閉 /*循環獲取數據、發送數據*/ char recvbuf[1024]; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(conn,recvbuf ,sizeof(recvbuf)); fputs(recvbuf,stdout); write(conn,recvbuf,sizeof(recvbuf)); } exit(EXIT_SUCCESS); } if(pid >0) //父進程 { close(conn);//父進程無需該鏈接套接字,它的任務是執行accept獲取鏈接 } else { close(conn); } }
啓動服務器端,使用多個客戶端進行鏈接,能夠看到服務器可以同時處理多個鏈接:
code
爲了實現聊天的功能,客戶端與服務器端都須要有一個進程來讀取鏈接,另外一個進程來處理鍵盤輸入。使用fork()來完成這個簡單的聊天程序。
客戶端程序:blog
//p2pcli.c #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<signal.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) void handler() { exit(EXIT_SUCCESS); } int main() { /*建立一個套接字*/ int sock = socket(AF_INET,SOCK_STREAM,0); if(sock == -1) { ERR_EXIT("socket"); } /*定義一個地址結構*/ struct sockaddr_in servaddr; memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5888); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); /*進行鏈接*/ if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0) { ERR_EXIT("connect"); } else { printf("鏈接成功\n"); } pid_t pid ; pid = fork(); if(pid == -1) ERR_EXIT("fork"); if(pid == 0) //子進程複製接收數據並顯示出來 { char recvbuf[1024]={0}; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(sock ,recvbuf,sizeof(recvbuf)); if(ret == -1) { ERR_EXIT("read"); } if(ret == 0) //鏈接關閉 { printf("鏈接關閉\n"); kill(getppid(),SIGUSR1); break; } else { printf("接收到信息:"); fputs(recvbuf,stdout); } } } else //父進程負責從鍵盤接收輸入併發送 { signal(SIGUSR1,handler); char sendbuf[1024]={0} ; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(sock,sendbuf,strlen(sendbuf)); memset(&sendbuf,0,sizeof(sendbuf)); } } close(sock); return 0; }
服務器端程序:隊列
// p2pser.c #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<signal.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #define ERR_EXIT(m)\ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) /*信號處理函數*/ void handler(int sig) { exit(EXIT_SUCCESS); } int main() { /* 建立一個套接字*/ int listenfd= socket(AF_INET ,SOCK_STREAM,0); if(listenfd==-1) ERR_EXIT("socket"); /*定義一個地址結構並填充*/ struct sockaddr_in addr; addr.sin_family = AF_INET; //協議族爲ipv4 addr.sin_port = htons(5888); //綁定端口號 addr.sin_addr.s_addr = htonl(INADDR_ANY);//主機字節序轉爲網絡字節序 /*重複使用地址*/ int on = 1; if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0) { ERR_EXIT("setsockopt"); } /*將套接字綁定到地址上*/ if(bind(listenfd,(const struct sockaddr *)&addr ,sizeof(addr))==-1) { ERR_EXIT("bind"); } /*監聽套接字,成爲被動套接字*/ if(listen(listenfd,SOMAXCONN)<0) { ERR_EXIT("Listen"); } struct sockaddr_in peeraddr; socklen_t peerlen = sizeof(peeraddr); int conn ; conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen); if(conn <0) ERR_EXIT("accept error"); else printf("鏈接到服務器的客戶端的IP地址是:%s,端口號是:%d\n",inet_ntoa(peeraddr.sin_addr),htons(peeraddr.sin_port)); pid_t pid ; pid = fork();//建立一個新進程 if(pid == -1) { ERR_EXIT("fork"); } if(pid == 0)//子進程 { signal(SIGUSR1,handler); char sendbuf[1024] = {0}; while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) { write(conn,sendbuf,sizeof(sendbuf)); memset(sendbuf,0,sizeof(sendbuf)); } exit(EXIT_SUCCESS); } else //父進程 用來獲取數據 { char recvbuf [1024]={0}; while(1) { memset(recvbuf,0,sizeof(recvbuf)); int ret = read(conn ,recvbuf,sizeof(recvbuf)); if(ret == -1) { ERR_EXIT("read"); } if(ret == 0) //對方已關閉 { printf("對方關閉\n"); break; } fputs(recvbuf,stdout); } kill(pid,SIGUSR1); exit(EXIT_SUCCESS); } /*關閉套接字*/ close(listenfd); close(conn); return 0;