博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
splice系统调用实现的TCP代理
阅读量:5755 次
发布时间:2019-06-18

本文共 2849 字,大约阅读时间需要 9 分钟。

正如Linus所说,splice实际上是内核空间的read/write,而tee则是内核空间的memcpy,至于sendfile,它只是一种特定的优化,该优化对于可以使用page cache的文件系统有效。

      Linus反复强调buffer,用来回答关于“为何splice和tee都要依赖管道”,也就是说,文件描述符的IO依赖于一种buffer,至于说sendfile为何没有使用buffer,那是因为只有适用于page cache的文件描述符才能使用sendfile,而page cache本身就可以作为一种buffer!
      为何不能做一个系统调用,可以直接将一个socket的数据发往另一个socket呢?不是说不能,而是为了API的稳定性,肯定不能单独为socket这种文件做一个splice,更加合理的是实现一个splice,其文件描述符可以是任意的文件描述符。然而如此一来就要在file_operations结构体里面添加新的适用于任意文件的钩子函数splice from/to,这样不是不可以,而是将一类文件和另一类文件通过splice关联了起来。比如从一个socket写到一个字符设备,第一,socket要知道该字符设备如何被写入,第二,字符设备要知道socket如何被读取...因此正规的splice使用了一个buffer,这就是pipe。总而言之,涉及到两个文件之间内容通信的话,必然需要一个buffer,这里的“必然”一词很有意思,不是基于能不能实现考虑,也不是基于性能考虑,而是基于API的稳定性以及设计的简单性考虑,用户态的read/write接口有一个参数就是buffer,splice虽然是内核态的,也需要一个buffer,内核态的现成buffer是什么呢?是pipe。
      以下是一个最简单的使用splice实现的TCP代理程序,如果不看上面扯的那些,可能对调用两次splice有点失望了,毕竟可以直接splice(sock1, NULL, sock2, NULL, 65535, 0)啊,可是必然需要一个buffer的话,就造一个内核buffer,即pipe,这样pipe+splice不再像buffer+read/write那样亲自干活了,它们变身为一个管理者,只发送指令,之后自己不用做事,静等内核完成内容的转移,没有看出效果的同学们,可能你们的数量量太小了,毕竟在内核空间copy内存和内核空间用户态之间copy内存是不同的,除了一系列的处理器检查之外,缺页处理也是一大户啊。下面是代码,没有异常处理,没有错误判断,直奔主题(splice代理即无极变速代理):

#define _GNU_SOURCE#include 
#include
#include
#include
#include
#include
int main(int argc,char *argv[]){ int fd, client_fd, server_fd, ret; int yes = 1; struct sockaddr_in addr = {0}; fd = socket(AF_INET,SOCK_STREAM,0); addr.sin_family = AF_INET; addr.sin_port = htons(1234); addr.sin_addr.s_addr = inet_addr("192.168.4.29"); ret = bind(fd,(struct sockaddr *)(&addr),sizeof(struct sockaddr)); ret = setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)); ret = listen(fd,5); socklen_t len = sizeof(addr); client_fd = accept(fd,(struct sockaddr*)(&addr),&len); { //接收客户端,连接服务端。 struct sockaddr_in addr = {0}, addr2= {0}; struct in_addr x; inet_aton("192.168.4.28",&x); addr.sin_family = AF_INET; addr.sin_addr = x; addr.sin_port = htons(12345); server_fd = socket(AF_INET,SOCK_STREAM,0); ret = connect(server_fd,(struct sockaddr*)&addr,sizeof(addr)); } { //将客户端数据转发到服务端 struct pollfd pfds[2]; int pfd[2] = {-1}; int ret = -1; pfds[0].fd = client_fd; pfds[0].events = POLLIN|POLLOUT; pfds[1].fd = server_fd; pfds[1].events = POLLIN|POLLOUT; ret = pipe(pfd); while (1) { int efds = -1; if ((efds = poll(pfds,2,-1)) < 0) { return -1; } //两次splice,第一次sock_client->pipe,第二次pipe->sock_server if(pfds[0].revents & POLLIN) { int rncount = splice(pfds[0].fd, NULL, pfd[1], NULL, 65535, SPLICE_F_MORE); int wncount = splice(pfd[0], NULL, pfds[1].fd, NULL, 65535, SPLICE_F_MORE); } } }}
我不晓得有没有人使用这种方式实现,因为我只关注OpenVPN,但是我还是真觉得这种方式比较靠谱,因为所谓的代理有两种:

1.一种是为了去控制。代理不想让你直接的访问目标,需要做一些前置工作,比如ACL,比如流控等等。
2.另一种是为了摆脱控制。为了防止内部人员随意访问外部,封掉了端口,但是你可以使用代理访问外部。

除此之外,代理就是一个转发器,作为转发功能,和以上两点无关,而以上两点,完全可以在while之前完成!
 本文转自 dog250 51CTO博客,原文链接:
http://blog.51cto.com/dog250/1334495

转载地址:http://gvjkx.baihongyu.com/

你可能感兴趣的文章
linux命令详解:cat命令
查看>>
不是你不够好,只是你给的不是我想要的
查看>>
js 的空值判断程序
查看>>
node.js module初步理解
查看>>
管理人员的自我压力管理
查看>>
WPF: 旋转Thumb后,DragDelta移动距离出错的解决
查看>>
C#用正则表达式一键Unicode转UTF8(解决LitJson中文问题)
查看>>
jps命令使用
查看>>
ViewState与Session
查看>>
VS中行号对齐的辅助线(虚线)去除
查看>>
深度优先遍历与广度优先遍历的区别
查看>>
CentOS 5.5安装SVN(Subversion)
查看>>
[nowCoder] 完全二叉树结点数
查看>>
urlrewrite使用地址重写
查看>>
linux系统加快大文件的写入速度
查看>>
HDFS DataNode 设计实现解析
查看>>
【前端也要学点算法】快速排序的JavaScript实现
查看>>
jsp的&lt;%%&gt;
查看>>
左右PHP自增力、神秘递减操作
查看>>
android 44 SQLiteOpenHelper
查看>>