上一讲中的简单时间获取服务器是一个迭代服务器,对于获取时间来说够用了。迭代服务器有这样的特点:同一时间只能给一个客户服务。也就是说,如果某一时刻服务器与某个客户正在连接,其它客户必须等到上一个客户与服务器断开连接后才能连接成功。这对于需要花长时间处理客户请求的服务器来说并不适用。
解决办法是将服务器改为并发服务器。这样,即使有一个客户正在和服务器连接,其它的客户也能与服务器建立连接获得服务。最简单的办法就是fork函数。

1、fork函数

fork函数是UNIX中派生进程的唯一方法,定义在<unistd.h>头文件中:

pid_t fork(void);

这个函数的奇特之处在于一次调用,两次返回。在父进程中返回子进程的ID,在子进程中返回0。所以,我们可以通过返回值判断当前进程是父进程还是子进程。

fork函数在子进程中返回0而不是父进程ID的原因是:任何子进程只有一个父进程,而这个父进程的ID可以通过函数getppid获得;但是对于父进程,可以有多个子进程,所以父进程无法通过函数获得子进程ID,如果父进程想跟踪所有子进程的ID,那么父进程必须在每次调用fork时记住子进程ID。

2、并发服务器

上面介绍了fork函数,可以通过调用fork函数达到并发的效果。下面是典型的并发服务器框架:

pid_t pid;
int listenfd,connfd;
listenfd=socket(...);
bind(listenfd,...);
listen(listenfd,...);
for(;;)
{connfd=accept(listenfd,...);if((pid=fork)==0){close(listenfd);//关闭listenfddoit(connfd);//处理请求close(connfd);//关闭子进程connfdexit(0);//子进程退出}close(onnfd);//关闭父进程connfd
}

这个框架中究竟发生了什么?可以用下面的图示展示出来。

(1)服务器调用socket、bind和listen函数后,处于监听状态,等待客户发送连接请求,这个时候服务器还没有调用accept:

(2)这时服务器只有一个套接字描述符listenfd,即监听描述符。当服务器中accept函数返回时,就有了两个套接字描述符listenfd和connfd,套接字描述符connfd是一个连接描述符,可以进行读写操作:

(3)这个时候客户与服务器建立了连接。随后服务器调用fork函数,自己变成父进程,复制自己形成一个子进程。由于两个进程相同,所以客户与两个进程的两个connfd都处于连接状态:

(4)然后,父进程中关闭连接套接字connfd,子进程关闭监听套接字listenfd:

这就达到了我们希望的状态,由子进程处理客户请求,父进程继续监听。

需要注意的是,close函数会导致发送一个FIN,随后TCP连接应该终止。那为什么父进程中close(connfd)后没有终止与客户的连接呢?每个文件描述符都有一个引用计数,是当前打开着的引用该文件的描述符的个数,只有引用计数为0时文件才会关闭。而fork函数执行后,对connfd套接字的引用计数是2,父进程关闭connfd后引用计数变为1,所以不会关闭连接。然而在子进程中,关闭connfd后引用计数变为0,会关闭与客户的连接。

3、时间获取程序的改进

这里我们只需要改进服务器程序,将它变成一个并发服务器。代码如下:

#include <sys/socket.h>
#include <string.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#define MAXLINE 1024int main(int argc,char *argv[])
{int listenfd;struct sockaddr_in servaddr;char buff[MAXLINE];time_t ticks;if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0){printf("socket error\n");return 0;}bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(5000);servaddr.sin_addr.s_addr=htonl(INADDR_ANY);if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0){printf("bind error\n");return 0;}if(listen(listenfd,5)<0){printf("listen error\n");return 0;}int connfd;socklen_t len;struct sockaddr_in cliaddr;pid_t pid;for(;;){len=sizeof(cliaddr);if((connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&len))<0){printf("accept error\n");return 0;}if((pid=fork())==0){if(close(listenfd)<0){printf("close listenfd error\n");return 0;}printf("[PID]%ld Receive a connection from:%s.%d\n",(long)getpid(),inet_ntop(AF_INET,&cliaddr.sin_addr,buff,sizeof(buff)),ntohs(cliaddr.sin_port));ticks=time(NULL);snprintf(buff,sizeof(buff),"%.24s\r\n",ctime(&ticks));if(write(connfd,buff,strlen(buff))<0){printf("write error\n");return 0;}printf("[PID]%ld sleep 5s.\n",(long)getpid());sleep(5);printf("[PID]%ld sleep done.\n",(long)getpid());if(close(connfd)<0){printf("close child connfd error\n");return 0;}return 0;}if(close(connfd)<0){printf("close parent connfd error\n");return 0;}}
}

这里我们为了增加服务器处理每个请求的时间,加了sleep(5),会看到,服务器在处理一个客户的请求时,也会与另一个客户建立连接处理请求。运行结果如下:

(1)打开服务器:

(2)客户1连接(进程ID是3594):

(3)客户2连接(进程ID是3596):

(4)下图是服务器处理情况:

可以看到,在客户1(进程ID是3594)仍在sleep的时候,服务器接受了客户2(进程ID是3596)的连接。

UNIX网络编程笔记(3):简单的并发服务器相关推荐

  1. UNIX网络编程笔记(4):简单的回射程序

    上一讲中我们通过调用fork函数实现了一个简单的并发时间获取服务器.这是一个简单的并发服务器框架,然而这里使用这个框架实现一个简单的回射服务器会出现一个问题,这个问题就是僵尸子进程. 1.回射程序 下 ...

  2. 进程fork和exec ---Unix网络编程笔记

    进程fork和exec ---Unix网络编程笔记 fork 一次调用,两次返回 fork的两个典型用法 最简单的并发服务器---fork子进程 exec -Unix网络编程笔记) fork #inc ...

  3. linux网络编程(二)高并发服务器

    linux网络编程(二)高并发服务器 错误处理 高并发服务器 多进程并发服务器 客户端 错误处理 #include "wrap.h"int Bind(int fd, const s ...

  4. UNIX网络编程笔记(2):一个简单的时间获取程序

    这一讲通过一个简单的时间获取程序简单介绍套接字编程. 1.套接字API 1.1.套接字地址结构 上一讲中介绍了TCP的一些内容,知道了一个套接字对唯一标识了网络中的一个TCP连接,而一个套接字标识了一 ...

  5. Unix 网络编程(四)- 典型TCP客服服务器程序开发实例及基本套接字API介绍

    转载:http://blog.csdn.net/michael_kong_nju/article/details/43457393 写在开头: 在上一节中我们学习了一些基础的用来支持网络编程的API, ...

  6. Linux网络编程(六)-高并发服务器03-I/O多路复用03:epoll【红黑树;根节点为监听节点】【无宏FD_SETSIZE限制;不需每次都将要监听的文件描述符从应用层拷贝到内核;不需遍历树】

    一.epoll概述 epoll的本质是一个[红黑树].监听结点为根节点. 大量并发,少量活跃效率高. epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并 ...

  7. Linux网络编程3——多进/线程并发服务器

    视频链接 黑马程序员-Linux网络编程_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA?p=37 目录 一.高并发服务器 1.1 图 ...

  8. 网络编程-C语言实现多进程并发服务器

    在Linux操作系统下,服务器通过fork()复制已调用的进程来创建子进程,以此来实现多进程并发服务器 功能:客户端发送任意字符串,服务器端将字符串小写转大写toupper();并返回给客户端,客户端 ...

  9. UNIX网络编程笔记(7):回射程序的UDP版本

    1.UDP简介 UDP是一个简单的传输层协议,应用进程往一个UDP套接字写入数据,随后被封装到一个UDP数据报,进而又被封装到一个IP数据报,然后发送到目的地.UDP不保证UDP数据报会最终到达目的地 ...

最新文章

  1. 线段树练习 3P3372 【模板】线段树 1
  2. 青少年学python第六节_青少年学Python(第2册)
  3. Android在ListView中嵌套一个GridView时只显示一行的原因及解决方法
  4. Mysql流程控制结构
  5. 【刷题记录】排列dp
  6. 445端口 mysql_关于如何关闭window端口445的详细介绍
  7. xshell 上下左右键乱码和退格键失效
  8. String字符串拼接小例
  9. 2021-2025年中国超本地服务行业市场供需与战略研究报告
  10. Python 爬取 201865 条《隐秘的角落》弹幕,发现看剧不如爬山?
  11. 输入矩阵java_java如何输入一个自定义矩阵
  12. scrapy基础知识之防止爬虫被反的几个策略::
  13. HDU 3949 XOR (线性基第k小)题解
  14. springcloud-gateway路由配置和跨域配置
  15. SpringBoot之Junit单元测试
  16. 电源php38电路,六款uc3842开关电源电路图分享
  17. Linux中使用sed命令替换字符串
  18. 2048小游戏后端的实现
  19. Kafka安全认证授权配置
  20. 全球四大卫星导航系统

热门文章

  1. 如果有一天生你养你的两个人都走了
  2. 6.22打包建立ISS虚拟目录,安装完运行你想运行的程序
  3. [导入]Google Earth坐标集(能更看清这个世界喽!)
  4. mvp的全称_是让人提神醒脑的 MVP、MVVM 关系精讲!
  5. 用php求矩形周长,PHP实现的简单三角形、矩形周长面积计算器分享
  6. linux usleep 线程控制权_linux多线程同步—信号量
  7. NYOJ 10 skiing
  8. 宏定义 #define 和常量 const 的区别
  9. php class使用方法,php的类使用方法问题
  10. 连续内存分区式内存管理