时间:2017-4-3来源:本站原创作者:佚名
张大胖研究TCP/IP已经有段时间了。他终于明白了所谓IP层就是把数据分组从一个主机跨越千山万水搬运到另外一主机,并且这搬运服务一点都不可靠,丢包、重复、失序可以说是家常便饭,怪不得说是“尽力而为”,基本上无所作为。脏活累活只好让TCP来做了,在两个主机的应用(进程)之间通过失败重传来实现可靠性的传输。张大胖经常感慨:这建立一个TCP连接可是相当的复杂,我的程序得先和远端的服务器打个招呼,然后它再给我打个招呼确认,我还得再给它确认下。这还不算完,我们的招呼中还得各自带上各自的序号,这将来传输真正的数据时用到。具体的传输就更麻烦了,什么滑动窗口,什么累积确认、分组缓存、流量控制,简直不是人做的事情。

到了断开连接的时候,还得考虑友好分手!

可是领导竟然让张大胖用这个超级复杂的TCP协议来编程,来设计一个客户端和服务器端的通信系统。张大胖掂量了下自己,觉得肯定搞不定,于是赶紧向自己的好基友,编程大神Bill求救,Bill在“这很简单啊,你去看看TCP/IP协议的RFC,然后用C语言编程实现不就行了吗。”张大胖心想这等于啥也没说,继续“跪求”。Bill终于说:“等着吧,我下周给你”Socket周一,Bill果然带着7、8张软盘来找张大胖了,把软盘的程序分别Copy到了两个电脑里,一个模拟客户端,一个模拟服务器。很快程序运行起来了,两个电脑可以通过TCP通信了。张大胖说:“大神,你是怎么做到的?”Bill说:“TCP协议的确很复杂,我们不能要求每个程序员都去实现建立连接的3次握手,累积确认,分组缓存,这些应该是属于操作系统内核的部分,没必要重复开发,但是对于应用程序来讲,操作系统需要抽象出一个概念,让上层应用去编程。““什么概念?”“socket””为啥叫socket?“"一个比喻而已,就像插座一样,一个插头插进插座,建立了连接。不过我设计这个socket可以理解为(客户端IP,客户端Port,服务器端IP,服务器端Port),对了,Port就是端口,通俗点讲就是一个数字而已"“好像不用port就可以吧,因为我们这是两个机器之间的通信,IP是不是就够了?”Bill说:“看来你忘了,TCP是两个进程之间的通信,客户端上可以有很多进程同时访问多个服务器,服务器上也有多个进程对外提供服务,肯定要区分开啊”张大胖不好意思的说:“原来端口号就是用来区分进程的,这样IP层发过来的数据包,到达TCP层以后就可以分发给各个应用程序了。”“对的,这叫多路复用。一般来说,服务器端都是被动访问的,所以大家需要知道它提供服务的端口号,要不然怎么连接?例如80,等,就是所谓知名端口号;而客户端访问服务器的时候,自己的端口号可以随机生成一个,只要不和别的应用冲突即可。”Socket编程张大胖问道:“那具体怎么使用你的Socket来编程?”“这要分为客户端和服务器端,两者不一样,对客户端来讲很简单,你需要创建一个socket,然后向服务器发起连接,连接上以后就可以发送,接收数据了,你看看这段伪代码“"恩,抽象以后果然是不一样,那些烦人的细节都被隐藏了,只剩下一些概念性的东西,用起来很清爽,这个clientfd我猜就是一个像文件描述符那样的东西吧?打开文件就会有一个"“对的,很好的类比,注意,在上面的伪码中,没有出现客户端的ip和端口,系统可以自动获得IP,也可以自动分配端口。还有,看到那个connect函数没有,其实就是在和服务器发起三次握手呢。”“那服务器怎么响应?”“服务器端要复杂一些,你想想看,第一,服务器是被动的,所以它启动以后,需要监听客户端发起的连接,第二,服务器要应付很多的客户端发起连接,所以它一定得各个socket给区分开了,要不就乱了套了,伪代码是这个样子的:”

张大胖说:“果然是复杂多了,listenfd,从名称看就是为了要监听而创建的socket描述符吧,bind是干嘛?嗯,我猜是为了声明说我要占用这个端口了啊,你们都别用了,listen函数才是真正开始监听了。

慢着,我赛,接下来是个死循环啊,啊对对,服务器端一直提供服务,永不停歇。可是这个accept是干嘛,为什么使用了listenfd,然后返回了一个新的connfd???”Bill满意的说:“不错,思考就有进步,可是你忘了我刚说的东西了,服务器要区分开各个客户端,怎么区分呢?那只有用一个新的socket来表示喽,你看后面的操作都是基于connfd来做的。还有这个accept相当于和客户端的connect一起完成了TCP的三次握手!至于之前的listenfd,它只起到一个大门的作用了,意思是说,欢迎敲门,进门之后我将为你生成一个独一无二的socket描述符!”“有道理,大神果然是大神,考虑的非常全面啊,不过似乎有个漏洞,你一开始说socket指的是(IP,Port),现在你已经有了一个listenfd的socket,端口是80然后每次客户端发起连接还要创建新的connfd,因为80端口已经被占用,难道服务器端会为每个连接都创建新的端口吗?”"这是个好问题啊"Bill说“其实新创建的connfd并没有使用新的端口号,也是用的80,可以这么理解,这个socket描述符指向一个数据结构,例如listenfd指向的结构是这样的:”“而一旦accept新的连接,新的connfd就会生成,像下面的表格,就生成了两个connfd,它们俩服务器端的ip和port都是想同的,但是客户端的IP和Port是不同的,自然就可以区分开来了”张大胖说:“唉,这底层做了这么多工作啊,看来socket必须得通过(客户端IP,客户端Port,服务器端IP,服务器端Port)来确定”“其实这个四元组还不太准确,因为咱们说了半天,都是TCP协议的socket,因为你们领导只要你实现这一个,你看过UDP没有?就是那个无连接的运输层协议,也有socket,所以更准确的定义的话,还得加上协议这一项,变成五元组(协议,客户端IP,客户端Port,服务器端IP,服务器端Port)”“大神,咱们什么时候讲讲UDP的socket?”"下次再说吧!"题外话:文中提到的Bill是向BillJoy致敬,这是一个天才程序员,主要工作包括BSDUnix操作系统,实现TCP/IP协议栈,vi编辑器,cshell,NFC,SPARC处理器,jini等。

当年DARPA(美国国防部先进研究项目局)和一个叫做BBN的公司签署了一个合同,要把TCP/IP协议加入到BerkeleyUnix当中,当研究生BillJoy看到BBN写的TCP/IP实现时,觉得非常差劲,拒绝把它加入内核,后来干脆卷起袖子自己实现了一个高性能的TCP/IP栈,这个协议栈至今是互联网的基石。

别人问他是怎么实现这么复杂的软件的,这位大神说:“很简单啊,你只需要看看协议,然后把代码写出来就行了”

你看到的只是冰山一角,更多精彩文章,尽在“码农翻身”







































石家庄治疗白癜风的医院
北京中科白癜风

转载请注明原文网址:http://www.gzdatangtv.com/cksc/cksc/6371.html
------分隔线----------------------------