前面实现的daytime服务器使用的单进程同步模型,处理完一个连接后才能处理下一个连接,这是最简单的服务器模型;
在处理并发请求时,这种模型的效率十分低下,为了利用多核CPU的性能,我们可以为每个连接fork一个子进程来进行处理,这就是多进程同步模型
下面就是多进程同步模型的daytime服务端实现方式:
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#define MAXLINE 4096
int main(int argc, char **argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
pid_t childpid;
// socket函数创建了一个网际(AF_INET)字节流(SOCK_STREAM)套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 设置套接字地址结构的地址族、IP地址和端口
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13);
// bind函数绑定地址结构到创建的套接字
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// listen函数把该套接字转换成一个监听套接字
// 1024指定系统内核允许在这个监听描述符上排队的最大客户连接数
listen(listenfd, 1024);
for ( ; ; )
{
// 阻塞在accept函数上, 等待客户进行连接
// 当连接建立后, 返回一个已连接描述符
connfd = accept(listenfd, NULL, NULL);
// fork会创建一个子进程, 并且会有2个返回值, 在子进程中返回0
// 在父进程中会返回子进程的pid
if((childpid = fork()) == 0)
{//这里表明在子进程中了
// '关闭'监听套接字, 注意这并不会真正关闭
// 因为系统对每个套接字都维护着一个引用计数, fork后监听套接字被
// 父子进程共享, 其引用计数为2, 真正的关闭发生在引用计数为1的时候
// 这里仅仅是将监听套接字的引用计数减1
close(listenfd);
// 获取当前时间, 并转换为直观可读的时间格式
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
// 把时间发送给客户, 然后关闭当前连接
write(connfd, buff, strlen(buff));
sleep(10);
close(connfd);
exit(0);
}
// 同上面一样, 这并不会导致服务端发送FIN分节
// 除非子进程先对已连接套接字进行了关闭操作
close(connfd);
}
close(listenfd);
}