SO_REUSEADDR選項
在上一篇文章的最后我們貼出了一個簡單的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狀態消失就可以重啟服務器。
/*設置地址重復使用*/
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);
}
}
啟動服務器端,使用多個客戶端進行連接,可以看到服務器能夠同時處理多個連接:
實現一個P2P簡單聊天程序
為了實現聊天的功能,客戶端與服務器端都需要有一個進程來讀取連接,另一個進程來處理鍵盤輸入。使用fork()來完成這個簡單的聊天程序。
客戶端程序:
//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;