承接之前博客的基于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;
}