承接之前博客的基於linux系統的CS模型實現,這裏再修改,CS模型中的服務器是迭代服務器,每次只能服務一個客戶,我們並不希望整個服務器被一個客戶單獨長期佔用,而是希望服務多個客戶,這裏就用到了fork一個子進程來服務每個客戶。這也是fork兩個典型用法之一。
fork函數用法:
#include <unistd.h>
pid_t fork(void);
返回:在子進程爲0,父進程返回子進程ID
理解fork最困難的地方:調用一次,返回兩次,它在調用進程(父進程)中返回子進程ID,子進程返回爲0。
fork之後的特性:父進程中調用fork之前的所有描述符在fork返回之後由子進程分享。我們的併發服務器就是利用了這個特點。
併發服務器原理講解
我們用fork實現併發服務器的核心代碼:
while(1)
{
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept");
printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0)
{
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);//將子進程退出,要不它會fork()
}
else
close(conn);
}
當一個連接建立,accept返回,服務器接着fork,然後由子程序服務客戶,父進程(也就是服務器)等待另一個連接(通過listenfd),既然新的客戶由子進程提供服務,父進程就關閉已連接套接字,這裏可能會有很多朋友疑惑,父進程關閉了套接字,子進程是否會收到影響,其實是不會的,這裏的close只是將引用-1,子進程可以繼續使用。
我們可以在子進程中顯示的關閉已連接套接字,這一點並非必須,因爲下一個語句exit,它的部分工作就是關閉所有由內核打開的描述符。
程序service:
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void do_service(int conn)
{
char recvbuf[1024];
while(1){
memset(recvbuf,0,sizeof(recvbuf));//初始化recvbuf
int ret=read(conn,recvbuf,sizeof(recvbuf));//函數從打開的文件,設備中讀取數據,返回讀取的字節數。
if(ret==0)
{
printf("client_close\n");
break;
}
else if(ret==-1)
{
ERR_EXIT("read");
}
fputs(recvbuf,stdout);
write(conn,recvbuf,ret);//buf中數據被複制到了TCP發送緩衝區
}
}
int main(void)
{
int listenfd;
/*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
ERR_EXIT("socket");
//IPV4地址結構
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;//地址家族
servaddr.sin_port=htons(5188);//端口,主機轉網絡
/*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");*/
/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
//地址reuseaddr
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
ERR_EXIT("setsockopt");
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind");
if(listen(listenfd,SOMAXCONN)<0)
ERR_EXIT("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen =sizeof(peeraddr);//typedef int socklen_t
int conn;//已連接套接字
pid_t pid;
while(1)
{
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept");
printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0)
{
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);//將子進程退出,要不它會fork()
}
else
close(conn);
}
return 0;
}
cilent:
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main()
{
int sock;
/*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
ERR_EXIT("socket");
//IPV4地址結構
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;//地址家族
servaddr.sin_port=htons(5188);//端口,主機轉網絡
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect");
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin) !=NULL){
write(sock,sendbuf,strlen(sendbuf));//發送
read(sock,recvbuf,sizeof(recvbuf));//接受
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;
}