使用零拷贝函数———sendfile函数以提高服务器性能

一、函数说明

sendfie函数在两个文件描述符之间直接传递数据,其中的操作完全在内核中执行,从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,大大提高了效率,被称为零拷贝。sendfile函数定义如下:

#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t offset, size_t count);

参数说明:

  • in_fd:待读出内容的文件描述符
  • out_fd:待写入内容的文件描述符
  • offset:指定从读入文件流哪个位置开始,NULL则从起始位置
  • count:指定传输的字节数

sendfile成功返回传输的字节数,失败返回-1并设置errno。

注意 —— in_fd文件描述符必须指向一个真正存在的文件,不能是socket或是管道;out_fd则必须指向socket。

 

二、使用sendfile

//使用C实现简单的HTTP服务

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<fcntl.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/sendfile.h>


char * SendHead(int c, int size, char *pathname)   /* 发送头部信息 */
{
	char buff[1024] = {0};
	strcpy(buff, "HTTP/1.1 200 OK\n\r");
	strcat(buff, "Server: myhttpd/1.0\n\r");
	strcat(buff,"Content-Length: ");
	sprintf(buff+strlen(buff),"%d",size);//将size的%d值写入到buff+strlen(buff)处
	strcat(buff, "\n\r");
	strcat(buff, "Content-Type: text/html;charset=utf-8\n\r");
//	strcat(buff, "Set-Cookie: \n\r");
//	strcat(buff, "Via: \n\r");

	strcat(buff,"\n\r\n\r");

	send(c, buff, strlen(buff), 0);
}

void SendError(int c)  /* 错误信息 */
{
	char buff[1024] = {0};
	strcpy(buff, "HTTP/1.1 404 NOTFOUND\n\r");

	send(c, buff, strlen(buff), 0);
}

void AnayRequst(char *recvbuf, char *pathname)  /* 分析客户端请求 */
{
	//GET /index.heml HTTP/1.1
	char *p = strtok(recvbuf, " ");
	p = strtok(NULL, " ");
	strcpy(pathname, "/var/www/html");
	strcat(pathname, p);
}

void SendData(char *pathname, int c)  /* 回馈客户端 */
{
	struct stat st;
	stat(pathname, &st);
	SendHead(c, st.st_size, pathname);

	int fd = open(pathname, O_RDONLY);
	assert(fd != -1);
	
	sendfile(c, fd, NULL, st.st_size);

	close(c);
}

int main()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in ser,cli;
	memset(&ser, 0, sizeof(ser));

	ser.sin_family = AF_INET;
	ser.sin_port = htons(80);
	ser.sin_addr.s_addr = inet_addr("192.168.31.36");

	int res = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));
	assert(res != -1);

	listen(sockfd, 5);

	while(1)
	{
		int len = sizeof(cli);
		int c= accept(sockfd, (struct sockaddr *)&cli, &len);
		if(c < 0)
		{
			continue;
		}
	
		while(1)
		{
			char recvbuf[1024] = {0};
			int n = recv(c, recvbuf, 1023, 0);
			if(n <= 0)
			{
				SendError(c);
				close(c);
				break;
			}

			printf("%s\n",recvbuf);

			char pathname[128] = {0};

			AnayRequst(recvbuf, pathname);

			SendData(pathname, c);
			
			close(c);
		}
	}
}

结果:

 

### 零拷贝技术与页缓存在操作系统或网络传输中的作用及关系 #### 什么是零拷贝技术? 零拷贝(Zero-Copy)是一种减少数据复制次数的技术,在传统的文件传输过程中,通常会经历多次的数据复制操作。具体来说,这些复制包括从磁盘到内核空间、再到用户空间以及最终回到内核空间的过程[^1]。而零拷贝通过优化这一流程,减少了不必要的内存拷贝和上下文切换,从而显著提高性能。 #### 页缓存的作用 页缓存(Page Cache),也称为内核缓冲区,是现代操作系统为了提高磁盘访问效率而引入的一种机制。当应用程序请求读取某个文件时,操作系统并不会立即将该文件的内容返回给应用层,而是先将其加载至页缓存中。如果后续有其他程序再次尝试读取相同部分的数据,则可以直接从高速的内存中获取,而非重新访问较慢的存储设备[^3]。 #### 零拷贝与页缓存的关系 尽管两者都旨在提升系统的整体效能,但它们的工作原理并不完全一致: - **协同工作**:在某些情况下,比如利用 `sendfile` 或者 SG-DMA 实现的大规模数据传送任务里,可以结合使用零拷贝技术和页缓存策略。这意味着即使不需显式地将资料搬移到使用者态区域,仍然能够享受到由前者带来的速度增益效果的同时享受后者所提供的预抓取优势。 - **冲突场景**:然而需要注意的是,并不是所有的场合下都可以同时发挥这两种方法的优势。例如采用直接I/O模式绕过了页面高速缓存子系统之后,虽然避免了一些额外开销,但却失去了可能存在的重复命中所带来的好处。 #### 应用于实际案例分析-Nginx中的实践 以Web服务器软件 Nginx为例说明上述理论的实际运用情况。它内部实现了自己的版本号叫做"zero copy"功能,即所谓的“零副本”。在这种设计思路指导下,Nginx能够在处理静态资源分发的时候极大限度降低CPU负担并加快响应时间——因为整个过程几乎不需要任何手动干预就能自动完成端口之间的高效交换动作。 另外值得注意的一点是在Java领域也有类似的体现形式之一便是FileChannel类里的两个重要成员函数transferTo() 和 transferFrom(), 当底层OS支持相应特性时候他们实际上就是调用了Linux下的sendfile API 来达成目的进而体现出所谓意义上的‘真正的’ zero-copy 行为特征[^2]. ```java @Override public long transferFrom(FileChannel src, long position, long count) throws IOException { return src.transferTo(position, count, this); } ``` 以上代码片段展示了如何借助高级别的抽象接口轻松实现跨通道间快速迁移大量字节流而不必担心传统方式所伴随的巨大代价问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值