新生命

其实我也搞别的编程http://www.nnhy.org 技术支持QQ群:10193406

  博客园 :: 首页 :: 联系 :: 订阅 订阅 :: 管理
  17 Posts :: 1 Stories :: 121 Comments :: 2 Trackbacks
    两年前用.net 2.0做了一个反向代理服务器,在这两年时间里,不断修改BUG以及优化性能,使得可用性大大提高。近来碰到一个功能需求,实在无法找出有效的解决办法,只好上来请教各位高人。
    先说说反向代理的工作机理吧。
1、客户端通过浏览器访问反向代理的时候,会发出一个HTTP请求,反向代理收到这个TCP连接的时候,建立一个新的会话用于处理这个请求(BeginAccept、EndAccept);
2、会话对象建立一个从客户端接收数据的委托,开始异步读取数据(BeginRead);
3、取得数据时,进入异步读取的回调函数中,开始处理数据(EndRead);
4、检查反向代理与服务器的连接是否已建立,如果没有建立,那么需要先建立连接(ConnectServer),并建立服务器的异步读取委托(BeginRead);
5、把数据异步写入服务器(BeginWrite);
6、重新建立客户端异步读取委托(BeginRead),回到3;
7、收到服务器返回数据时,处理后,异步写入客户端(BeginWrite);
8、重新建立服务器异步读取委托(BeginRead),回到7;

所有的数据传输,都使用异步来完成,而只需要在3和7处为业务编写数据处理代码即可。
实际上,对于反向代理来说,只需要处理客户端发来的数据就可以了,需要把HTTP的HOST头替换为真实服务器,而对于服务器响应的数据,只需要原样发送给客户端就可以了。

在步骤3中,我们只知道当前收到了客户端发来的数据,而不知道这个数据是不是Http请求头,或者是完整的Http请求头。幸好,对于反向代理来说,不需要关心是否是完整的Http请求头,只需要检查是否是Http请求头,如果是,就修改Host即可。在这里,我假设Http请求的第一个数据包肯定是独立的数据包,不会“粘”在TCP连接中上一次数据的后面,这样就可以直接使用Http协议规定的格式来检查这个数据包是否Http请求头了。虽然这个假设没有什么依据,但它确实非常有效。

程序就这样工作了两年,没有什么问题。

但接下来,问题就出现了,有一个需求,要求能够把服务器返回的页面中的某个字符串替换为指定的字符串。比如我用反向代理指向博客园,我就需要把博客园页面中所有使用了绝对路径的连接修改为指向反向代理服务器的连接。这就要求在步骤7这里处理数据,把数据转为字符串,然后替换链接,然后才发往客户端。

但步骤7每次收到的数据只是一个片段,而不是整个页面的HTML。即使我们再次假设Http响应的第一个数据包是独立的数据包,也只能识别哪些是响应头,哪些是数据体而已。也想过每一段数据转为这一段的字符串进行处理,但是,如果刚好某个字符被网络层拆分到两个TCP数据包里怎么办?还有,想博客园这样使用了gzip的,如果不接受完整个页面的数据,是无法解压的;就算这两种情况都不存在,而网络层刚好在超链接的地方拆分数据包怎么办?

因此,最保守的做法就是拿到整个页面数据再开始处理。也想过Http响应头那里有个Content-Length指明内容长度的,但实际中,很多响应根本就不到这个段。

我查看过HttpListener类和HttpListenerRequest类,尝试从中发现它是如何接受完一次请求(响应)的,可惜这两个类调用了大量NativeAPI,就无法得知了。

还有浏览器,它又是如何得知某次响应是否已经完成的呢?

还请各位高人多多指教!

这个代理已经放到codeplex上,大家有兴趣可以看看:http://www.codeplex.com/XProxy/

还有,不要忘了给我的问题提个解决思路^_^
谢谢!

QQ:99363590
E-Mail:nnhy#vip.qq.com
QQ群:10193406
posted on 2008-03-10 16:08 大石头 阅读(2244) 评论(25)  编辑 收藏 网摘

Feedback

#1楼 2008-03-10 16:14 micYng      
我有一个想法,TCP中的数据流是一个Stream,判断有没有\0这个EOF
  回复  引用  查看    

#2楼 2008-03-10 16:25 死死      
基本的socket编程知识啦
  回复  引用  查看    

#3楼[楼主] 2008-03-10 16:32 大石头      
@micYng

我的确是操作Stream的,但是每接受完一个数据包得到的数据都在字节数组里面了,还要怎么样判断这个Stream?不是很明白

◎死死
还请赐教^_^

  回复  引用  查看    

#4楼 2008-03-10 16:43 死死      
如果远程主机关闭了 Socket 连接并且已接收到所有可用数据,EndRead 方法将立即完成并返回零字节。

  回复  引用  查看    

#5楼 2008-03-10 16:43 jillzhang      
判断结束标记
  回复  引用  查看    

#6楼 2008-03-10 16:43 jillzhang      
ENDFlag
Socket大部分都是这么干的

  回复  引用  查看    

#7楼[楼主] 2008-03-10 17:08 大石头      
◎死死

你说的这个我知道呀。问题是,有些时候,一个tcp会用来发送多次http请求的。还有,就算只传一次,我也不知道我是否收到了所有可用数据呀,我怎么能知道还会不会有下一次EndRead呢。

◎jillzhang
Http中有这个么?还有,这里是异步操作的,这样可行么?

  回复  引用  查看    

#8楼 2008-03-10 17:17 jillzhang      
@大石头
异步操作应该和这个没关系
同步是服务器黑咻黑休的把活干完了,然后再将大的响应发送给客户端,这样客户端就很不爽,因为客户端要长时间处于等待

而异步是服务器干活的同时,就将当前的干活的进度发给客户端,客户端更能知晓服务器现在做什么,而且状态信息比较小,传输快,等干完了,服务器再告诉客户端,俺作完了,给你响应

和结束标志没必然联系吧

  回复  引用  查看    

#9楼 2008-03-10 17:22 jillzhang      
传统的Socket程序,服务端和客户端之间数据传输,靠的是NetStream,而一般情况下,接收端开始可能并不知晓当前的流长度,因为数据流不是一股脑彭一下就到达接收端的,接收端可以一次接收1024字节,这样接收点,网络数据流就向前流动点,接收的再接收,这样很多情况下,数据流在交互频繁的时候会产生排队,第二个消息的数据和第一个消息的数据很可能重叠,我以为你是说如何从数据流中区分出消息来
  回复  引用  查看    

#10楼[楼主] 2008-03-10 17:30 大石头      
呵呵,如果两端都是自己的程序,拿好办,自定义一套协议就可以了。

问题是现在是Http协议呀,好像Http协议里面也没有规定一个tcp不能传多个http请求

  回复  引用  查看    

#11楼 2008-03-10 17:36 jillzhang      
@大石头
http协议的格式头是标准化了的,里面有Content-Length标示内容长度

  回复  引用  查看    

#12楼 2008-03-10 17:42 jillzhang      
@大石头
你可能又说了,有些http消息是没有content-length的,可传统得处理数据流的方式也有两种:
1)在开头标明内容长度,正如有content-length的情况
2)在结尾用固定字符标示结尾。正如没content-length的情况,这种情况采用的是chunked编码,这种编码算法中由一个标明长度为0的chunk结束
每个Chunk有两部分组成,第一部分是该Chunk的长度和长度单位(一般不写),第二部分就是指定长度的内容,每个部分用CRLF隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些没有写的头部内容

  回复  引用  查看    

#13楼 2008-03-10 17:43 jillzhang      
总之就两种:
1)先告诉人家我多长
2)不告诉多长,但你读的时候,我会告诉你我还有没有

就是这种思路,其他的我也不知道了,传统的Socket是根,其他HTTP,SOAP的设计好多都基于这些思想。

  回复  引用  查看    

#14楼 2008-03-10 17:44 死死      
--引用--------------------------------------------------
大石头: ◎死死

你说的这个我知道呀。问题是,有些时候,一个tcp会用来发送多次http请求的。还有,就算只传一次,我也不知道我是否收到了所有可用数据呀,我怎么能知道还会不会有下一次EndRead呢。
--------------------------------------------------------

刚才已经说了 当EndRead返回0 就代表对方调用了shutdown关闭了socket,而且已接收到所有可用数据

正常情况下 一次http tcp连接就一个来回(一个请求 一个返回)
当然貌似http1.x协议可以重用一个tcp发送多次http请求(比如一堆的图片)

但不管它是发送几次,处理起来都是一样的 就是数据分段的问题

  回复  引用  查看    

#15楼 2008-03-10 17:47 jillzhang      
正常情况下 一次http tcp连接就一个来回(一个请求 一个返回)
----------------------------------------------
三次握手没听说么?

  回复  引用  查看    

#16楼 2008-03-10 17:48 jillzhang      
@死死
但不管它是发送几次,处理起来都是一样的 就是数据分段的问题
----------------------------------------
这个说到点子上了

  回复  引用  查看    

#17楼 2008-03-10 17:49 jillzhang      
现实中不光是tcp协议,好多编码都涉及到数据分段,越是底层这东西越重要,做gif编解码也深有感触
  回复  引用  查看    

#18楼 2008-03-10 17:49 死死      

if(EndRead() == 0 )
{
//处理接受到的数据
}
else
{
BeginRead()
}

  回复  引用  查看    

#19楼 2008-03-10 17:51 死死      
--引用--------------------------------------------------
jillzhang: 正常情况下 一次http tcp连接就一个来回(一个请求 一个返回)
----------------------------------------------
三次握手没听说么?
--------------------------------------------------------

这里的来回是指应用层的 httpRequest httpResponse

  回复  引用  查看    

#20楼 2008-03-10 17:55 jillzhang      
@死死
:)
另外你说的EndRead()对于chunked是不可行的,因为它根本不晓得长度,在读取得时候,也无所谓什么End不End的,如果两个Response排队了,1次读取1024,很可能就将第二个ResponseMessage中的部分Byte Buffer,读取到,但此时仍可以进行分段,分段的原理就是基于定义好的Message Define,那具体就是判断EndFlag

  回复  引用  查看    

#21楼 2008-03-10 17:59 jillzhang      
甚至说它不只不适合与chunked,它是不适合1个连接,多个消息的情况下。因为它只能标识我把网络数据流读完了,而网络数据流很笼统,它可能是1个消息,也可能10个,要从中获取到想知道的内容,必须数据分段。你可以将读取到的byte buffer,先存到memory中,再根据具体role进行分段
  回复  引用  查看    

#22楼 2008-03-10 18:00 死死      
晕 扯太多啦
人家楼主就是想知道如何得知一次请求(或响应)的数据已接收完
也就是EndRead()的问题啦
至于chunked分段数据, 那个已经成为标准啦 没什么可谈的

  回复  引用  查看    

#23楼 2008-03-10 18:04 jillzhang      
读完不读完你的是对的
但我想楼主本意应该不在此。读完不读完有什么意义?没有实际含义的

  回复  引用  查看    

#24楼[楼主] 2008-03-11 08:49 大石头      
我大概明白两位的意思,不外乎:
1,请求头或相应头标明数据长度;
2,使用结束符判断是否已完成;

我说了,不少服务器的响应,的确没有Content-Length头,尽管它是标准;
一个tcp通道多次http请求,的确有这么一回事,我做了两年这个程序,碰到了很多这种情况;
你们所说的这个结束标记,我在抓包的过程中,从未发现,这点我需要再确认一下;
死死 的那个办法,貌似不实用,我是异步方式工作的,难道我要等到网络断开EndRead返回0的时候再去处理前面的数据?我记得收到最后一个包离收到0的包间隔很久的,这样的话岂不是程序没有什么效率可言了?还有,如果这个tcp传多个http请求,那就郁闷了。

我只了解http请求的chunked分段,可否跟我说说http响应的chunked分段?

谢谢两位高手^_^

  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1098947




相关文章:

相关链接: