IO与服务器
1、缓存 IO:
(1)什么时缓冲IO
缓存 IO 又被称作标准 IO,大多数文件系统的默认 IO 操作都是缓存 IO。
在 Linux 的 缓存 IO 机制中,操作系统会将 IO 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。
(2)缺点
(1)数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据
(2)拷贝操作所带来的 CPU 以及内存开销是非常大的。
网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作。
刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:
第一阶段:等待数据准备 (Waiting for the data to be ready)。
第二阶段:将数据从内核拷贝到进程中 (Copying the data from the kernel to theprocess)。
对于socket流而言,
第一步:通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
第二步:把数据从内核缓冲区复制到应用进程缓冲区。
(3)IO模型
网络应用需要处理的无非就是两大类问题,网络IO,数据计算。相对于后者,网络IO的延迟,给应用带来的性能瓶颈大于后者。
1.阻塞IO (blocking IO):最常用、最简单、效率最低
阻塞模式下用户进程需要等待两次,一次为等待io中的数据就绪,一次是等
待内核把数据拷贝到用户空间 。
实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用send()的同时,进程将被阻塞,在此期间,进程将无法执行任何运算或响应任何的网络请求。
一个简单的改进方案是在服务器端使用多线程(或多进程)。
举例:创建三个有名管道,并向里面输入数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
int fd1 = open("./f1", O_RDWR);
int fd2 = open("./f2", O_RDWR);
int fd3 = open("./f3", O_RDWR);
char buf[64] = {
0};
int ret;
while(1)
{
ret = read(fd1, buf, sizeof(buf));
if(ret != -1)
{
printf("fd1=%s\n", buf);
}
memset(buf, 0, sizeof(buf));
ret = read(fd2, buf, sizeof(buf));
if(ret != -1)
{
printf("fd2=%s\n", buf);
}
memset(buf, 0, sizeof(buf));
ret = read(fd3, buf, sizeof(buf));
if(ret != -1)
{
printf("fd3=%s\n", buf);
}
memset(buf, 0, sizeof(buf));
}
close(fd1);
close(fd2);
close(fd3);
return 0;
}
代码运行:
多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。
具体使用多进程还是多线程,并没有一个特定的模式。传统意义上,进程的开销要远远大于线程,所以如果需要同时为较多的客户机提供服务,则不推荐使用多进程;如果单个服务执行体需要消耗较多的CPU资源,譬如需要进行大规模或长时间的数据运算或文件访问,则进程较为安全。
并发服务器模型 TCP
{
多进程并发服务器 tcpserver_fork.c
基本原理: 每连接一个客户端,创建一个子进程,子进程负责处理connfd(客户请求) 父进程处理sockfd(连接请求)。
细节处理:
回收子进程资源多线程并发服务器 tcpserver_pthread.c
基本原理: 每连接一个客户端,创建一个子线程,子线程负责处理connfd(客户请 求),主线程处理sockfd(连接请求)。
//注意:子线程结束时,需要进行资源回收
}
上述多线程的服务器模型似乎完美的解决了为多个客户机提供问答服务的要求,但其实并不 尽然。如果要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统 资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。
并且尤其是对于长连接来说,线程的资源一直不会释放,如果后面陆续有很多连接的话,就 会造成性能上的瓶颈。
总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。
例:
<1>多进程并发tcp服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>