专栏名称: 码农翻身
工作15年的前IBM架构师分享好玩有趣的编程知识和职场的经验教训, 不容错过。
目录
相关文章推荐
OSC开源社区  ·  2024前端现状:开发者最爱用React、最 ... ·  2 天前  
OSC开源社区  ·  谷歌将“杀死”ChromeOS,计划将其合并 ... ·  4 天前  
OSC开源社区  ·  通义灵码SWE-GPT:从静态代码建模迈向软 ... ·  5 天前  
OSC开源社区  ·  逃离“北上广深”,新一线是最好的去处吗? ·  5 天前  
程序员的那些事  ·  重磅!Chrome ... ·  6 天前  
51好读  ›  专栏  ›  码农翻身

张大胖的socket

码农翻身  · 公众号  · 程序员 架构  · 2016-11-09 19:58

正文

张大胖研究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, 443等, 就是所谓知名端口号; 而客户端访问服务器的时候,自己的端口号可以随机生成一个, 只要不和别的应用冲突即可。”

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 是向 Bill Joy致敬,  这是一个天才程序员,主要工作包括BSD Unix操作系统, 实现TCP/IP协议栈, vi 编辑器,c shell , NFC, SPARC处理器,jini等。

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


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


你看到的只是冰山一角, 更多精彩文章,尽在“码农翻身” 微信公众号, 回复消息"m"或"目录" 查看更多文章

有心得想和大家分享? 欢迎投稿 ! 我的联系方式:微信:liuxinlehan  QQ: 3340792577

公众号:码农翻身

“码农翻身”公众号由工作15年的前IBM架构师创建,分享编程和职场的经验教训。


掘金是一个高质量的技术社区,从 Swift 到 React Native,性能优化到开源类库,让你不错过互联网开发的每一个技术干货。长按图片二维码识别或者各大应用市场搜索「掘金」,技术干货尽在掌握中。