服务器
客户端
P2P模式区别于传统的C/S模式,在P2P模式中每个结点(或对等体)既可以作为客户端也可以作为服务端。
P2P体系的主要特征
使用P2P网络的例子:
Napster混合体系结构
文件搜索:集中式
文件传输:P2P
即时通信
混合体系结构的优势
定义:
指在主机上运行的应用程序的实例。每个进程都是一个正在执行的程序,它具有自己的代码和数据空间,以及其他系统资源,如文件描述符和网络连接。
进程间通信:
同一主机内:在同一主机内,进程间通信(IPC)机制允许进程互相交换数据。这通常是由操作系统提供支持的,包括管道、消息队列、共享内存等方式。
不同主机:在不同主机上的进程通过网络进行通信,常见的是通过交换报文的方式。这种通信要求使用网络协议来确保数据的正确传输。
客户端进程
服务器进程
注意事项
- P2P架构中的进程:即使在P2P架构中,也存在客户端和服务器进程的概念。在P2P网络中的每个节点都可能同时运行客户端和服务器进程,客户端进程发起请求,服务器进程响应请求。
问题一:进程标识和寻址问题(服务用户)
问题二:传输层与应用层的服务接口(服务)
问题三:如何使用传输层提供的服务,实现应用进程之间的报文交换,实现应用(用户使用服务)
在网络通信中,为了确保报文正确送达目的地,我们需要准确地标识网络中的进程。这个过程称为对进程进行编址或地址分配。
进程为了接受报文,必须有一个标识,即SAP(发送需要标识)。
主要组成:
主机标识:每台主机在网络中都有一个唯一的标识,即IP地址。在IPv4中,这是一个32位的数字,通常以点分十进制形式表示,比如 192.168.1.1
。
进程标识:
某些端口号被预先分配给特定的服务。例如,HTTP服务通常使用端口80,而FTP服务使用端口21。
端节点标识:
通信实体:
当应用层的进程向另一个进程发送数据时,它需要向传输层提供一系列明确的信息,以便传输层可以正确地处理和封装这些数据。这些信息包括:
传输层实体(TCP或UDP)会创建一个新的段(对于TCP)或数据报(对于UDP),这个过程称为封装。
如果每次通过Socket API发送报文时都需要手动指定完整的信息(如源IP、源端口、目标IP、目标端口),过于繁琐易错,不便于管理。
为了简化这一流程,引入了Socket的概念。Socket在这里充当一个标识符或句柄(handle),类似于操作系统重文件操作的句柄。
当操作系统打开一个文件时,它返回一个句柄,程序通过这个句柄进行读写操作。同理,Socket就是网络通信的句柄。
在TCP/IP模型中,两个进程间的通信在开始之前需要建立一个连接,这就是TCP协议的特性,确保了数据的可靠传输。
在操作系统级别,这个通信关系通常用一个整数(Socket描述符)来本地表示,这样就减少了应用程序处理的复杂性。
TCP Socket的定义
TCP Socket的特点:
下面是TCP Socket通信机制概念图,展示了TCP Socket在应用层和传输层之间提供了一个接口。
通信流程:程序(一个进程)首先创建创建一个socket,并指定目标IP和端口,通过这个socket,程序可以发起一个到服务器的连接请求,连接成功后,双方就可以开始数据交换。
如图,主机1具有IP地址 1.1.1.1
,这台主机的一个进程想要与主机2进行通信,为此它创建一个源端口号为233的TCP socket 11111
,主机1的进程使用这个socket发送数据到主机2的IP地址 2.2.2.2
为端口号为80的服务。而主机2收到来及主机1的请求后,主机2使用端口号为80的TCP socket22222来回复主机1。
UDP是一个无连接的网络服务,允许两个进程之间的通信而无需建立连接。
在UDP中,每个发送的数据包(报文)都是独立传输的,而且每个数据包都可能被发送到不同的目标进程。
因此,UDP Socket只能由一个整数(socket描述符)来标识本地应用实体。
并且,使用UDP Socket传递给传输层的信息量最小。基本上,只需要知道源端的IP地址和端口号。
尽管UDP不需要建立连接,但在发送报文时,发送方必须指定目标的IP地址和端口号,这样数据才能被正确地路由到接收方的进程。
接收方在接收到数据包时,传输层会提供发送方的IP和端口号,以便应用层知道数据来自哪里,并且可以选择如何响应。
UDP Socket的定义
UDP socket是一种网络通信端点,用于通过UDP协议发送和接收数据。与TCP socket不同,UDP socket不需要在通信前建立连接。
UDP Socket的标识
发送数据报的过程:
套接字是计算机网络中的一个抽象层,它为应用层进程提供了发送和接收数据的方法。它是应用层与传输层之间的接口。
套接字的作用:
网络协议栈:
端到端的通信:
应用层协议定义了运行在不同端系统上的应用如何相互交换报文。它规定了报文的格式、字段的语义以及发送和响应报文的时序规则。
程序需要通过应用编程接口(API)调用网络基础设施,来发送和接收报文,以及实现应用所需的时序和逻辑。
定义了:运行在不同端系统上的应用进程如何相互交换报文。
应用协议的作用
公开协议与专用协议
公开协议(Open Protocol):
- 这些协议由RFC(Request for Comments)文档定义,并且是公开的,这意味着任何人都可以使用这些协议来创建兼容的软件和设备。
- 公开协议的例子包括HTTP(Web通信)和SMTP(电子邮件传输),它们支持不同厂商和应用之间的互操作性。
专用协议(Dedicated Protocol):
- 专用或私有协议不公开,通常是由单个组织控制并且可能只用于其产品内部。
- 如:Skype使用的是一种专用协议,它的具体实现细节并不公开,这可能限制了与其他产品或服务的互操作性。
某些应用(如:文本信息),要求数据绝对可靠,不能有任何丢失。
有些应用(如:音频)能容忍一定程度的数据丢失。
保证吞吐量:一些应用,如流媒体视频,需要有保证的最小吞吐量,以确保流畅播放。
弹性吞吐量:有些应用,如文件下载,可以适应网络吞吐量的变化,即当网络条件好时可以提高速度,网络条件差时可以减速。
低时延:实时应用,如网络电话(VoIP)或在线游戏,要求传输延迟非常低,以保持良好的用户体验。
高时延容忍:如电子邮件或网页浏览,用户通常可以接受稍长的延迟。
为什么要有UDP?
UDP的存在有几个关键的必要性和优势:
虽然IP协议提供了主机到主机的通信,但它本身不能区分一台主机上运行的不同应用进程。UDP在端到端的通信基础上,通过端口号区分了不同的应用进程。这允许同一网络层IP地址的不同进程接收其特定的数据包。
省去了建立连接的时间,使得UDP非常适合需要快速数据交换的事务性应用,如DNS查询。
UDP不提供数据重发机制,如果数据包在传输中丢失,UDP不会尝试重新发送。这缺乏可靠性的特点使UDP适合那些对实时性要求高的应用,比如实时视频或语音通信,这些应用可以容忍一些数据包的丢失而无需等待重发,这样可以减少延迟。
UDP允许应用程序以其决定的速度发送数据,而不会因网络拥塞而降低速度。这与TCP不同,TCP会根据网络状况动态调整数据发送速度,以避免过多的数据包引起网络拥塞。
加密:标准的TCP和UDP本身不提供加密功能。
明文传输:由于TCP和UDP不加密数据,所有通过互联网的数据传输都是透明的,可以被任何路由数据包的中间节点读取。
实现在TCP之上:SSL是在TCP协议之上实现的,它在传输层和应用层之间提供一个加密层。这个加密层为TCP连接提供了安全性增强。
私密性:SSL使用加密算法确保数据在传输过程中的私密性,防止数据被未经授权的第三方读取。
数据完整性:SSL提供完整性检查,确保数据在传输过程中没有被篡改。
端到端鉴别:SSL支持使用数字证书进行端到端鉴别,这确保了通信双方的身份。
Web页面通常是由多个对象组成,这些对象可能是HTML文件,JPEG图像、可执行的客户端程序(如Java小程序)…
一个Web页面通常有一个HTML文件,这个HTML文件通过超链接引用又包含了若干个对象嵌入在HTML文件中。
每个Web对象都可以通过一个URL进行引用和访问。URL指定了如何找到和访问互联网上的资源。
URL 格式
- 协议名(Prot):如
http
或https
,定义了客户端和服务器之间通信的协议。- 用户名(user):用于认证的用户名,通常和密码一起出现。
- 口令(psw):与用户名对应的密码,用于访问控制。
- 主机名:资源所在的服务器地址,如
www.someSchool.edu
。- 端口(port):服务器上服务监听的端口号。如果未指定,将使用协议的默认端口。
- 路径名:资源在服务器上的具体位置,如
/someDept/pic.gif
。
HTTP,或超文本传输协议,是Web上使用的主要应用层协议。它基于客户端/服务器模型工作,涉及Web浏览器(客户端)和Web服务器之间的交互。
在以HTTP为背景下的客服/服务器模式的通讯过程:
首先客服端(通常是Web浏览器)启动通信过程。用户输入URL或点击链接,浏览器将这些动作转换为HTTP请求。HTTP使用TCP协议来保证数据传输的可靠性,并向服务器的特定端口发起连接。建立连接后,浏览器将HTTP请求发送到服务器。请求包含请求方式(如GET、POST)、目标资源的URL和HTTP版本,还可能包含请求头(如用户代理、接受的内容类型)和请求体(通常用于POST请求)。
服务器接收到HTTP请求后,首先解析请求并定位请求的资源。然后服务器想客户端发送HTTP响应,包括一个状态码(如200表示成功),响应头(包含有关响应的元数据)和响应体(请求的资源内容)。如果请求的是一个文档或图像,该资源将作为响应体传输。对于动态内容,服务器可能会实时生成响应。
HTTP是一个无状态协议,意味着服务器不保存任何关于客户端请求的状态信息。
为了客服这一限制,Web应用通常使用Cookie来维护用户状态。
非持久HTTP
HTTP/1.0协议
每个请求/响应都需要建立一个新的TCP连接,请求完成后立即关闭TCP连接。
最多只能在一个TCP连接上发送一个对象,如果需要下载多个对象就需要建立多个TCP连接。
这样做的好处是可以减少服务器的资源消耗,因为每个TCP连接都需要占用一定的缓冲区和变量。但是,这样做的缺点是会增加客户端和服务器之间的网络延迟,因为每次发送一个请求都需要重新建立新的TCP连接。
持久HTTP
HTTP/1.1协议
连接可以用于多个请求/响应,会保持一段时间再关闭。
多个对象可以在一个TCP连接上述传输,不需要每个对象建立新连接。
优点包括:1. 减少了建立和关闭连接的消耗和延迟,特别是高延迟的网络下,性能得到显著提高。
非持久HTTP连接操作的过程:
假设用户输入URL:www.someSchool.edu/someDept/home.index
(包含文本和10个JPEG图像的引用。)
HTTP客户端(浏览器)在端口号80向服务器 www.someSchool.edu
发起TCP连接。
这个连接的建立大约需要一个往返时间(RTT)。
服务器在端口80监听连接请求,一旦接收到,就接受链接并返回客户端,表示连接已经建立。
一旦TCP连接建立,浏览器通过向TCP连接的套接字发送HTTP请求报文,报文表示客服端需要对象 someDepartment/home.index
。
服务器接收到HTTP请求报文后,查找请求的 home.index
文件,找到文件后,服务器将其数据封装在一HTTP响应报文中,并通过TCP连接的套接字回传给客户端。
一旦响应报文发送完成,服务器关闭TCP连接。
HTTP客服端接收到HTML文件后,在用户的浏览器中渲染出来。
浏览器解析HTML内容,检查页面中的其他资源,例如图像、CSS文件、JavaScript文件等。
对10个JPEG对象,重复 1 − 5 1-5 1−5 步。
往返时间RTT(round-trip time):RTT是指一个小的分组从客户端出发到服务器,然后再返回客户端的总时间。(传输时间忽略)
非持久HTTP连接的响应时间组成:
第一个RTT用于初始化TCP连接。
客户端发送一个SYN包到服务器,等待服务器响应SYN-ACK,然后客户端回复ACK以完成三次握手。
第二个RTT用于发送HTTP请求并等待HTTP响应。
一旦TCP连接建立,客户端发送HTTP GET请求,并等待服务器回复包含所请求文件的HTTP响应。
最后,文件的传输时间取决于文件大小和网络带宽。
总的响应时间是2RTT加上文件的传输时间。
非持久HTTP的缺点:
持久HTTP的优点:
在持久HTTP下,有两种主要的请求/响应模式:非流水方式和流水方式。
在非流水方式的持久HTTP中,客户端需要等待前一个响应被完全接受后,才能发出下一个请求。这个模式下每个引用对象的获取还是需要花费一个RTT。
流水方式,是HTTP/1.1的默认模式。这种模式下客户端不必等待前一个响应,可以连续不断地发送多个HTTP请求。所有引用(小)对象只花费一个RTT是可能的。
HTTP有两种类型的报文:请求、响应
HTTP请求报文由ASCII码组成,(人能阅读)
请求报文的组成部分
请求行:包含方法(如GET、POST、HEAD)、请求的资源URL以及HTTP版本。例如:
GET /somedir/page.html HTTP/1.1
首部行:包含了各种首部字段,用来传递关于请求、客户端、所请求资源的额外信息。常见的首部行包括:
Host
: 指定请求的服务器的域名。User-agent
: 标识发出请求的浏览器或其他客户端。Connection
: 指示连接的类型,如 close
表示不持久连接。Accept-language
: 指定客户端优先接受的语言,例如法语(fr
)。换行回车符:在首部行后,使用一个单独的换行回车符(通常是 \r\n
)来表示报文头部的结束。
实体正文(在请求报文中可能不存在):如果是POST或PUT等类型的请求,实体正文会包含发送给服务器的数据,如表单内容。
描述了通过HTTP协议向服务器提交表单数据的两种方法:使用POST方法和使用URL查询字符串(通常与GET方法结合使用)。
POST方式:
例如,如果有一个表单是用于搜索动物,用户可能会在表单中输入"monkeys"和"banana"作为搜索词,这些词会被包含在POST请求的实体主体中发送到服务器。
URL方式:
&
符号分隔,参数名和参数值之间由 =
符号连接。例如:
www.somesite.com/animalsearch?monkeys&banana
:这个URL中,"monkeys"和"banana"是添加到查询字符串的搜索参数,但没有明确的参数名。http://www.baidu.com/s?wd=xx+yy+zzz&cl=3
:在这个例子中,“wd”和“cl”是参数名,“XX+YY+zzz”和“3”是这些参数的值。在URL中,空格通常被替换为 +
符号或者 %20
。在HTTP协议中,方法类型定义了客户端可以对服务器上的资源执行的操作。不同版本的HTTP协议支持不同的方法集合。
以下是HTTP/1.0和HTTP/1.1中常见方法类型:
HTTP/1.0:
HTTP/1.1:
HTTP响应报文由服务器发送给客户端,以响应客户端之前发出的请求。报文的结构通常包括状态行、首部行,以及可选的消息正文。
响应报文的组成部分:
状态行:包含协议版本、状态码、状态文本。
HTTP/1.1 200 OK\r\n
每个部分之间用空格分隔,并且状态行以回车换行符(\r\n
)结束。
首部行:紧随状态行之后的是一系列的首部行,它们提供了关于服务器和响应的额外信息
close
告诉客户端,服务器将在完成本次响应后关闭TCP连接。text/html
,表示发送的是一个HTML文档。每个首部行由首部名、冒号、空格以及首部值组成,并且每一行都以 \r\n
结束。
首部行和消息正文之间有一个空行(\r\n
),标记着首部的结束和消息正文的开始。
消息正文:包含了实际的响应内容,如请求的HTML页面。
Content-Type
和 Content-Length
一致。状态码是标准化的,它们被分为几个不同的类别,从1xx(信息性状态码)到5xx(服务器错误)。
200 OK
请求成功,请求对象包含在响应报文的后续部分
301 Moved Permanently
请求的对象已经被永久转移了; 新的URL在响应报文的Location: 首部行中指定
客户端软件自动用新的URL去获取对象
400 Bad Request
一个通用的差错代码,表示该请求不能被服务器解读
404 Not Found
请求的文档在该服务器上未被找到
505 HTTP Version Not Supported
这个状态码表示服务器不支持或者拒绝支持请求消息中使用的HTTP协议版本。
通常发生在客户端使用服务器不支持的新版本或者过时的HTTP版本时。
Cookies在用户与服务器之间维持状态时发挥着关键作用,尤其是在HTTP这种无状态的协议中。
Cookies的4个组成部分:
Set-Cookie
首部行向用户浏览器发送一个或多个Cookies。这些Cookies通常包含一个唯一的标识符,用来识别用户。例子:
- 假设有一个用户,我们称她为Susan。Susan通常使用同一台PC和Internet Explorer浏览器上网。
- 当Susan首次访问一个电子商务网站时,她的浏览器通过HTTP请求向服务器请求页面。
- 服务器响应这个请求,同时在HTTP响应中使用
Set-Cookie
首部行发送一个唯一的ID作为Cookie。- Susan的浏览器存储了这个Cookie,并在之后每次请求该网站时,都会在HTTP请求中包含这个Cookie。
- 服务器收到含有Cookie的请求后,检索后端数据库,识别Susan的ID,并根据存储的信息定制响应(例如加载她的购物车内容)。
Cookies能带来什么:
如何维持状态
Cookie
和 Set-Cookie
首部行被用来传输Cookies,从而实现在无状态的HTTP协议中维护用户状态的目的。Cookies与隐私:
- Cookies通过允许网站跟踪用户的行为和偏好来增强用户体验,但这也可能影响用户的隐私。
- 网站可能会将收集到的信息用于非原始意图的目的,或者出售给第三方(如广告公司),这可能导致用户的浏览习惯和个人信息被非授权方访问。
- 使用Cookies的搜索引擎和广告网络能够在用户访问各种网站时跟踪他们的活动,进而建立起用户的行为模式,这可能用于目标广告或其他形式的营销活动。
下面来讲解Cookies在客户端和服务器之间如何工作以及他们是如何在HTTP请求和响应中被发送的。
Set-cookie
首部行发送一个Cookie。例如,Set-cookie: 1678
,其中 1678
可能是分配给用户的唯一标识符。Cookie
首部行完成,如 Cookie: 1678
。Cookie
首部行,它可以查找后端数据库中与该Cookie相对应的用户信息,比如用户的偏好设置、账户信息等。Web缓存是一种特殊的网络服务,它存储过去请求的Web资源副本,如网页、图片和文件。当Web缓存存在时,它可以作为客户端和原始服务器(origin server)之间的中间实体,即充当代理服务器。
工作原理:
Web缓存的位置
好处:
条件GET方法用于主要用于判断缓存中的对象是否为最新版本,从而决定是否需要从原始服务器重新下载该对象。
工作原理:
If-Modified-Since:
。304 Not Modified
。这意味着对象自上次缓存以来没有发生变化,因此不需要重新下载。200 OK
,并附带最新的数据。例子
- 对象未修改:
- 缓存服务器发送HTTP请求,包含
If-Modified-Since:
。- 原始服务器发现对象自指定日期以来未修改。
- 响应为
HTTP/1.0 304 Not Modified
,不包含对象数据。- 缓存服务器继续使用其缓存副本。
- 对象已修改:
- 缓存服务器发送类似的请求。
- 原始服务器检测到对象已被修改。
- 发送
HTTP/1.0 200 OK
响应,附带最新的数据。- 缓存服务器更新其缓存副本。
重要性和效益:
FTP的定义:FTP是一种网络协议,用于在客户端和服务器之间传输文件。它可以将文件上传到远程主机或从远程主机下载文件。
客户端/服务器模式:
标准:FTP协议的细节和规范在RFC 959文档中有所描述,这是FTP通信标准的正式定义。
端口号:FTP服务器通常在网络上的21号端口上监听来自客户端的连接请求。
FTP客户端与FTP服务器的连接
身份确认和命令传输
文件传输过程
控制连接的特点
服务器的状态维护
FTP是一个有状态的协议,这意味着服务器会为每个客户端会话维护状态信息。这包括客户端的当前工作目录、账户信息和任何特定于会话的设置或历史记录。这样做允许在多个请求之间保持连续性和上下文。
FTP 命令、响应
- 命令样例:
- USER username:用于发送用户名进行身份认证。
- PASS password:用于发送密码进行身份认证。
- LIST:请求服务器返回远程主机当前目录的文件列表。
- RETR filename:从远程主机的当前目录检索(下载)文件。(gets)
- STOR filename:向远程主机的当前目录存放(上传)文件。(puts)
- 返回码样例:这些是FTP操作中返回的状态码,类似于HTTP状态码。
- 331 Username OK, password required:用户名正确,需要密码。
- 125 Data connection already open; transfer starting:数据连接已打开;传输开始。
- 425 Can’t open data connection:无法打开数据连接。
- 452 Error writing file:写入文件时出错。
电子邮件系统主要由三个核心组件:用户代理、邮件服务器和简单邮件传输协议(SMTP)。
电子邮件系统的工作流程通常如下:
SMTP使用TCP在邮件客户端和服务器之间传输邮件。默认情况下,SMTP通信使用的端口号是25。
SMTP允许直接从发送方的邮件服务器传输电子邮件到接收方的邮件服务器,不需要中间的转发步骤。
传输的三个阶段:
命令/响应交互:
报文必须是7位 A S C I I ASCII ASCII码
简单的SMTP交互
S: 220 hamburger.edu
:服务器(S)向客户端(C)发送一个220响应码,表示SMTP服务已准备好。C: HELO crepes.fr
:客户端发送HELO命令和自己的域名,开始SMTP会话。S: 250 Hello crepes.fr, pleased to meet you
:服务器响应250,表示成功,并对客户端发出的HELO命令进行确认。C: MAIL FROM:
:客户端指定发件人的电子邮件地址。S: 250 alice@crepes.fr... Sender ok
:服务器再次响应250,确认发件人地址是有效的。C: RCPT TO:
:客户端指定收件人的电子邮件地址。S: 250 bob@hamburger.edu... Recipient ok
:服务器确认收件人地址是有效的。C: DATA
:客户端请求开始邮件内容的传输。S: 354 Enter mail, end with "." on a line by itself
:服务器响应354,指示客户端开始发送邮件内容,以单独一行的点号(“.”)结束。- 客户端发送邮件正文,每行代表邮件的一部分,以一个单独的点号(“.”)行结束。
S: 250 Message accepted for delivery
:服务器接收了完整的邮件内容,响应250表示消息已被接受用于传递。C: QUIT
:客户端发送QUIT命令,请求关闭连接。S: 221 hamburger.edu closing connection
:服务器响应221,表示它即将关闭连接。
SMTP总结
HTTP比较:
首部行:
MIME:多媒体邮件扩展(multimedia mail extension)是一种标准,它扩展了电子邮件的功能,允许发送和接收非ASCII字符集的内容,比如多媒体文件。
image/jpeg
表明附件是一个JPEG格式的图片。base64
是一种编码方法,它允许将二进制数据转换成ASCII文本格式,这样就可以通过SMTP发送。+
)和斜线(/
)组成的字符,以及在数据的最后用于填充的等号(=
)。image/jpeg
中的 image
是类型,jpeg
是子类型。用于从服务器访问和管理邮件。常见的邮件访问协议包括:
POP (Post Office Protocol): 邮局访问协议,其规范被定义在RFC 1939中。
IMAP (Internet Mail Access Protocol): 互联网邮件访问协议,定义在RFC 1730中。
HTTP (HyperText Transfer Protocol)
POP3(邮局协议版本3)常用于接收电子邮件,它包含两个主要阶段:用户确认阶段和事务处理阶段。
用户确认阶段
user
: 客户端发送用户名以申明用户身份。pass
: 客户端发送口令来完成认证。+OK
: 表示成功的响应。-ERR
: 表示发生错误的响应。事务处理阶段
list
: 请求邮件列表,服务器响应每封邮件的编号和大小。retr
: 根据邮件编号检索邮件内容。dele
: 删除指定编号的邮件。quit
: 退出会话。POP3在默认模式下,客户端连接到邮件服务器,下载所有新邮件,并将它们从服务器上删除。这意味着一旦下载,邮件只存在于本地设备上,不再服务器上。
由于邮件被下载并从服务器删除,如果用户更换设备,他们将无法访问那些已下载的邮件。这限制了从多个设备访问邮件的能力。
用户可以配置客户端在下载邮件后在服务器上保留邮件的副本。这允许用户在不同设备上访问邮件,但不同步邮件的状态(如已读/未读)。
POP3在会话中是无状态的。
IMAP 是一种更现代且功能更丰富的邮件协议,它支持复杂的邮件管理操作:
总结:
tianmeng@pku.edu,cn
所在的邮件服务器 www.pku.edu.cn
。www.example.com
中,com
是顶级域名,example
是二级域名,而 www
是子域名。DNS的历史
ARPANET的名字解析解决方案
- 在ARPANET中,每台计算机都被赋予了一个没有层次的唯一字符串作为主机名。这些名字构成了一个平面命名空间,没有嵌套或分级的结构。
- 当时的名字解析是通过一个中央的“维护站”来管理的,该站点负责维护一张包含主机名和对应IP地址映射的文件:Hosts.txt
- 网络上的每台主机需要定期从这个维护站下载Hosts.txt文件,以便本地解析主机名到IP地址。
ARPANET解决方案的问题
随着网络的扩大,原始的名字解析方法开始显示出其局限性:
- 主机名分配困难:没有层次的命名空间意味着所有主机名都在同一级别上,这使得随着主机数量的增加,命名冲突和分配变得苦难
- 文件的管理、发布、查找都很麻烦:每台主机都需要定期更新本地的Hosts.txt文件,这不仅在宽带上是一种浪费,而且查找时也不如动态解析高效。
DNS的整体思路和目标
DNS的主要思路:
- 层次化、基于域的命名机制。
- 若干分布式的数据库完成名字到IP地址的转换:名字到IP地址的转换不是集中在一个单一的位置完成,而是通过一个全球性的分布式数据库实现。这些数据库位于全世界的多个DNS服务器上。
- 运行在UDP之上:DNS查询通常使用用户数据报协议(UDP)在端口号53上进行。
- 虽然DNS是互联网核心功能的一部分,它实际上是作为一个应用层协议来实现的。DNS的复杂性被处理在网络的边缘,即在端用户设备的软件或在相应的服务器上。
DNS主要目的:
- 实现主机名与IP地址的转换(name/IP translate):核心功能是将人类可读的主机名(如
www.example.com
)转换为机器可读的IP地址(如192.0.2.1
),以便网络通信可以正常进行。其他目的:
- 主机别名(Host aliasing)到规范名字的转换:DNS允许将一个主机的别名(如一个简化的名字)转换为其规范的域名。
- 邮件服务器别名到邮件服务器的正规名字的转换。
- 负载均衡(Load Distribution):DNS还可以用于负载均衡,通过返回不同的IP地址响应来分散到特定主机的流量。
.com
、.edu
、.gov
等,这些是最常见的域名后缀,任何个人或机构都可以注册。.cn
、.us
、.nl
、.jp
等,这些通常为特定国家或地区保留。ustc.edu.cn
、auto.ustc.edu.cn
、www.auto.ustc.edu.cn
。ustc.edu.cn
代表中国科学技术大学的域),也可以用来指向域上的一个具体主机(如 www.ustc.edu.cn
指向学校的主页服务器)。每个顶级域(TLD)都负责管理其下的子域。比如,.jp
是日本的国家顶级域,它下面可以划分为多个子域,如 ac.jp
(通常用于学术机构)、co.jp
(通常用于商业公司)。同样,.cn
是中国的国家顶级域,它也可以被划分为如 edu.cn
(教育机构)和 com.cn
(商业组织)等子域。
创建一个新的子域时,需要得到上一级域的授权。例如,如果某个学校想要创建 school.edu.cn
这个子域,它需要从负责 .edu.cn
的注册机构获得授权。
将域名管理与物理网络解耦使得域名系统非常灵活。它可以根据组织的需求来划分和管理域名,而不受地理位置或网络拓扑的限制。
名字服务器的问题
区域(Zone)
名称空间中的区域划分
顶级域服务器负责存储顶级域名的信息,通常包括顶级域名(如“.com”,“.org”,“.net”,“.edu”,和“.gov”)和国家代码顶级域名(例如“.cn”,“.uk”,“.fr”,“.ca”,“.jp”)。
Network Solutions公司负责维护“.com”TLD服务器。
Educause公司是负责维护“.edu”TLD服务器的组织。
区域名字服务器(也称区域DNS服务器)是负责管理特定DNS区域内部的域名解析信息的服务器。这些服务器维护者资源记录(Resource Records, RR),资源记录是DNS中的基本信息单元,包含了映射域名到IP地址(以及其他类型的映射)的数据。
资源记录(Resource Records)
RR格式:( d o m a i n n a m e , t t l , t y p e , c l a s s , V a l u e domain_name, ttl, type, class, Value domainname,ttl,type,class,Value)
Domain_name(域名):记录相关联的域名
T t l Ttl Ttl, time to live(生存实现):表示资源记录在DNS缓存中的存活时间,过了这个时间,缓存记录会被删除,必须重新查询权威DNS服务器获取新的记录。
TTL告诉其他服务器在多长时间内可以缓存这条记录,而不是每次需要时都像DNS服务器查询,这样做的目的如下:
- 缓存的目的:缓存DNS记录的主要目的是提高性能。当一个递归服务器查询DNS记录并收到回应时,它会将这些信息存储在本地缓存中。如果在TTL到期之前有对同一域名的查询请求,递归服务器就可以直接从缓存中提供答案,而不是再次进行全球性的DNS查询。这减少了解析时间,降低了对权威服务器的查询压力,从而提高了整个DNS解析过程的效率。
- 删除的目的:删除(或更新)缓存的目的是保持数据一致性。如果域名对应的IP地址或其他相关信息发生了变化,老的信息仍然存储在DNS缓存中可能会导致用户无法访问该域名或者被错误地导航到旧的IP地址。因此,当TTL值到期,缓存的记录会被删除,确保下次查询会从权威服务器获取最新的信息。
Class(类别): 表示记录的类别,对于互联网(Internet),其值通常为IN,代表Internet。
Value(值):根据不同**类型(type)**的资源记录。
Type(类型): 表示资源记录的类型。
资源记录类型
Type = A
- Name 为主机名
- Value 是主机的IPv4地址
Type = NS (Name Server)
- Name 是域名(如foo.com)
- Value 是该域名的权威名称服务器地址。告诉DNS查询这个特定的权威服务器是哪一个。
Type = CNAME(Canonical Name)
- Name 是别名
- Value 是该别名的规范名字。例如例如
www.ibm.com
的规范名字为servereast.backup2.ibm.com
。Type = MX(Mail Exchange)
- Value 为Name对应的邮件服务器的别名的正规名字。
应用程序发起请求:应用调用 解析器(resolver)
应用程序需要访问一个域名(如:example.com),为了将域名解析成IP地址,应用程序回想系统中的解析器(Resolver)发送一个解析请求。
解析器向本地名称服务器发出请求:
解析器抽到请求后,就会向配置的本地名称服务器(Local Name Server)发出一个DNS查询。这个查询是封装在UDP协议的数据包中,通常使用53号端口。
本地名称服务器处理请求并相应:
本地名称服务器首先会查看自己的缓存,看是否有对应的记录。如果没有,它会进一步向上级DNS服务器查询(如根服务器、顶级域名服务器、权威名称服务器),直到找到答案。当本地名称服务器获得了域名对应的IP地址,它会将这个信息作为响应发回给解析器。
解析器将响应返回给应用程序:
解析器收到从本地名称服务器发来的包含IP地址的响应后,就会将这个IP地址提供给发起请求的应用程序。随后,应用程序就可以使用这个IP地址与目标服务器建立连接。
本地名字服务器(Local Name Server)
本地名字服务器是DNS的一个关键组件,主要有以下特征:
非层次结构性质
本地名字服务器并不严格属于DNS的层次结构。本地名字服务器更多地起到一个中介的角色,在解析过程中相当于一种桥梁作用。
与ISP关联
几乎每个互联网服务提供商(ISP),都会提供一个本地DNS服务供其用户使用。
这些本地DNS服务器也被成为“默认名字服务器”。
DNS查询的第一站
当一个主机发起一个DNS查询时,这个查询首先被送到其配置的本地DNS服务器。
代理作用
如果本地DNS服务器中没有缓存所需的信息,它会作为代理,将查询转发到DNS层次结构中的其他服务器,例如向根服务器、TLD服务器或权威名字服务器查询,直到找到相应的记录。
一旦本地服务器接收到这些信息,它会将结果返回给发起查询的主机,并将记录存入缓存以加快未来的查询过程。
名字服务器(Name Server)
**名字解析过程**
情况1:如果请求的域名在本地名字服务器所管理的区域内部,它将能够直接解析该域名并返回结果给请求主机。
情况2:缓存解析(cashing):如果请求的域名不在本地名字服务器的区域内,本地名字服务器会查看其缓存记录。如果该域名最近被解析过,并且缓存的记录仍然有效(即没有超过TTL),本地名字服务器会使用这个缓存记录来回应请求。
当与本地名字服务器不能解析名字时,联系根名字服务器顺着根-TLD一直找到 权威名字服务器。
递归查询(Recursive Query):
在递归递归查询中,客户端请求其本地DNS服务器解析一个域名。本地DNS服务器将负责整个查询过程:
在这个过程中,客户端只与本地DNS服务器交互,所有的查询负担都放在了这个服务器上。
由于互联网上的每个DNS解析请求都可能涉及到根服务器,导致根服务器承受巨大的查询压力。其解决方法是迭代查询。
迭代查询(Iterative Query)
在迭代查询中,客户端请求本地DNS服务器解析一个域名,但过程稍有不同:
迭代查询中,本地DNS服务器每次查询都会得到指向下一个应当联系的服务器的指示,而不是最终结果。每一步它都需要根据上一个服务器的指示继续向下查询。
DNS协议中查询和响应报文的报文格式相同。
报文首部
报文的具体内容包括:
在DNS系统中,性能的一个重要优化是使用缓存。缓存是一种机制,它允许名字服务器存储最近查询过的域名到IP地址的映射信息。这样,当同一个或者相近的查询再次发生时,服务器可以直接从缓存中提供答案,而不必每次都进行完整的查询。这减少了对上游服务器,特别是根服务器和TLD服务器的查询需求,从而大大提高了效率。
缓存的优点:
缓存可能引起的问题:
解决方案:
使用TTL: 每个DNS记录都有一个称为“生存时间”(Time to Live,TTL)的值,这个值告诉服务器应该缓存记录多长时间。一旦TTL到期,缓存的记录就会被删除(或标记为过时),这样在下次有查询请求时,服务器就会重新从权威服务器获取最新信息。
TTL的默认设置: TTL的值可以由域名的管理员设置,不过它通常有一个默认值,比如48小时。这意味着两天后,缓存的信息将被视为无效,需要重新查询。
当需要引入一个新的子域时,如“Network Utopia”,这个过程如下:
注册域名:
首先为新域名" n e t w o r k u t o p i a . c o m networkutopia.com networkutopia.com"选择一个或多个权威DNS服务器,并向域名注册机构提供选择的权威DNS服务器的主机名和IP地址。
更新顶级域名服务器
注册机构将在".com"顶级域的DNS服务器中为"$networkutopia.com$"添加两条资源记录(Resources Records,RRs):
配置权威DNS服务器
在"$networkutopia.com$"的权威DNS服务器上,需要配置DNS记录,使得所有人都能解析到该域下提供的服务。
www.networkutopia.com
映射到一个IP地址,允许互联网用户通过浏览器访问网站。networkutopia.com
域内电子邮件的邮件服务器地址(例如,mail.networkutopia.com)。对于DNS的攻击方式,主要包括分布式拒绝服务(DDoS)攻击、重定向攻击和利用DNS基础设施进行的DDoS攻击。
DDoS攻击:
重定向攻击:
利用DNS基础设施进行DDoS:
纯P2P模式有以下特点:
纯P2P的例子有:
问题:从一台服务器向N个节点(peer)分发一个大小为F的文件所需要的时间。
C/S模型中,一台服务器负责向多个客户端发送文件。这个分发过程的时间取决于服务器的上载带宽(us)和客户端的下载带宽(di)。
在C/S模式下,文件分发给所有客户端所需要的总时间至少是服务器发送N个副本所需时间和所有客户端中下载带宽最小的那个客户端下载文件所需的最大值。表达式为:
D c − s > max { N F u s , F d m i n } D_{c-s} > \max\left\{\frac{NF}{u_s}, \frac{F}{d_{min}}\right\} Dc−s>max{usNF,dminF}
在P2P模式下的文件分发时间,有以下几个取决因素:
服务器传输:服务器至少需要上载一个文件的副本。这样,至少一个客户端可以开始拥有文件的副本并开始将其分发给其他节点。
客户端传输:在P2P网络中,每个客户端在下载了文件拷贝之后,也能成为一个上载源,帮助分发文件。
总体下载量:考虑到所有客户端的总下载量为 N F NF NF,因为每个客户端都需要一个文件的副本。
总体上载带宽:不仅服务器可以上载,所有客户端也可以提供上载带宽,总上载带宽是服务器上载带宽与所有客户端上载带宽之和,即 $u_s + \sum u_i $,其中 $\sum u_i $是所有客户端上载带宽的总和。
在P2P模式下,分发文件所需的时间 D P 2 P D_{P2P} DP2P 是以上三个时间中的最大值:
$ D_{P2P} > \max\left{\frac{F}{u_s}, \frac{F}{d_{min}}, \frac{NF}{u_s + \sum u_i}\right} $
P2P模式通常能更有效地分发文件,特别是当有大量的客户端同时在线时,因为它能利用所有客户端的上载带宽,而不仅仅是服务器的带宽。这样,随着客户端数量的增加,网络的分发能力也相应增加。
P2P(对等网络)与客户端-服务器(C/S)模型在文件分发时间上的对比图表:
BitTorrent是一种点对点(Peer-to-Peer, P2P)通信协议,用于高效地分发大量数据,避免了依赖于单个服务器的需求。它通过将文件分散成许多小块来分发,每个用户下载的同时也会上传,增加网络的整体带宽和文件的可用性
BitTorrent协议的工作原理:
用户想要下载一个文件时,首先连接到一个中央服务器(tracker),这个服务器跟踪所有分享该文件的用户(peer)的信息。用户(Alice)通过tracker了解哪些peer拥有文件的不同部分,并与这些peers建立连接来交换文件块。
当Alice加入torrent网络,开始时她没有任何文件块。她的客户端会向tracker注册,并获取其他正在分享该文件的peers列表。之后,Alice的客户端会尝试与这些peers建立直接的连接,并开始下载文件块。同时,为了遵守协议,Alice也会上传她已经下载的文件块给其他peers,这样她就参与到了整个网络中的文件分发。
为了提高效率,BitTorrent使用了一种“稀有块优先”(rarest first)的策略,即优先下载在网络中较为稀缺的文件块,以确保所有文件块都有较好的可用性和分发速度。此外,BitTorrent 还采用了“对等交换”(tit-for-tat)策略,即用户的客户端会优先向那些向其提供数据的peers上传数据,以鼓励共享和交换,这是一种激励机制,确保网络中的资源分享是活跃和平衡的。
稀缺块优先策略:
在任意给定时间背景下,网络中的某些文件块可能会比其他快更稀缺(更少),或者说持有这个文件块的用户少(用户没上线),而没有足够的资源上传这块,导致下载过程的停滞。 为了解决稀缺块缺少问题,稀缺块优先策略是通过监控网络中的文件块分布,确定哪些块是稀有的,客户端优先下载这些稀缺块,从而确保这些块在网络中更广泛地分布。
对等交换策略
为了解决”免费搭车“问题,需要一种激励机制,对等交换策略的引入正是为了激励用户贡献自己的带宽,并为网络中的其他用户提供文件块。这个策略基于经济学中的互惠原则,意在建立一个互助合作的网络环境。 对等交换策略的解决办法是通过跟踪上传到每个对等方的数据量,并优先分配下载带宽给那些贡献更多上传带宽的用户。如果用户不上传,或者上传很少,他们的下载优先级会降低,从而激励用户参与文件的上传。
关键点:
例子:
Alice使用P2P软件打算下载一个MP3文件,Alice的P2P客户端通过Internet搜索其他用户Peers共享文件列表,找到并选择了一个拥有这个MP3文件的Peers(Bob),文件从Bob的PC传送到Alice的笔记本上(HTTP),当Alice下载的同时,Alice也会上传已经下载的文件部分给掐他Peers。
所有的Peers都是服务器 == 可拓展性好。
上面的例子中,存在两个问题:
可能解决的方案:
Napster的集中是文件设计
当对等方连接到网络时,他会向中心服务器提供其IP地址和它所拥有的内容(如音乐文件、视频等)的列表。
当用户Alice查询一个MP3文件时,服务器会查找哪些对等方拥有这个文件,并将这些信息提供给Alice。
根据服务器提供的信息,Alice可以直接从拥有该文件的对等方那里请求并下载文件。
文件的传输是点对点的,即直接从Bob的点到传输到Alice的电脑,而不是经过中央服务器。
集中式目录存在的问题:
覆盖网络(Overlay Network):
协议的操作:
对等方加入网络:
新对等方X首先需要发现已经存在于Gnutella网络中的其他对等方:使用可用对等方列表。
- X自己维护一张对等方列表(这个列表基于X之间的交互,记录了那些经常在线的对等方。)
- X可以联系维持列表的Gnutella站点,从中获取对等方的信息。
X获取了对等方列表,它会尝试与列表上的对等方建立TCP连接。X会依次尝试列表中的IP地址,直到成功建立起至少一个TCP连接为止。(这个连接通常是通过标准的网络握手过程建立的。)
连接建立后,X会想已建立连接的对等方Y发送一个Ping报文。
在Gnutella网络中,所有收到Ping报文的对等方会回应一个Pong报文。
Pong报文会包含对等方的IP地址、它共享的文件数量以及文件的总字节数,
X会从多个对等方接收到Pong报文,通过这些Pong报文,X利用这些网络信息与更多的对等方建立TCP连接。
由于每个查询都会被广播到多个对等方,大量的查询可能会导致网络负担过重。为了防止网络被过度的查询流量淹没,Gnutella协议使用了限制范围的洪泛查询。这意味着查询报文在网络中传播的距离是有限制的,以减少网络的负担。
KaZaA网络,它使用了一种称为“超级节点”(Super Nodes)或组长的结构来优化搜索和文件传输过程。
KaZaA网络使用了一些机制来提高文件共享的效率和鼓励用户参与。这些小技巧包括请求排队、激励优先权和并行下载。
DHT是一种分布式存储系统,它提供了一种键值对(key-value pair)存储的方法,允许进行快速的查找操作。DHT在多个应用中非常有用,尤其是在P2P网络中,因为它能够在没有中心服务器的情况下,高效地处理节点的加入和离开,以及数据的存储和检索。
在DHT中,数据以键值对的形式存储。键(Key)是数据的唯一标识符,而值(Value)则是与键相关联的数据内容,可以是文件的位置、数据的内容或者是其它任何需要存储的信息。
DHT使用哈希函数来决定数据应当存储在网络中的哪个位置。哈希函数将键转换成一个固定长度的数字标识符,该标识符通常是一个很大的整数。哈希函数需要具备一定的属性,如均匀性,确保数据能够均匀分布在所有节点上,减少冲突,以及不可逆性,保护原始数据的安全。
在DHT中,每个参与的计算机或服务器被称为一个“节点”(Node)。每个节点负责存储一部分键值对,这一部分由哈希函数计算得出。
当一个键值对被添加到DHT时,它会被存储在哈希值与之最接近的节点上。这意味着每个节点都会存储一个哈希值范围内的所有键值对。这种方法确保了即使某个节点不可用,也可以从其他节点找到数据。
当需要查找一个键对应的值时,发起查询的节点会根据哈希函数和键来确定应当查询的节点。然后,它会向这个节点发起一个查询请求。如果这个节点拥有这个键的数据,它就会返回相应的值;如果没有,则可能会将查询请求转发给其他可能有这个数据的节点。
为了保持网络的健康和高效,DHT网络需要不断地进行维护。这包括处理节点的加入和退出、数据的复制和迁移、以及定期的健康检查等。
DHT非常适合大规模的网络,因为它能够在节点数目大幅增加时保持高效的性能。
DHT通过数据复制和其他机制来提高容错性。即使个别节点失败或离开网络,整个网络仍然可以正常工作。
DHT网络能够自动地处理节点的加入和退出,无需人为干预。
DHT广泛应用于各种P2P系统,比如文件共享服务BitTorrent的追踪器就是通过DHT来实现的。
非结构化P2P这类网络没有固定的网络拓扑结构或严格的资源组织方式。节点以随机的方式连接,文件和资源的分布也是随机的。
非结构化P2P包括:
非结构化的特点是:
结构化P2P网络使用严格定义的拓扑结构和算法来组织资源和节点,其中最著名的是分布式散列表(DHT)。
特点:
优点:
视频流化服务(如Netflix和YouTube)在现代互联网中占据了巨大的份额。这些服务器需要将大量数据传输给全球范围内的用户,这就带来了一系列挑战,尤其是在规模性和异构性方面。
规模性:
单个服务器或数据中心无法处理如此巨大的用户基础和数据量。这主要是因为:
异构性:
解决方案:分布式的,应用层面的基础设施。
视频 概念:由一系列固定速度显示的图像组成的序列。(e.g.:24images/sec)
网络视频特点:
数字化图像和编码:
数字图像由像素(Picture Elements)的阵列组成,每个像素使用若干个bits来表示其颜色和亮度。
编码:通过利用图像内部和图像之间的冗余来减少所需的比特数,从而实现压缩。
这里的冗余是指数据中那些不必要或者重复的信息,这部分信息可以通过编码算法被去除或简化以减少文件大小。
空间编码例子:如果一幅图像主要由同一颜色组成(比如紫色),那么不需要发送每个像素的颜色值。相反,只需要发送颜色值一次和这个颜色重复的次数(N)。
时间编码例子:当传输视频时,如果连续两帧(frame i 和 frame i+1)之间只有少量的变化,那么就没有必要发送整个frame i+1的数据。可以只发送从frame i到frame i+1之间发生变化的部分。
下面是两种视频编码时考虑的比特率控制方法:
存储视频的流化服务是指将视频文件保存在服务器上,以便用户可以通过互联网按需进行流式播放的服务。这种服务允许用户随时随地观看内容,不需要完整地下载整个视频文件。
DASH(Dynamic Adaptive Streaming over HTTP)是一种先进的多媒体流化技术,也称为自适应流或自适应比特率流。它允许高质量的视频流通过普通的HTTP网络传输,同时能动态适应用户的网络条件。
DASH的工作原理:
客户端在播放视频时不断监控网络状况,并动态请求最合适的视频块。这种“智能”客户端可以:
挑战:服务器如何通过网络向上百万用户同时流化视频内容(上百万视频内容)?
这种选择可能包括一下几个问题:
这种方法结构简单,但不具备可拓展性,难以适应不断增长的用户和数据需求。
使用CDN技术在整个网络中部署多个缓存节点,将内容存储得更靠近用户。
CDN 的工作原理
Socket编程是一种网络编程的方式,它允许应用程序通过网络发送和接收数据。Sockets是应用层与TCP/IP协议族传输层之间的抽象层,使得程序员可以不必处理协议的细节而进行网络通信。
在传输层服务中主要有两种socket类型:
TCP(传输控制协议)提供了一种可靠的服务,确保从一个进程发送到另一个进程的数据能够按序、可靠地传输。
TCP套接字编程的过程:
服务器设置:
服务器进程必须首先启动并运行,以便可以监听和接受客户端的连接请求。然后服务器创建一个套接字,这个套接字仅用于接受新的连接请求,通常被称为“欢迎套接字”。服务器将这个欢迎套接字与本地端口绑定,这个客户端就知道向哪个端口发送连接请求。服务器在欢迎套接字上进行阻塞式等待,即进入等到状态,直到客户端发起连接请求。
客户端发起连接
客户端创建一个本地套接字,操作系统会为其分配一个端口号(自动的,即“隐式绑定”)。然后客户端指定要连接的服务器的IP地址和端口号。客户端会调用连接(connect)API,请求与服务器的指定端口和IP地址建立TCP连接。
服务器接受连接
当客户端的连接请求到达服务器时,服务器接受请求,解除阻塞状态,并返回一个新的套接字。这个新套接字专门用于与请求的客户端进行通信。通过返回新的套接字,服务器可以继续在欢迎套接字上监听其他的连接请求,允许它与多个客户端同时通信。同时,服务器可以使用源IP地址和源端口号来区分不同的客户端。
TCP连接建立:
当连接请求被接受,客户端和服务器之间建立了一个TCP连接,这时客户端的套接字和服务器的新套接字已经连接,可以开始数据交换。
数据传输:
一旦TCP连接建立,客户端和服务器之间就形成了一条可靠的通信“管道”。双方可以通过这条管道按需发送字节流,无需担心数据的顺序、可靠性问题。
数据传输完成后,客户端和服务器将关闭他们的套接字,并释放资源。
下面介绍两个在网络编程中常用的数据结构,sockaddr_in
和 hostent
。他们应用于进行地址和端口的配置以及域名解析。
sockadd_in
sockaddr_in
是一个用于存储地址和端口的数据结构,在IPv4通信中非常重要。下面是该数据结构每个字段的说明。
short sin_family
:地址族。u_short sin_port
:端口号。struct in_addr sin_addr
:IP地址。这是一个结构体,它包含了一个无符号长整型的字段,用于存储网络接口的IPv4地址。char sin_zero[8]
:填充字段,确保sockaddr_in
的总长度与sockaddr
结构相同。这个字段不用于网络通信,只是为了保持内存对齐,通常设置为0。hostent
hostent
结构用于保存域名解析函数的返回信息,它在域名到IP地址的转换过程中使用:
char *h_name
:官方的主机名。char **h_aliases
:主机的别名列表,是一个以NULL结尾的数组。int h_addrtype
:地址类型,通常是 AF_INET
。int h_length
:地址的长度,对于IPv4来说,通常是4字节。char **h_addr_list
:IP地址列表,是一个以NULL结尾的数组。对于IPv4,每个地址是一个4字节的数。h_addr
是h_addr_list
的第一个地址的别名,用于历史兼容。当使用如gethostbyname()
这样的函数时,如果您查询的是主机名,它会返回一个指向hostent
结构的指针。然后,您可以取出h_addr_list
中的第一个地址(h_addr
),并将其复制到sockaddr_in
结构的sin_addr
字段中,以用于创建网络连接。
例子:C客户端(TCP)
#include
#include // 标准库(用于 atoi 函数)
#include
#include
#include
#include // Internet地址族
#include // 网络数据库操作
int main(int argc, char *argv[])
{
struct sockaddr_in sad; // 用来保存服务器IP地址的结构
int clientSocket; // 套接字描述符
struct hostent *ptrh; // 指向主机表条目的指针
char Sentence[128]; // 发送数据的缓冲区
char modifiedSentence[128]; // 接收数据的缓冲区
int port; // 端口号
char *host; // 主机名指针
if (argc != 3) {
fprintf(stderr, "Usage: %s \n" , argv[0]);
exit(1);
}
host = argv[1];
port = atoi(argv[2]);
// Create a socket
clientSocket = socket(PF_INET, SOCK_STREAM, 0);
if (clientSocket < 0) {
fprintf(stderr, "Error: Socket creation failed.\n");
exit(1);
}
// Clear sockaddr structure
memset((char *)&sad, 0, sizeof(sad));
sad.sin_family = AF_INET; // Set family to Internet
sad.sin_port = htons((u_short)port); // Set port number
// Convert host name to equivalent IP address and copy to sad
ptrh = gethostbyname(host);
if (ptrh == NULL) {
fprintf(stderr, "Error: Invalid host name.\n");
close(clientSocket);
exit(1);
}
memcpy(&sad.sin_addr, ptrh->h_addr, ptrh->h_length);
// Connect the socket to the specified server
if (connect(clientSocket, (struct sockaddr *)&sad, sizeof(sad)) < 0) {
fprintf(stderr, "Error: Connection failed.\n");
close(clientSocket);
exit(1);
}
// Read a sentence from the user
printf("Enter a line of text:\n");
fgets(Sentence, sizeof(Sentence), stdin);
// Send the line to the server
if (write(clientSocket, Sentence, strlen(Sentence) + 1) < 0) {
fprintf(stderr, "Error: Failed to send data.\n");
close(clientSocket);
exit(1);
}
// Read the server's reply
if (read(clientSocket, modifiedSentence, sizeof(modifiedSentence)) < 0) {
fprintf(stderr, "Error: Failed to receive data.\n");
close(clientSocket);
exit(1);
}
// Output the modified sentence
printf("FROM SERVER: %s\n", modifiedSentence);
// Close the connection
close(clientSocket);
return 0;
}
C服务器(TCP):
#include // 标准输入输出定义
#include // 标准库(用于 atoi 函数)
#include // 内存操作
#include // UNIX标准函数定义
#include // 套接字主要头文件
#include // Internet地址族
#include // 网络数据库操作
void capitalizeSentence(char *str); // 假设此函数定义为将字符串转换为大写
int main(int argc, char *argv[])
{
struct sockaddr_in sad; // 结构体保存服务器的IP地址
struct sockaddr_in cad; // 结构体保存客户端地址
int welcomeSocket, connectionSocket; // 套接字描述符
socklen_t alen; // 地址长度
char clientSentence[128]; // 客户端发送的句子
char capitalizedSentence[128]; // 服务器将要发送回去的句子
int port; // 端口号
if (argc != 2) {
fprintf(stderr, "Usage: %s \n" , argv[0]);
exit(1);
}
port = atoi(argv[1]);
// 创建套接字
welcomeSocket = socket(PF_INET, SOCK_STREAM, 0);
if (welcomeSocket < 0) {
fprintf(stderr, "Error: Socket creation failed.\n");
exit(1);
}
// 清零 sockaddr 结构
memset((char *)&sad, 0, sizeof(sad));
sad.sin_family = AF_INET; // 设置地址族为Internet
sad.sin_addr.s_addr = INADDR_ANY; // 设置本地IP地址
sad.sin_port = htons((u_short)port); // 设置端口号
// 绑定套接字
if (bind(welcomeSocket, (struct sockaddr *)&sad, sizeof(sad)) < 0) {
fprintf(stderr, "Error: Binding failed.\n");
close(welcomeSocket);
exit(1);
}
// 监听套接字
if (listen(welcomeSocket, 10) < 0) { // 10是最大客户端排队数
fprintf(stderr, "Error: Listening failed.\n");
close(welcomeSocket);
exit(1);
}
// 服务器主循环
while(1) {
alen = sizeof(cad);
// 接受连接
connectionSocket = accept(welcomeSocket, (struct sockaddr *)&cad, &alen);
if (connectionSocket < 0) {
fprintf(stderr, "Error: Accept failed.\n");
continue;
}
// 读取客户端句子
if (read(connectionSocket, clientSentence, sizeof(clientSentence)) < 0) {
fprintf(stderr, "Error: Reading from socket failed.\n");
close(connectionSocket);
continue;
}
// 转换句子为大写
capitalizeSentence(clientSentence);
strcpy(capitalizedSentence, clientSentence); // 假设转换已完成
// 发送转换后的句子回客户端
if (write(connectionSocket, capitalizedSentence, strlen(capitalizedSentence)+1) < 0) {
fprintf(stderr, "Error: Writing to socket failed.\n");
}
// 关闭连接
close(connectionSocket);
}
// 关闭欢迎套接字
close(welcomeSocket);
return 0;
}
// 假设capitalizeSentence函数的实现
void capitalizeSentence(char *str) {
// 逻辑实现将str中的每个字符转换为大写
}
UDP在客户端和服务器之间没有连接,不进行握手。
发送端需要再每个UDP数据报中指定接收方的IP地址和端口号,服务器接收到数据报后,必须从该数据报中提取发送端的IP地址和端口号。
UDP数据报可能丢失或乱序,没有内建的重传和顺序控制机制。
进程视角看UDP服务:
UDP为客户端和服务器提供不可靠的字节组的传送服务。
UDP的客户端和服务器之间的套接字交互过程较为简单,因为它不需要建立和维护一个连接。下面是详细的交互过程:
创建UDP套接字:
客户端通过调用socket()
函数创建一个数据报套接字,指定PF_INET
作为协议族和SOCK_DGRAM
作为套接字类型。
int clientSocket = socket(PF_INET, SOCK_DGRAM, 0);
构造服务器地址:
客户端需要知道服务器的IP地址和端口号,因此它将这些信息填入一个sockaddr_in
结构体中。使用函数inet_pton()
来将点分十进制的IP地址转换为网络字节顺序的二进制形式,端口号则用htons()
转换为网络字节顺序。
发送数据:
使用sendto()
函数,客户端将数据发送到指定的服务器地址和端口号。在sendto()
调用中,客户端必须提供包含服务器地址的sockaddr_in
结构体,以及该结构体的大小。
sendto(clientSocket, message, messageLength, 0, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
接受相应:
客户端可以选择性地使用recvfrom()
函数等待并接收服务器的响应。这个函数也会返回发送端的地址信息,客户端可以使用这些信息来验证响应的来源。
recvfrom(clientSocket, buffer, bufferLength, 0, (struct sockaddr *)&fromAddr, &addrLength);
关闭套接字:
通信完成后,客户端关闭套接字释放资源。
close(clientSocket);
客户端交互的完整代码:
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 9876 // 服务器的端口号
#define SERVER_IP "127.0.0.1" // 服务器的IP地址
#define MAXLINE 1024
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in serveraddr;
char sendline[MAXLINE], recvline[MAXLINE];
int n;
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 设置服务器地址
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(SERVER_IP);
serveraddr.sin_port = htons(SERVER_PORT);
while (fgets(sendline, MAXLINE, stdin) != NULL) {
// 发送数据到服务器
sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *) &serveraddr, sizeof(serveraddr));
// 从服务器接收响应
n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0; // null terminate
fputs(recvline, stdout);
}
// 关闭套接字
close(sockfd);
return 0;
}
创建UDP套接字: 类似于客户端,服务器也通过调用socket()
函数来创建一个UDP套接字。
绑定套接字: 服务器使用bind()
函数将其套接字绑定到一个地址和端口上,这样它就可以在该端口上接收到达的数据报。
bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
等待数据: 服务器使用recvfrom()
函数等待来自客户端的数据。当数据报到达时,recvfrom()
会填充一个sockaddr_in
结构体,其中包含了发送端的地址信息。
recvfrom(serverSocket, buffer, bufferLength, 0, (struct sockaddr *)&clientAddr, &addrLength);
发送响应: 服务器处理接收到的数据后,可以使用sendto()
函数发送响应回客户端。在sendto()
调用中,服务器提供客户端的地址信息,以确保响应能被发送到正确的目的地。
sendto(serverSocket, response, responseLength, 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr));
服务器交互的完整代码:
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 9876 // 服务器的端口号
#define MAXLINE 1024
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in serveraddr, clientaddr;
char msg[MAXLINE];
socklen_t clientlen;
int n;
// 创建UDP套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 设置服务器地址
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(SERVER_PORT);
// 绑定套接字到地址
bind(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));
while (1) {
clientlen = sizeof(clientaddr);
// 接收来自客户端的数据
n = recvfrom(sockfd, msg, MAXLINE, 0, (struct sockaddr *) &clientaddr, &clientlen);
msg[n] = 0; // null terminate
printf("Received: %s", msg);
// 将接收到的消息发送回客户端
sendto(sockfd, msg, n, 0, (struct sockaddr *) &clientaddr, clientlen);
}
// 关闭套接字(理论上服务器是不会停止的,但这是一个好习惯)
close(sockfd);
return 0;
}
在这整个交互过程中,UDP协议不保证数据包的顺序、可靠传输或是数据包不重复。因此,任何必要的可靠性措施(如确认回执、重传机制等)都需要在应用层实现。UDP的优势在于它的轻量级和低延迟特性,非常适合那些对时效性要求高的应用,如视频会议或在线游戏。