草庐IT

背了一年的计网八股,还不知道什么是 Socket?

小牛肉 2023-03-28 原文

​前言

不明白 Socket 是什么的主要原因其实就是没有实际的网络编程经验,就没有在代码里用过 Socket,背来背去还是脑袋一片浆糊,很正常,看完这篇文章肯定就清楚了(狗头)

TCP 四元组

要说 Socket,那当然不能绕过 TCP 了,各位不妨先来思考下如何确定一个 TCP 连接?

以小黑和小白为例,他们分别位于不同的小区,小黑找小白玩,需要知道小白的小区和门牌号,也就是说,小区 + 门牌号就是小白家的入口,知道了这个入口,小黑就能找到小白。反之也是同样的。

小区类比于 IP 地址,门牌号类比于端口号,IP 地址 + 端口号(小区 + 门牌号) 就能唯一确定一个程序。光有小区不行,光有门牌号也不行,所以这就是为什么说网络层负责建立主机到主机的通信(IP 地址),传输层负责建立端口到端口的通信(端口号)了。

这个很好记忆,你上线一个网站的时候,如果没有绑定域名的话,那么就只能通过 IP 地址 + 端口号(默认是 80,浏览器上不显示)访问。

总结下就是,TCP 四元组可以唯一的确定一个连接,四元组包括如下:

  • 目的 IP 地址
  • 目的端口
  • 源 IP 地址
  • 源端口

其中:

  • IP 地址(源地址和目的地址,32位)是在 IP 头部中
  • 端口号(源端口和目的端口,16位)是在 TCP 头部中

Socket

掌握了四元组这个基本概念,我们再来解释 Socket。

上文说过,小区 + 门牌号是住宅的入口,IP 地址 + 端口号是一个程序的入口,这个入口就是 Socket,那么服务端和客户端之间想要进行通信,只要互相暴露出自己的入口(Socket),就能够找到彼此了。

更严谨来说,Socket 封装了基本的通信功能,是 TCP/IP 协议的基本操作单元。

以 Java 中的 Socket​ 类为例,服务端和客户端首先都需要调用构造函数创建 Socket 暴露自己的入口(绑定 IP 地址和端口,也可以调用 bind 方法进行绑定)

光暴露了入口还不行,你还得竖起耳朵听,不然别人来敲门你听不见那也没法通信啊,所以接下来服务端调用 accept() 方法,该方法将一直等待,直到客户端请求服务端的入口,再就是 TCP 三次握手建立连接的过程了。

服务端 Socket 创建一般使用 ServerSocket 类,该类提供了非常重要的 accept(建立连接) :

那客户端是如何请求服务端的入口的呢?也就是是如何发起连接的呢,客户端在创建好 Socket 后,调用 connect(host, port) 函数发起连接,该函数需要指明服务端的 IP 地址和端口号。

所以说,TCP 三次握手其实是发生在客户端 connect​ 和服务端 accept​ 两个函数之间。握手完了就可以通过 read()​ 和 write() 来通信啦。这里需要重点注意的是:监听的 Socket 和真正用来传数据的 Socket 是两个不同的 Socket:

  • 一个是 监听 Socket;
  • 一个是 已连接 Socket;
看下上述的 ServerSocket.accept​ 方法就明白了,accept会返回一个 Socket 对象,后续服务端和客户端之间的数据传输都用这个 Socket:

事实上,在三次握手的过程中,内核(Kernel)为每个连接都维护了两个队列:

  • TCP 半连接队列:这个队列存储没有完成三次握手的 Socket,此时服务端处于 syn_rcvd 的状态;
  • TCP 全连接队列:这个队列存储已经完成了三次握手的 Socket,此时服务端处于 established 状态;
当 TCP 全连接队列不为空后,服务端的 accept() 函数,就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 并返回,用于后续服务端和客户端的通信。

总结

综上, 基于 TCP 协议的 Socket 调用过程就结束了,下面由贴心助理 ChatGPT 总结下:

以下全是 ChatGPT 生成的结果,没有一个字是我写的(?),虽然是我引导了很多轮的结果,但是输入合适的 Promt 并配合上下文 ChatGPT 基本能输出 90% 想要的内容,确实太强了

文字解释:

代码示例:

客户端代码示例

服务器端代码示例

由于我懒得画图,所以决定再让 ChatGPT 帮我生成下,虽然结果不是很行,不过还是能看,不用我费劲画了,舒服!

在上面的流程图中,Socket 客户端和服务端之间的通信过程如下:

  1. 客户端创建一个 Socket 对象,指定服务端的 IP 地址和端口号,然后调用 connect() 方法发起连接请求。
  2. 服务端创建一个 ServerSocket 对象,并指定端口号,然后调用 accept() 方法等待客户端连接请求。
  3. 当客户端的连接请求到达服务端,服务端的 accept() 方法会返回一个新的 Socket 对象,该 Socket 对象代表了客户端和服务端之间的通信连接。
  4. 客户端可以通过该 Socket 对象的 getOutputStream()​ 方法获取输出流对象,用于向服务端发送数据;也可以通过该 Socket 对象的 getInputStream() 方法获取输入流对象,用于接收服务端发送的数据。
  5. 服务端可以通过该 Socket 对象的 getOutputStream()​ 方法获取输出流对象,用于向客户端发送数据;也可以通过该 Socket 对象的 getInputStream() 方法获取输入流对象,用于接收客户端发送的数据。
  6. 客户端和服务端可以通过各自的输出流和输入流进行数据的读写操作。
  7. 当通信完成后,客户端和服务端都需要调用该 Socket 对象的 close() 方法关闭连接。

有关背了一年的计网八股,还不知道什么是 Socket?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  4. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  5. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  6. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  7. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  8. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  10. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

随机推荐