Chapter 2: Application Layer

Network applications are the raisons d’être of a computer network—if we couldn’t conceive of any useful applications, there wouldn’t be any need for networking infrastructure and protocols to support them.

2.1 Principals of Network Applications

当我们有一个网络应用的点子时,我们得先明白现实世界中的网络应用是如何运行的。在网络应用最核心的开发任务就是编写能够跑在不同终端并使得它们可以经由网络核心(network core)互相通信的程序代码。在Web应用中,有两类应用不断地互相通信,一类是运行在用户host上的浏览程序(browser program),另一类是运行在Web服务器上的Web服务程序(Web server program)。
Pasted image 20240527021928.png

2.1.1 Network Application Architectures

应用程序开发者的角度来看,网络架构是固定的(fixed),并且为应用程序提供了一套特定的服务。换句话说,当开发者创建应用程序时,他们可以依赖这个预先定义好的网络架构来构建功能,而不需要关心网络的底层细节。

  • application architectures的选择上应用程序员通常会从下面当今主流的两种结构中进行选择:
    1.The client-server architecture:
    - 在客户机-服务器体系结构下,服务器总会一直运行等待客户机的服务请求。当Web服务器收到来自客户机的请求对象服务时,服务器就会返回被请求的对象。
    - 这种体系结构有一些特点值得我们注意:在客户机-服务器模式下client-hosts不能直接通信;而且服务器有一个固定且周知的地址,我们称为IP地址。因而,客户机总能通过发送packet数据包的方式和服务器进行通信连接。
    - 遵从客户机-服务器模式的应用有Web, FTP, Telnet, e-mail等。
    - 通常情况下,单服务器不足以响应来自客户机的所有请求,which can be overwhelmed to the server. 因此,数据中心(Data center) 应运而生。我们熟知的各种搜索引擎和很多大型社交应用就是运行在散布世界各地的data center上的。
    2.The peer-to-peer (P2P) architecture(P2P file sharing system)
    - 和客户机-服务器体系结构严重依赖数据中心不同,对等网络的应用对数据中心几乎没有依赖,它们通过hosts在网络上的互联间接的通信。
    - P2P体系结构中的每个host(被称为peer)即充当着服务器的角色——响应其他peer host的服务请求的同时它们也充当着客户机的角色——向其他peer host发起服务请求。
    - 这些对等体不受服务提供商所有,它们运行在用户所有的终端设备上。因为这些对等体应用之间的通信不经由data center,因而被叫做peer-to-peer。
    - P2P体系结构最引人入胜的特性就是自扩展性(self-scalability)。这意味着每当新用户加入P2P网络,网络的总体带宽和资源分发能力也随着新用户的加入而增长。这是因为新用户不仅从网络中下载内容,同时也成为了内容的分发者。而不是像传统的客户机-服务器模型那样,服务器的负载随用户数量增加而线性增长。例如,在BitTorrent这样的P2P文件分享系统中,每个用户在下载文件的同时,也会上传已经下载的文件部分给其他用户。这样,文件的分发不再依赖于单一的服务器,而是由网络中的所有用户共同完成,从而实现了自扩展。
    - Cost effective,因为P2P体系结构对数据中心的依赖降低,因此可以在组件data center服务器上省下一大笔开销。
    Pasted image 20240527032107.png

2.1.2 Processes Communicating

  • 当程序载入内存在host上运行的时候,它就变成了进程(process)。当我们说不同end systems之间的通信时,实际上是Application layer之间的通信,即不同宿主机中进程的通信。在计算机网络课程中,我们不关心同一宿主机里的进程间通信。
  • 不同终端间的进程通过计算机网络交换报文(exchanging messages) 来与彼此通信。 发送进程创建报文并将其转发到网络上;接收进程接收这些报文并作出回应。
Client and Server Processes
  • 一个网络应用要实现不同end system上进程的通信,就要包括一对进程,即:
    (1)发送报文的进程——我们将其标为Server;
    (2)接收报文的进程——我们将其标为Client。
    • 在客户机-服务器体系结构下,孰是客户机孰是服务器很好辨别,因为服务器(Content Service Provider)总是向客户机(User's host)发送报文信息。
    • 在P2P体系结构下,end system的身份就相对模糊起来。因为peer host即可能是上传信息的一方(响应请求),也可能是下载信息(发起请求)的一方。
  • 我们对client和server的定义如下:
    In the context of a communication session between a pair of processes, the process that initiates the communication (that is, initially contacts the other process at the beginning of the session) is labeled as the client. The process that waits to be contacted to begin the session is the server.
    即发起会话请求的是客户端(客户端请求),接收并反映请求的是服务器端(服务器端回应)。
The Interface Between the Process and the Computer Network

从一个进程向另一个进程传输的报文都要经由下层网络。我们之前提到:计算机网络的下层是为上层的服务而存在的。在这里我们会有疑问,网络栈中 应用层传输层 之间的接口是什么呢?这个处于应用和网络之间的软件接口API就叫做Socket——应用进程与传输层协议间的接口。
Pasted image 20240527174749.png

  • 在网络应用开发时,开发者只对socket接口的应用层一侧有完全的掌握,但对传输层一侧几乎没有控制。但开发者对传输层一侧能够控制的有:
    (1)传输层协议(choice of transport protocol)
    (2)修改一些传输层的参数(ability to fix a few transport-layer parameters)
Addressing Processes
  • 要实现两进程之间的通信,我们就得先知道destination process的地址是什么。要知道目标进程的确切地址,我们需要知道:
    (1)主机地址——IP address
    (2)区别目标主机的接收进程的标识——Port number
    Popular applications have been assigned specific port numbers. For example, a Web server is identified by port number 80. A mail server process (using the SMTP protocol) is identified by port number 25. A list of well-known port numbers for all Internet standard protocols can be found at www.iana.org. We’ll examine port numbers in detail later

2.1.3 Transport Services Available to Applications

前文提到Socket是应用进程与传输层协议间的接口,应用进程的发送方将报文信息经由socket传给传输层协议——which has the responsibility of getting the messages to the socket of the receiving process. 因此,开发者在开发应用时必须选择一个传输层协议来保证报文从网络层向传输层的正常传递。

  • 在传输层协议的选择上我们往往会考虑如下方面:
    • 数据传输的可靠度
    • 吞吐率
    • 时延
    • 安全性
Reliable Data Transfer

我们之前了解过,数据包packet在进程到进程传输过程中可能发生丢失、某些bit位出错的情况。在一些如e-mail、金融软件和文件传输的情况时,数据丢失可能造成严重的后果。为保证数据传输时的包完整度,一些协议提供这种可靠的信息传输。一旦传输层协议提供这种服务,发送进程即可将信息包传给socket且保证在信息传输过程中的不出错。

一些协议不提供这样的可靠性,这样的协议可能为那些loss-tolerant applications所用。比如一些音视频的多媒体应用,它们容许在信息传出中一定量的数据丢失。

Throughput

吞吐率是用来衡量sending process能够向receiving process传输bit的速率大小。我们已经学习过在网络路径上可能不止一条通信会话,可能会有多个会话共享网络路径的带宽,随着其他会话的上线下线,可用带宽会随时间发生变化。这种现象自然地引出另一种传输层提供的服务——guaranteed available throughput at some specified rate。有这种服务的保证,应用可用要求一个最低r bits/sec的最小吞吐率,这对那些带宽敏感的应用是极为需要的。一些对吞吐率变化不敏感的应用——弹性应用(elastic application) 对这种保证性服务就没有这么需要了。

Timing

传输层协议也可以提供时间保证(time guaranteed)的服务。Timing guarantees 可以多种方式提供给应用进程,如guarantee might be that every bit that the sender pumps into the socket arrives at the receiver’s socket no more than 100 msec later.

Security

传输层也会向应用提供一个或多个安全服务(Security services)。例如,在发送端,传输层可以加密(encrypt)发送进程所发送的所有信息,并在接收端的传输层解密(decrypt)后将信息传给接收进程。

2.1.4 Transport Services Provided by the Internet

The Internet(通常上是TCP/IP协议族网络),向应用提供两种传输协议——TCP和UDP。开发人员在开发网络应用时,最先考虑的问题之一就是应用应当使用TCP协议还是UDP协议。这两个协议中的每种协议都提供了不同的服务集。
Pasted image 20240528195845.png

TCP Services

TCP服务模型涵括了面向连接的服务可靠数据传输的服务,当选用TCP作为其传输层协议时,应用就获得了TCP所提供的这两种服务。

  • Connection-oriented service :在报文开始传输前,TCP要求客户端和服务器端互换传输层信息,这也称作握手(handshaking)。握手阶段完成之后,一个进程socket间的TCP connection就建立完成了,这个连接是全双工的。当报文发送结束,应用就必须切断连接。
  • Reliable data transfer service:依靠TCP协议通信的进程所发送的所有信息都是顺序正确且无误的。TCP也提供拥塞控制机制(congestion-control mechanism),当接发双方间存在拥塞时,TCP拥塞控制机制会遏制发送进程继续发送报文。
UDP Services

相比TCP,UDP是”轻便“简洁的,它没有一些附加功能,是一种轻量的传输协议。

  • UDP是无连接的,因此传输报文前不需要握手。
  • UDP也不提供可靠传输服务,因此不保证接收进程收到报文的完整和准确性。
  • UDP也不提供拥塞控制机制。

*Note that neither of these two protocol provides any securing encryption.Transport Layer Security (TLS) is Internet community developing for TCP enhancement. It's not provided by TCP
or UDP itself.

Services Not Provided by Internet Transport Protocols

我们之前通过四个维度总结传输层协议提供的服务:可靠数据传输、吞吐率、timing、安全性。在前面谈论TCP和UDP所提供服务时都漏掉时间和吞吐率保证,这是因为现今的传输层协议不提供时间和吞吐率的保证服务。但这不意为着不能使用Internet拨打电话或视频通话。这是因为如今的互联网可以为time-sensitive application提供满意的服务,但不提供任何timing和throughput的保证。
Pasted image 20240528210914.png

2.1.5 Application-Layer Protocols

我们已经了解到网络进程间的通信是通过socket这个API实现的。但我们仍不了解关于messages报文是如何构造的?报文中不同fields的含义是什么?进程何时发送报文?Application-layer protocol会告诉我们答案。一个应用层协议的具体定义如下:

  • The types of messages exchanged, for example, request messages and response messages
  • The syntax of the various message types, such as the fields in the message and how the fields are delineated
  • The semantics of the fields, that is, the meaning of the information in the fields
  • Rules for determining when and how a process sends messages and responds to messages

一些应用层协议在RFC中能够找到详细定义,如HTTP;但也有一些应用层协议作为财产专利不公之于众,如Skype。
弄明白网络应用和应用层协议的区别很重要,应用层协议知识网络应用的一部分,我们举一个例子:

  • Web 是一个客户端-服务器应用,允许用户按需从Web服务器获取文档。
  • Web应用 包含多个组件,比如:
    • 文档格式的标准(即HTML)
    • Web浏览器(例如Chrome和Microsoft Internet Explorer)
    • Web服务器(例如Apache和Microsoft服务器)
  • 应用层协议,即HTTP,定义了浏览器和Web服务器之间交换的消息的格式和顺序。

2.1.6 Network Applications Covered in This Book

网络应用日新月异,第二章将以其中一小部分切入讲解:the Web, electronic mail, directory service, video streaming, and P2P applications。它们应用最广泛且易于理解。

2.2 The Web and HTTP

90年代初期之前,网络只能做一些很简单的应用。后来,现象级的应用横空出世——World Wide Web。Web应用是第一个进入大众视线的Internet应用。Web的出现极大地改变了人们在工作环境内外的互动方式,使得互联网从众多数据网络中脱颖而出,成为了基本上唯一的全球性数据网络。

2.2.1 Overview of HTTP

超文本传输协议(HyperText Tranfer Protocol) 是Web应用在应用层中的核心协议。HTTP完成两份程序——客户端程序和服务器程序,这些程序运行在不同的end system上并通过HTTP报文相互通信。HTTP规定了应用报文的格式和这些报文传输的方式。

HTTP相关概念
  • A Web page(document) 包含了许多对象(文件),例如HTML文件 、JPEG图片、JS文件、CCS格式的文本文件或一段视频等。一般的Web pages包括一个base HTML file和若干相关对象(如一些图片)组成。这些文件都有一个单一的URL(Uniform Resource Locator) 所标记,目标gif的URL的格式如下:

    其中是服务器主机地址,而 则是一个路径名。
  • 在Web应用中我们一般使用Web browserWeb server来表示Web应用的Client side或Server side。Web server通常由一个URL地址表示。它们都是遵循HTTP协议的程序,规范了Web clients向Web server索求Web pages的方式和Web servers向Web clients传输Web pages的方式。
  • 下图展示了一个简单的HTTP报文传输过程:首先Web clients通过HTTP请求报文向Web server发送一个网页对象的请求,随后server收到请求并通过包含对象的HTTP响应报文以响应。
    Pasted image 20240603225246.png
  • 由于传输过程中对准确性的要求,HTTP协议使用TCP作为下层的传输层。HTTP clent先初始化与server的TCP连接,一旦连接被建立,browser进程和server进程就可以通过其socket 接口进行TCP的通信。当client将报文发送到socket 接口之后,报文就进到TCP的掌握之中。之后,不论报文信息丢失还是恢复,这些都不是HTTP所关心的范畴(而是传输层及下面协议所关注的内容)。
  • 需要留意的是HTTP server在向HTTP client发送请求文件时,并不记录关于client的任何信息。因而,当client在短时间向server发送两次关于同一对象的请求报文时,server会死板地再次发送请求文件。正是HTTP server不保存clients的信息,HTTP因此被称作stateless protocol

2.2.2 Non-Persistent and Persistent Connection

在许多Internet应用中,client和server会彼此相互通信很长一段时间。在这一大段时间里,client发送一系列请求,server回应这一系列请求。因此,我们产生了一个疑问:这些请求-响应是应该通过多个不同的(separate)TCP连接发送?还是通过一个保持连接的(same)TCP连接发送?

HTTP with Non-Persistent Connections
  • 我们先介绍非持续连接的HTTP协议。假设网页中由一个base HTML和10张JPEG图片,也就是11个对象在同一个服务器resident。以下是base HTML的URL:

    在整个非持续链接的过程中发生了:
    1. HTTP client 通过端口80初始化与服务器 的TCP连接。其中80号端口是HTTP默认的端口号。同建立的TCP连接一道建立的还有client side和server side各自的socket 接口。
    2. HTTP client通过其socket向HTTP server发送一个请求报文,其中包括着路径名 (We will discuss HTTP messages in some detail below.)
    3. HTTP server通过其socket收到来自client的请求报文,从memory(RAM或disk)中读出请求对象的内容, 将对象在HTTP应答报文中进行封装并从其socket中向client送出应答报文。
    4. HTTP 服务进程告诉TCP关闭TCP连接。
    5. client process收到响应报文,TCP连接关闭
    6. 重复直到客户端进程收到所有网页对象。
      以上的步骤展示了一个非持续链接的例子,下一个对象的传输与上一个对象请求所建立的TCP连接没有关系(可以串行也可以一定程度的并行传输)。HTTP/1.0采用的就是这样的非持续连接的方式。
  • 在学习Persistent Connections前,我们先了解以下RTT(Round Trip Time) 的概念。RTT是一个small packet(因为包很小,因此不考虑传输时延)从客户端进程到服务器进程再回到客户端进程的时间,与包传播时延、包排队时延、包处理时延有关。下图示例中,从用户点击一个超链接(hyperlink)开始,总共的响应时间粗略地计算为:Pasted image 20240604010021.png
HTTP with Persistent Connections
  • 通过上面对非持续连接的HTTP的学习,我们能够看到这种方式的缺点是非常明显的:每一次的对象请求都需要建立一个全新的TCP连接。如此,必须额外地分配TCP buffer且TCP variables必须在client side和server side二者中保存起来。这样的特点使得Web server的burden会是十分严重的。而且每次重新建立TCP的初始化连接还会额外地浪费一次RTT的时间。
  • 提供持续连接的HTTP/1.1,server会在发送响应报文后仍然打开TCP连接。之后client和server之间的请求和回应就都将建立在同一个已建立的TCP连接上。如此一来,每次请求和应答就免去了TCP连接初始化的步骤,省去一个RTT时间。

2.2.3 HTTP Message Format

HTTP的规定了HTTP报文格式(HTTP massage formats) 。HTTP报文有两种格式:(1)HTTP请求报文格式(2)HTTP响应报文格式 。

HTTP Request Massage

下面我们给出一个简单的HTTP请求报文:

GET /somedir/page.html HTTP/1.1 
Host: www.someschool.edu 
Connection: close 
User-agent: Mozilla/5.0 
Accept-language: fr
  • 其中我们可以了解很多信息。首先,HTTP报文是用ASCII码编写的,因此我们可以通过阅读轻易了解其中的含义。为方便阅读,每一行以一个回车符(CR)和一个换行符(LF)作为结束,将报文划分为“五行”。最后一行额外地多一个CR和LF。
    1. HTTP请求报文的第一行叫作请求行(Request line) 。请求行有三个字段(field):HTTP方法字段、URL字段、HTTP版本字段。
      • 方法字段多个不同的操作方式,GETPOSTHEADPUTDELETE。本例中请求操作方式是GET,表示浏览器向服务器请求一个对象。
      • Method field中所请求操作的对象就放在URL字段中。
      • 最后版本信息在版本字段中表示。本例中HTTP版本为HTTP/1.1。
    2. HTTP请求报文之后的行(lines)的叫做头部行(Header lines)
      • 头部行Host: www.someschool.edu 指明了对象所在的host位置。
      • 头部行Connection: close 表示浏览器告诉服务器在自己接收到目标报文后关闭TCP连接。
      • 头部行User-agent: Mozilla/5.0 是发起此请求浏览器的种类。
      • 头部行Accept-language: fr向服务器指明用户期望得到一个法语版本的html对象。
    3. 在下图中,我们发现在头部行的下面还有一片实体空间在例子中没有得到体现,这是因为一般在POST操作时才会用到下面的entity body。
      Pasted image 20240605003249.png
HTTP Response Message

下面我们给出一个简单的HTTP响应报文。

HTTP/1.1 200 OK 
Connection: close 
Date: Tue, 18 Aug 2015 15:44:04 GMT 
Server: Apache/2.2.3 (CentOS) 
Last-Modified: Tue, 18 Aug 2015 15:11:03 GMT 
Content-Length: 6821 
Content-Type: text/html 
(data data data data data ...)
  • 从响应报文的格式来看,我们不难看出它是对请求报文的响应,包含一个状态行(Status line) 、六个头部行(Header line)响应实体(Entity body)
    1. 状态行包括三个字段:HTTP版本字段、状态码(请求成功或失败的状态)和原因短语(提供状态码的附加信息)。
      • 在这个例子中,HTTP/1.1 200 OK 表示协议版本是HTTP/1.1,并且everything is OK。
      • 常见的状态码和原因短语有:
        (1)200 OK: Request succeeded and the information is returned in the response.
        (2)301 Moved Permanently: Requested object has been permanently moved; the new URL is specified in Location: header of the response message. The client software will automatically retrieve the new URL.
        (3)400 Bad Request: This is a generic error code indicating that the request could not be understood by the server.
        (4)404 Not Found: The requested document does not exist on this server.
        (5)505 HTTP Version Not Supported: The requested HTTP protocol version is not supported by the server.
    2. 之后的六行都是头部行
      - 头部行Connection: close 告诉客户机进程在发送完报文后就要关闭TCP连接了。
      - Date:头部行表示了服务器响应并发出对象报文的时间。
      - Server:头部行类似于User-agent头部行,指出报文是被Apache/2.2.3 (CentOS) 所发出。
      - Last-Modified:头部行表示源服务器认为资源最近一次被修改的日期。
      - Content-Length:头部行声明了对象的大小,单位为byte。
      - Content-Type:头部行声明了对象的格式,在本例中是html格式的文本。
      Pasted image 20240605012506.png

2.2.4 User-Server Interation: Cookies

我们前文提到,HTTP协议提供的是一个无状态的服务,这简化了服务器设计,提高了服务器的性能。然而由于user identification的需求,HTTP使用Cookies使网站能够保存用户的轨迹,以便提供面向用户的内容。

  • 下图中,我们可以看到cookie由4部分组成(假设之前访问过ebay):
    (1) a cookie header line in the HTTP response message;
    (2) a cookie header line in the HTTP request message;
    (3) a cookie file kept on the user’s end system and managed by the user’s browser;
    (4) a back-end database at the Web site.
    Pasted image 20240605013943.png

2.2.5 Web Caching

Web cache—也叫代理服务器(proxy server),是一种代表源Web服务器为HTTP请求提供服务。下图中演示了一个代理服务器的工作方式,当用户浏览器发送HTTP请求报文时,会先在Web cache中寻找是否存有请求对象,因此Web cache需要拥有自己的磁盘存储来拷贝保存最近被请求的对象。
Pasted image 20240605160542.png

  • 假如我们现在需要请求对象。则会有以下步骤:
    1. 浏览器先建立与Web cache的TCP连接并向Web cache发送对象的HTTP请求报文。
    2. Web cache检查本地是否存储该对象,如果有,则将对象包含在HTTP响应报文的实体空间(entity body)中返回给浏览器。
    3. 如果Web cache本地不存有该对象信息,Web cache就会和地址是的源服务器建立一个TCP连接。随后Web cache会在cache-server的TCP连接上向源服务器发送HTTP请求报文,然后源服务器将目标对象包含在响应报文中发送给Web cache。
    4. 当Web cache收到对象后,它会将对象拷贝放在本地,然后将拷贝的对象包含在响应报文中发送给client浏览器进程。
      在这里我们可以发现,Web cache实际上同时充当了client和server的身份。有了Web cache,(1)client请求的响应时间会减少;(2)cache的存在减少了institution访问Internet的拥塞。
      一般而言,Web cache受ISP所管理。
  • 为了更深入地理解cache的工作原理,我们用下图的例子来解释。我们看到一个LAN-Institutional network还有WAN——Public Internet。其中LAN内部的网络带宽达到了100Mbps而LAN接入WAN的链路只有15Mbps的带宽。
    Pasted image 20240605164341.png
  • 假设每秒传输15个大小1Mbps的对象,忽略请求报文的大小,则有LAN上的网络拥塞度(traffic intensity)为:而接入WAN链路的网络拥塞度为:我们之前就了解过,当网络拥塞度达到接近1时,链路上的延时会达到分钟级别,这是无法被机构用户所接受的。一种解决办法是将公网接入链路的带宽增加到100Mbps,这样的总相应时间大约2s。另一种就是加入cache,将用户常访问的objects存储到本地,这样用户的请求就不必再占用公网的带宽了。
  • 假设Web cache的hit rate是0.4(即40%的请求都能通过cache得到满足),则平均时延就成了:而且减少了接入链路带宽的占用率,而且获得了比第一种解决方案更小的response time。
  • 即便Content Distribution Networks(CDNs) 得到了广泛的应用,但是Web cache仍然发挥着不可或缺的作用。它们的关系就如同main memory和cache memory的关系一样。
The Conditional GET
  • 尽管caching的方法有效的降低了响应时间,但也带来了新的问题——处在cache中的信息可能是过时的。因此,HTTP提供一种允许cache检查数据是否是”fresh“的机制叫做conditional GET。如果HTTP 请求报文(1)使用GET方法;并且(2)包含If-Modified-Since:头部行。则称这样的HTTP请求报文为conditional GET message
  • 现在,我们用一个例子来了解一下conditional GET是如何影响报文传输的吧:
    1. 首先,proxy server向Web server发送一个请求报文 ```
    GET /fruit/kiwi.gif HTTP/1.1 
    Host: www.exotiquecuisine.com
    
    1. 然后cache收到来自Web server的响应报文
    HTTP/1.1 200 OK 
    Date: Sat, 3 Oct 2015 15:39:29 
    Server: Apache/1.3.0 (Unix) 
    Last-Modified: Wed, 9 Sep 2015 09:23:24 
    Content-Type: image/gif 
    (data data data data data ...)
    
    1. 之后cache转发响应报文给client browser,并在本地缓存对象文件。其中,cache也会将Last-Modified日期存在本地,在之后收到来自client browser的对象请求时,proxy server可以通过conditional GET来检查本地的object是否是过时的
    GET /fruit/kiwi.gif HTTP/1.1 
    Host: www.exotiquecuisine.com 
    If-modified-since: Wed, 9 Sep 2015 09:23:24
    
    1. 第四步,proxy cache收到源服务器的响应报文。304 Not Modified表示object并没有修改过,因此entity body 不需要放任何数据。也使得proxy server可能放心地去转发之前拷贝的object。
    HTTP/1.1 304 Not Modified 
    Date: Sat, 10 Oct 2015 15:39:29 
    Server: Apache/1.3.0 (Unix) 
    (empty entity body)
    

2.2.6 HTTP/2

  • 制定于2015年的HTTP/2是千禧年后新一版的HTTP协议,之前是在1997年制定的HTTP/1.1。HTTP/2旨在通过单个TCP连接上的请求和响应多路复用来减少感知延迟,提供请求优先级和服务器推送功能,并有效压缩HTTP头字段。HTTP/2不改变HTTP方法、状态码、URL或头字段,而是改变数据在客户端和服务器之间的格式和传输方式。
  • 前面我们提到过,持久的TCP连接允许通过单个TCP连接从网页获取多个对象,但这可能会导致Head of Line(HOL) 问题,例如一个网页中有一个base HTML文本、一个长视频和视频后的一些小对象,这会导致这些小对象会一直等待长视频传输完成后才开始传输。HTTP/1.1对此的解决方法是打开多个并行的TCP连接,但这可能会导致网络的拥塞。
  • HTTP/2的主要目标之一是减少用于传输单个网页的并行TCP连接数量。这不仅减少了服务器需要打开和维护的套接字数量,还允许TCP拥塞控制按预期运行。但是,由于只有一个TCP连接来传输网页,HTTP/2需要精心设计的机制来避免HOL阻塞。
HTTP/2 Framing

HTTP/2对于HOL的解决方案是将报文”打碎“为多个帧(frame),然后多个对象轮番发送,按一定顺序发送第一帧接着第二帧等。这样能够极大的缩短用户的感知延迟。将HTTP报文成帧并在在client端重组报文是HTTP/2最大的改进之一。头部字段划分成一个帧,entity body被划分成一个或多个帧。

Responce Message Prioritization and Server Pushing

在client向server发送多个请求时,它可以在权重1-256之间设置请求报文的响应优先级。数字越大表示优先级越高。
HTTP/2还有能力向一个client的一个请求发送多个响应报文,在发送完源请求对应的响应报文后server将额外的对象push给client。

HTTP/3

2.25 File Tranfer Protocol*

FTP: 文件传输协议

□ 向用户主机上传输文件或从远程主机上接收文件
□ 客户机/服务器模式

  • 客户端:发起传输的一方
  • 服务器:远程主机
    □ ftp:[RFC 959]
    □ ftp服务器:端口号为21

在TCP/IP协议中, 需要两个端口,一个是数据端口,一个是控制端口。

控制端口一般为21,而数据端口不一定是20,这和FTP的应用模式有关,如果是主动模式,应该为20,如果为被动模式,由服务器端和客户端协商而定。相比于HTTP,FTP协议要复杂得多。复杂的原因,是因为FTP协议要用到两个TCP连接,一个是命令链路,用来在FTP客户端与服务器之间传递命令;另一个是数据链路,用来上传或下载数据。

FTP: 控制连接与数据连接分开

□ FTP客户端与FTP服务器通过端口21联系,使用TCP作为传输协议
□ 客户端通过控制连接获得身份确认
□ 客户端通过控制连接发送命令浏览远程目录
□ 收到一个问价传输命令时,服务器打开一个到客户端的数据连接
□ 一个文件传输完成后,服务器关闭连接
□ 服务器打开第二个TCP数据连接用来传输另一个文件

□ 控制连接:带外(out of band)传送
□ FTP服务器维护用户的状态信息:当前路径、用户账户与控制连接对应
(即FTP服务器维护client的状态)

FTP命令、响应

□ FTP 在控制连接上,信息以ASCII文本的方式传送

  • 命令样例
    • USER username
    • PASS password
    • LIST:请服务器返回远程主机当前目录检索文件(gets)
    • STOR filename:向远程主机的当前目录存放文件(puts)
  • 返回码样例
    • 状态码与状态信息(同HTTP)
    • 331 Username OK,
      password required
    • 125 data connection already open;
      transfer starting
    • 425 Can't open data connection
    • 452 Error writing file

2.3 Electronic Mail in the Internet

Electronic mail has been around since the beginning of the Internet. It was the most popular application when the Internet was in its infancy.

在本节,我们将介绍Internet e-mail中最核心的应用层协议SMTP(Simple Mail Transfer Protocol)

  • 从下图我们可以看到,Internet邮件系统由三部分构成:user agentsmail servers还有SMTP
    Pasted image 20240605212341.png
    • E-mail中一些有名的user agent包括Microsoft Outlook、Web-based Gmail、Gmail App 等。
    • Mail server是e-mail infrastructure中的核心。每个收件人都会有一个唯一某个mail server中的mailbox。在e-mail 系统中,报文由发送人的user agent发出,经由发件人的mail server送到收件人的mail server中,等待收件人查看邮箱中的内容。不仅如此,mail server 也会对收件人mail server中传输的失败情况作出反应。发件人的server会将报文放在message queue中,如果没有收到收件server的收到反馈,发件server就会每隔一段时间发送邮件。如果很长时间仍没有反馈,则用一则email通知发件人。
    • SMTP使用可靠的TCP传输协议来传送信件。和大多数应用层协议一样,SMTP有两个端构成—client side和server side。

SMTP

SMTP(Simlple mail trasfer protocol)是互联网最早出现的应用层协议之一(它出现于1982年,比HTTP还早)。由于它出现的时间很早,考虑的事情不完全,也因此,它在当今是一种legacy technology。例如:SMTP规定在发送E-mail报文时要先将报文的body部分编码成7-bits ASCII,7位的ASCII显然不适用于现代多媒体信息的传送,只适用文本的传输,也体现了其过时的性质。

  • 我们通过下面的例子简单看看SMTP是怎么工作的:
    1. Alice给她的user agent提供Bob的邮箱地址(如bob@someschool.edu)和信件并操作user agent发送报文。
    2. User agent将报文发送给Alice的mail server处,mail server会将这些e-mail排队依次发出。
    3. Alice的mail server通过TCP与另一台mail server连接。
    4. 在SMTP握手初始化完成后,SMTP client side通过TCP连接发送Alice的mail。
    5. Bob的mail server收到信件后将信件放在Bob的mailbox中。
    6. Bob随时查看。
      Pasted image 20240608041543.png
  • 现在我们来看看其中的更多细节:在client SMTP发送mail时,它会先和server SMTP的25号端口建立TCP连接。如果server下线,client之后会重新请求连接。一旦连接建立,client就会和server初始化SMTP应用层的握手。在这个阶段,SMTP client和server会互相发送email地址。这个过程完成后,client就可以发送报文了,SMTP可以提供报文传输的可靠性。client继续传输其他报文,如果报文没有了,client就会关闭TCP连接。
  • 下面我们看看SMTP会话的例子:
    S:  220 hamburger.edu 
    C:  HELO crepes.fr
    S:  250 Hello crepes.fr, pleased to meet you 
    C:  MAIL FROM: <alice@crepes.fr>
    S:  250 alice@crepes.fr ... Sender ok 
    C:  RCPT TO: <bob@hanburger.edu>
    S:  250 bob@hamburger.edu ... Recipient ok 
    C:  DATA 
    S:  354 Enter mail, end with ”.” on a line by itself 
    C:  Do you like ketchup? 
    C:  How about pickles? 
    C:  . 
    S:  250 Message accepted for delivery 
    C:  QUIT 
    S:  221 hamburger.edu closing connection
    
    • 我们可以看到这里client发送的命令HELOMAIL FROMRCPT TODATAQUIT。这些自解释性的命令我们很容易看明白。与HTTP一样,SMTP报文的每一行也是由CRLF结尾的(Carriage Return and Line Feed)。SMTP提供持久性的连接,因此,在我们没有发起QUIT请求前,我们可以一直用DATA命令向server发送多个email。
    • 我们可以通过telnet命令来直接与server建立直接的会话,像这样:
      telnet servername 25
      这样就在本地主机和邮件服务器之间建立一个TCP连接。之后,我们应当收到服务器的220回复。
      连接成功后,我们就可以发送SMTP命令了:
    HELO yourDomain.com
    MAIL FROM: <yourEmail@yourDomain.com>
    RCPT TO:<recipientEmail@recipientDomain.com>
    DATA
    Subject: Test Email
    This is a test email sent from Telnet.
    .
    QUIT
    

2.3.2 Mail Message Formats

在向别人写信时,我们一般会在信纸上写上“给某某”和“爱你的某某”。SMTP中mail message的格式也一样,在真正写信之前,必须先写上头部行。每个message头都必须包含From:头部行和To:头部行,Subject头部行时可以舍去的。
SMTP的massage header可能是这样的:

From: alice@crepes.fr 
To: bob@hamburger.edu 
Subject: Searching for the meaning of life.

2.3.3 Mail Access Protocols

通过之前对SMTP的学习,我们可能会有mail server存在意义何在的疑问。确实,信件总是先被传送到mail server后再转发给用户,总会有让用户有信息安全方面的担忧。但是,让用户时时刻刻保持在线连网的状态对很多人而言是不现实的。因此,我们在发电邮时会先将email发到一个always-on shared mail server中。

  • 我们接着了解一下email message的传输路径吧!实际上,email是可以直接由发信人的agent之间发送到收信人的mail server中的。但一般我们不这样做,如下图:
    Pasted image 20240609005629.png
    • 至此,我们仍有一个疑问——收件人(recipient)Bob是如何收到给他的message的?我们学习了SMTP,但是SMTP只是发信的协议(push protocol)。
    • 当今,我们主要有两种方式来将mail server中的信件转发给user agent——HTTPInternet Mail Access Protocol(IMAP)。前者一般用于Web-based email或手机应用(如Gmail),后者用在mail clients(如Outlook)。

2.4 DNS-The Internet's Directory Service

Just as humans can be identified in many ways, so too can Internet hosts.

2.4.1 Services Provided by DNS

在Internet中,有两种方式来识别一个主机:(1)通过hostname;(2)通过IP地址;可以理解为human name和human ID number。人们当然更喜欢便于记忆的hostname,但是router路由时当然喜欢定长结构化的IP地址进行路由。这也就是DNS的主要工作。

在Internet中,DNS是(1)不同层次的DNS服务器组成的分布式数据库;(2)允许hosts查询分布式数据库的的应用层协议

通常情况下,DNS运行在BIND(Berkeley Internet Name Domain)软件上,DNS一般采用UDP传输层协议,使用53号端口

DNS作为一个工具,常常被同为应用层的协议如:HTTP、SMTP所使用。下面举例简单说明一下DNS是怎么工作的。
假设browser要请求URL 中的内容,在发送请求报文前:

  1. client host在53号端口运行DNS client应用;
  2. browser从URL中拽出hostname,,将其送给DNS应用;
  3. DNS 客户端向 DNS 服务器发送包含主机名的查询
  4. DNS 客户端从 DNS 服务器收到包含IP地址的回复
  5. 一旦browser从 DNS 收到IP地址,他就会初始化与HTTP服务进程80号端口的TCP连接。

我们从上面例子中看到,DNS提供IP地址解析的服务为应用添加了额外的时延。但和Proxy Server会缓存HTTP报文一样,结构化的DNS服务器也同样会将DNS的相关数据cache到临近client host的DNS server。
从此我们可以看到,DNS提供的服务不仅仅只是IP地址和hostname之间的转换。还有:

  • Host aliasing:主机别名。一个主机可以有一个规范主机名(Canonical hostname)多个别名(Alias hostname),DNS可以将这些别名映射到同一个IP地址。
  • Mail server aliasing:邮件服务器别名。为方便记忆,e-mail地址也可以拥有别名。如Bob有一个yahoo的邮箱地址是bob@yahoo.com。我们看到的yahoo的邮件服务器主机名是简单的yahoo.com,但它的规范主机名可能会是relay1.west-coast.yahoo.com。显然,yahoo.com更容易记忆。
  • Load distribution:负载分配。对于一些大型公司,它们可能会将Web服务器分布的放在各个地方同时为用户提供服务。这些服务器有不同的IP地址,但使用相同的hostname。DNS数据库中会将这些地址与同一hostname关联起来。DNS会决定使用哪个服务器去响应用户的Web请求。

2.4.2 Overview of How DNS Works

我们已经概况的了解了DNS提供服务的,本节我们来看看DNS所提供的 主机名-IP地址 映射服务。假设某些应用需要用到这样的服务,他会先将需要转换的信息给客户端的DNS,然后DNS完成转换工作。

在很多UNIX-based的机器上,系统会提供gethostbyname()函数封装转换服务。随后用户机上的DNS会接管,向网络送出query message。所有的DNS问询和其收到的回复报文都会被53号端口通过UDP数据报的方式传输。经过一段时间的时延后DNS收到映射后的内容。之后,这段内容会被传给应用。应用所看到的只是一个黑盒子,将主机名(IP地址)丢进去,黑盒会返回应用想要的IP地址(主机名)。但事实上,这”个“黑盒子涵括了分布在全球成千上万的DNS服务器和指定这些服务器通讯的应用层协议

对于DNS,最简单的设计思想可能就是中心化的设计了!即让一个DNS服务器记录所有的主机名-IP地址映射关系。但我们也很容易发现其中的弊端:
- A single point of failure
- Traffic volume
- Distant certralized database
- Maintenance
而DNS数据库设计是分布式的、层次化的。但中心化的单一DNS服务器just doesn't scale

A Disruibuted, Hierarchical Database

DNS是一个浩大的工程,分布式、层次化的DNS服务器遍布世界各地。得益于这种扩展性(scale),没有一个DNS服务器需要记录全部的 主机名-IP地址 映射关系,主机名-IP地址的映射分布交叉地存放在各个DNS服务器中。

我们用下图来近似地描述这种分布式、层次化的关系,我们可以看到三类DNS服务器:root DNS 服务器top-level domain(TLD) DNS服务器authoritative DNS服务器
Pasted image 20240612014451.png

  • 根DNS服务器:世界上有13个IPv4的根服务器,为12个组织所管理,但是为了保证根域名服务器的可用性,会部署多个节点。因此也有说全球根服务器的数量在1000+左右(截至2020)。根域名服务器能够提供 TLD服务器的 IP地址

  • 顶级域服务器:TDL服务器管理并提供特定TLD的DNS服务。这些顶级域名有:comorgnetedugov,还有国家TLD如:ukcnjp等。顶级域名由不同的公司或组织所拥有和管理,如Educause管理edu TLD。TLD服务器提供权威DNS服务器的IP地址。

  • 权威DNS服务器:权威DNS服务器记录存储特定域名的官方DNS,记录主机名和IP地址之间的映射关系。任何提供公开访问主机的组织都需要应用权威DNS服务器。对于这些组织来说,它们可以自己搭建服务器或交给特定的服务提供商。大多数的大学和大型公司都有自己的一级和二级权威DNS服务器(备份)。

从DNS服务器层次结构中我们看到root、TLD还有authoritative DNS服务器。但在DNS服务器层次结构外还有一种十分重要的DNS服务器,叫做local DNS server。尽管严格来说local DNS server并不属于architecture,但却处在architecture的C位。每个ISP都会有这样的local DNS server(也叫default name server)。下面我们演示local DNS server是怎么工作的(假设主机cse.nyu.edu想要获得主机gaia.cs.umass.edu的IP地址):

  1. 主机cse.nyu.edu向local DNS server发送解析gaia.cs.umass.edu的查询报文;(query)
  2. Local DNS 服务器向 Root DNS 服务器转发查询报文;(query)
  3. Root DNS 服务器根据顶级域名edu返回负责该顶级域名TLD服务器的IP地址列;(reply)
  4. Local DNS 服务器向其中一个 TLD DNS 服务器发送请求报文;(query)
  5. TLD DNS 服务器根据umass.edu后缀返回authoritative DNS 服务器的IP地址;(reply)
  6. Local DNS 服务器直接向主机dns.umass.edu发生请求报文;(query)
  7. Authoritative DNS 服务器返回gaia.cs.umass.edu的IP地址;(reply)
  8. Local DNS 服务器将IP地址转发给主机cse.nyu.edu。(reply)
    在整个过程中,为了获得目标之际的IP地址映射,一共发送了8个DNS报文:四个query messages和四个reply messages。之后,我们会了解到DNS caching是怎么优化DNS查询过程的。
    Pasted image 20240618164507.png

之前,我们假设TDL 服务器默认知道目标hostname在哪个authoritative DNS 服务器的情况,但现实中不全是这样的。可能的情况是,TLD服务器只认得一些知道authoritative DNS 服务器hostname的intermediate DNS 服务器在哪。这样,local DNS 服务器就需要额外的DNS通讯才能找到最终的authoritative DNS 服务器。(额外 N个intermediate DNS 服务器产生额外 2N个DNS messages
Recurive queries and iterative queries
The example shown in Figure 2.19 makes use of both recursive queries and iterative queries. The query sent from cse.nyu.edu to dns.nyu.edu is a recursive query, since the query asks dns.nyu.edu to obtain the mapping on its behalf. However, the subsequent three queries are iterative since all of the replies are directly returned to dns.nyu.edu. In theory, any DNS query can be iterative or recursive. For example, Figure 2.20 shows a DNS query chain for which all of the queries are recursive. In practice, the queries typically follow the pattern in Figure 2.19: The query from the requesting host to the local DNS server is recursive, and the remaining queries are iterative

DNS Caching

在上面的例子中,我们看到,请求DNS服务的client向知道目标主机 hostname/IP 映射关系的authoritative server请求DNS服务时,DNS messages的数量会随着intermediate DNS 服务器结点的增加而增加:而使用caching的方式时,当cache命中,DNS消息的数量将锐减到:从中,我们能够看到在服务器结点之间加入一个cache是多么重要。

DNS caching的思想十分简单,和HTTP中的proxy server(Web cache)的思想类似,都是通过保存近期请求的数据来减少未来请求的处理时间和网络流量。这种缓存机制在提高DNS解析速度和减少网络拥塞方面起着至关重要的作用。

在下面的例子中,在Requesting host首次请求查询gaia.cs.umass.edu的 IP 时,local DNS 服务器会将主机gaia.cs.umass.edu的 hostname/IP 映射关系缓存到本地。如果另一台主机也有查询gaia.cs.umass.edu的 IP 的话,local DNS server会直接将缓存在本地的 IP 地址转发给requesting host,即只需要2步。
Pasted image 20240618185617.png

2.4.3 DNS Records and Messages

DNS 服务器共同组成了存放Resource Records(RRs) 的DNS 分布式数据库。每个DNS reply message 都包含着至少一个的资源记录(RRs)

RRs是一个包含以下字段(fields)四元组(four-tuple):这个四元组中,前三个字段我们望文生义,最后一个英文缩写 TTL(Time To Live) 表示这个RR在DNS caching 中剩余的存放时间(当RR长时间没有被访问时,RR就会从DNS caching中被清除出去)。

下面,我们看看 RRs 的其余三个字段的含义。其中,字段Name和字段Value的含义取决于Type字段:

  • 如果Type=A,这时,Name就是一个主机名 并且 Value是主机的 IP 地址。因而,Type A 记录提供 标准的 hostname/IP 映射关系, (relay1.bar.foo.com, 145.37.93.126, A) 就是一个Type A的记录。(当Type=AAAA表示正在查询域名的IPv6地址)
  • 如果Type=NS,这时,Name就是一个域名(domain) 并且 Value是知道如何获得域名内主机 IP 地址的authoritative DNS server的主机名(foo.com, dns.foo.com, NS)就是一个Type NS的记录。这种记录告诉其他DNS服务器如果需要解析foo.com域内的任何主机名,应该查询dns.foo.com这个权威DNS服务器。
  • 如果Type=CNAME,那么 Value 就是一个规范主机名(canonical hostname) ,这时的 Name字段用来表示主机别名 这种记录向querying hosts提供主机的规范主机名。(foo.com, relay1.bar.foo.com, CNAME)就是一种CNAME记录。
  • 如果Type=MX,则 Value表示mail server的规范名Name表示mail server的别名(foo.com, relay1.bar.foo.com, CNAME) 就是一种 CNAME 记录。
    Noter:
    MX records allow the hostnames of mail servers to have simple aliases. By using the MX record, a company can have the same aliased name for its mail server and for one of its other servers (such as its Web server). To obtain the canonical name for the mail server, a DNS client would query for an MX record; to obtain the canonical name for the other server, the DNS client would query for the CNAME record

RRs 不同的Type代表着不同的含义,不同服务器中记录的RRs的种类也不尽相同。在authoritative DNS 服务器中,存储的是Type A的RRs;在其他DNS 服务器如TLD服务器中存放着Type为NS的RRs。

DNS Messages

在之前的Figure 2.19中,我们知道DNS query messages 和 reply DNS messages 是如何传输的。我们看到两种DNS message,但事实上,不论是query messages 还是 reply messages 都使用同一DNS报文格式。DNS报文格式如下:

  • 头12个字节叫DNS报头(header section)。其中又有很多不同作用的字段。
    • 标识字段(16 bits):用于标识一次查询/响应过程,由客户端设置,服务器返回相同的值。
    • 标志字段:包含多个控制标志,如1 bit query/reply flag(query(0)/reply(1))、操作码、是否期望递归查询等。
    • 问题计数:指示查询请求中问题的数量。
    • 回答资源记录数:指示响应中回答的数量。
    • 权威名称服务器计数:指示响应中权威名称服务器记录的数量。
    • 附加资源记录数:指示响应中附加记录的数量。
  • 接下来的DNS报文节叫做DNS question section。它包括:
    (1) 名称字段(Name field),它表示querying hostname。
    (2) 类型字段(Type field),它表示querying name的种类。
  • 然后来自DNS服务器的reply在DNS answer section 中存放。根据报头和question section的内容,answer section可以返回多个不同的RRs。
  • Authority section 包括了其他权威服务器的记录信息。
  • Additional section 中包含着其他有用的信息,比如用于支持额外功能的记录。
    Pasted image 20240619182044.png

举一个DNS querying message的例子:

Header Questions
Transaction ID: 0x9ad0 Name: www.baidu.com
Flags: 0x0100 (Standard query) Type: A (Host Address)
Questions: 1 Class: IN (Internet)
Answer RRs: 0
Authority RRs: 0
Additional RRs: 0

我们现在再看看DNS reply message的例子:

Header Sect. Questions Sect. Answers Sect.
Transaction ID: 0x9ad0 Name: www.baidu.com Name: www.baidu.com
Flags: 0x8180 (Standard query response, No error) Type: A (Host Address) Type: A (Host Address)
Questions: 1 Class: IN (Internet) Class: IN (Internet)
Answer RRs: 1 TTL: 86400
Authority RRs: 0 Data length: 4
Additional RRs: 0 Address: 123.125.114.144

nslookup program

  • 当我们想要请求DNS服务时,我们可以使用nslookup来获取域名和IP地址之间的映射。
Inserting Records into the DNS Database

我们已经了解了如何从DNS distributed database中获取我们想要的信息。但当我们创立一个公司后,我们需要拥有自己的网站来宣传自己的产品。我们应该怎么做呢?(假设我们创立商业公司,也就是TLD为com

  1. 首先,我们要在域名注册商(registrar)那里注册一个自己的域名。
  2. 当域名创建之后,你需要向域名注册商提供你的主要和次要权威DNS服务器的名称和IP地址。
  3. 之后,域名注册商会将一个Type NS 和 一个Type A 的记录将你的DNS服务器的hostname/IP mapping关系加入到TLD com 的服务器中。
  4. 然后还需将你的Web server地址以Type A 的资源记录还有你的mail server地址以Type MX 的资源记录的方式在你的权威DNS服务器中建立 hostname/IP mappings。

2.5 Peer-to-Peer File Distribution

在之前的章节中,我们已经学习过的应用层应用有:Web应用、e-mail和DNS,他们都是基于C/S 体系结构。这些应用对于一个时刻响应的服务器的需求是时时刻刻都存在的。但Peer-to-peer网络体系结构对这样的服务器的依赖是很少的,由于每个终端设备(也叫节点)都可以处理请求且资源分散在各个节点上的,因此每个节点既可以作为客户端,也可以做服务器。

在本节中,我们将了解当今最流行的P2P file distribution protocol——BitTorrent。我们之前已经了解过,在C/S网络体系结构中,资源只保存在服务器端,资源的传输方向只能是 server->client。这样,服务器频繁的传输资源会对服务器带来严重的负担。而在P2P网络体系结构中,资源可以分布地存储在各个peer中,每个peer都可以是资源的请求者,也可以是资源的提供者

2.5.1 Scalability of P2P Architecture

为了展现P2P体系结构在传输文件上的优越性,我们用下图的例子来演示并计算每种architecture的分发时间。

我们假设服务器接入链路(access link)的上传速率为 ,第 个对等体接入链路的上传速率为 且第 个对等体接入链路的下载速率为 。我们在假设分布在服务器的文件大小是 分发时间(distribution time) 是将文件全部传给 个对等体的时间。
Pasted image 20240620183658.png

Distribution Time for Client/Server Architecture

假设采用C/S体系结构的分发时间是 。由于在C/S体系结构下,服务器需要将 File 向每个peer都发送一遍,因此服务器需要传输 bits。而我们知道服务器的上传速率是 ,因而,在理想情况下的分发时间为: 假设对等体接入链路中最慢的下载速率是 ,同时考虑到着两种情况,那么我们会得到分发时间:若只考虑服务器的上传和对等体的下载,那么分发时间:在这个等式中我们发现当时间成为瓶颈时,分发时间会随着 的增加而增加。这还是不考虑网络带宽的情况下。

Distribution Time for P2P Architecture

当网络采用P2P体系结构时,情况则大有不同!因为每个对等体peer都可以辅助服务器分发文件,本着人多力量大的原则,随着对等体越来越多,分发时间不会像C/S网络那样线性增加。
Pasted image 20240620192758.png
下面,我们用计算来进一步观察在P2P网络下的分发时间的变化。

  • 在向对等体发送文件之前,我们要先明确最开始的时候只有服务器拥有文件的信息。因此,服务器至少要向接入网络中传输一次。这次分发时间至少需要(与C/S模式不同,P2P网络的服务器在这一次的传输后就可以撒手人寰了。之后peers会将文件分布在各个peer处。)
  • 和C/S体系结构一样,下载速率最慢的对等体接收到 bits 也需要不少于 的时间。
  • 最后,我们看到系统总的上传速率等他所有对等体上传速率相加,即 。然后系统将 bits的文件分发给 个对等体。这时需要最少

将这三部分合在一块,我们就会得到P2P的分发时间:从上面的等式我们看出,P2P结构降低了最小分发时间的负担。若不考虑其他因素,P2P系统的最小分发时间为:

BitTorrent

BitTorrent 是一个流行的 P2P(点对点)文件分发协议。在 BitTorrent 的术语中,参与特定文件分发的所有节点(peers)的集合被称为一个 “torrent”。在 torrent 中的节点互相下载文件的等大小块(chunks),典型的块大小是 256 KB。当一个节点首次加入 torrent 时,它没有任何块。随着时间的推移,它会逐渐积累越来越多的块。在下载块的同时,它也会上传块给其他节点。一旦一个节点获得了整个文件,它可以选择离开 torrent,或者留在 torrent 中继续向其他节点上传块。此外,任何节点都可以在只有部分块的情况下随时离开 torrent,并且之后可以重新加入 torrent。
Pasted image 20240620202147.png
BitTorrent 是一个复杂的系统,但我们可以通过其最重要的机制来理解它的基本原理。每个 torrent 都有一个叫做 tracker 的基础设施节点。当一个 peer 加入 torrent 时,它会在 tracker 中注册,并定期通知 tracker 它仍然在 torrent 中。这样,tracker 就知道 peers 的数量。

当一个新的 peer,比如 Alice,加入 torrent 时,tracker 会从参与的 peers 中随机选择一部分(比如说50个)并将这些 peers 的 IP 地址发送给 Alice。Alice 拥有了这个 peers 列表后,会尝试与列表上的所有 peers 建立并发的 TCP 连接。我们称 Alice 成功建立 TCP 连接的 peers 为 “neighboring peers”。随着时间的推移,一些 peers 可能会离开,而其他的 peers(不在最初的50个之内)可能会尝试与 Alice 建立 TCP 连接。因此,一个 peer 的neighboring peers 的数量会随时间波动。

在任何给定的时间点,每个 peer 都会拥有文件的一部分 chunks,不同的 peers 拥有不同的 chunks 子集。Alice 会定期向她的neighboring peers 请求他们拥有的 chunks 列表。如果 Alice 有 L 个不同的neighbor,她将获得 L 个 chunks 列表。知道这些后,Alice 可以请求她当前没有的 chunks。

因此,在任何时候,Alice 都会拥有一部分 chunks,而且知道她的邻居拥有哪些 chunks。有了这些信息,Alice 需要做出两个重要的决定:首先,她应该首先向邻居请求哪些 chunks? 其次,她应该向哪些邻居发送请求的 chunks?在决定请求哪些 chunks 时,Alice 使用了一种叫做 “rarest first” 的技术。Rearest first 用来确定在她没有的 chunks 中,哪些 chunks 是在她的邻居中重复次数最少的 ,然后最先请求这些最稀有的 chunks。这样,最稀有的 chunks 能更快地被重新分配,目的是(大致)使 torrent 中每个 chunks 的副本数量均衡。

为了确定她应该回应哪些请求,BitTorrent 使用了一个巧妙的“交易”算法。基本思想是 Alice 优先回应那些当前以最高速率向她提供数据的邻居。具体来说,对于她的每个邻居,Alice 持续测量她接收比特的速率,并确定四个以最高速率向她提供比特的 peers。然后她通过向这四个相同的 peers 发送 chunks 来回报他们。每10秒,她会重新计算速率,并可能修改这四个 peers 的集合。在 BitTorrent 术语中,这四个 peers 被称为 unchoked peer。重要的是,每30秒,她还会随机选择一个额外的邻居并向其发送 chunks。我们称这个随机选择的 peer 为 Bob。这时的 Bob 就是 optimistically unchoked peer。因为 Alice 向 Bob 发送数据,她可能会成为 Bob 的前四个上传者之一,这样 Bob 就会开始向 Alice 发送数据。如果 Bob 向 Alice 发送数据的速率足够高,Bob 可能会反过来成为 Alice 的前四个上传者之一。换句话说,每30秒,Alice 会随机选择一个新的交易伙伴并开始与该伙伴交易。如果两个 peers 对交易感到满意,他们会将对方放入他们的前四个列表中,并继续彼此交易,直到其中一个 peer 找到更好的伙伴。这样,能够以兼容速率上传的 peers 往往能够找到彼此。随机邻居选择也允许新的 peers 获得 chunks,这样他们就有东西可以交易。除了这五个 peers(四个“顶级” peers 和一个探测 peer)之外的所有其他邻居 peers 都被 “choked”,即他们不会从 Alice 那里接收任何 chunks。BitTorrent 还有许多其他有趣的机制,这里没有讨论,包括 pieces(小 chunks)、pipelining、random first selection、endgame mode 和 anti-snubbing。

刚才描述的交易激励机制通常被称为 tit-for-tat。尽管已经有研究表明这种激励方案可以被规避,但 BitTorrent 生态系统仍然非常成功,有数百万个同时在线的 peers 在数十万个 torrents 中积极分享文件。如果 BitTorrent 没有设计 tit-for-tat(或其变体),即使其他方面完全相同,BitTorrent 都可能不会存在,因为大多数用户只想作为数据的接收者。

最后,我们简要提及 P2P 的另一个应用,即分布式哈希表(DHT)。分布式哈希表是一个简单的数据库,数据库记录分布在 P2P 系统的 peers 中。DHT 已被广泛实施(例如,在 BitTorrent 中)并且是广泛研究的主题。在配套网站的视频笔记中提供了一个概述。

2.6 Viedo Streaming and Content Distribution Networks

By many estimates, streaming video account for about 80% of Internet traffic in 2020. [Cisco 2020]

本节课中,我们会简单了解当今网络世界中的视频流服务是怎么实现的。我们会看看这些 streaming video 是如何用应用层协议和一些 cache-like 的服务实现的。

2.6.1 Internet Video

在流存储视频应用(streaming stored video applications)中,underlying medium 就是提前录制好的视频。这些视频会被放置在服务器中,如何client向服务器发送需要视频的请求报文。

那么什么是 video medium 呢?我们要先明白视频就是一连串的图片,我们常常听到24帧、30帧60帧的视频。这表示视频每秒钟会显示60帧(张)的图片。而这些图片又是由一连串的像素构成的,这些像素由可以用表示亮度和颜色的 bits 来表示。这是压缩(compression)的过程,也是视频的特点之一 —— 将视频文件压缩成任何所需的比特率(bit rate)。比特率越高视频的质量越高。

从网络的视角来看,video 最重要的特征可能就是比特率了。一般来说,压缩后网络视频的比特率通常在100Kbps 到 4Mbps。如果想要传输4K以上的视频,那么至少需要10Mbps的比特率,这样会占用大量的网络和存储资源。为保证视频持续稳定的传输,网络就必须提供比压缩视频比特率还大的网络吞吐量(throughput)。

如果端到端的吞吐量小于压缩视频的比特率,我们还可以将源视频压缩成不同比特率的版本,中断设备根据当前网络吞吐量来选择多大比特率的版本。这也就是为什么视频网站上会存在144p、480p、1080p、2k、4k多个版本的视频供用户选择。

2.6.2 HTTP Streaming and DASH

当视频采用HTTP媒体流(HTTP streaming)传输时,视频会存放在HTTP服务器的一个URL处。当用户想要观看视频时,client会建立与server的TCP连接并通过HTTP GET 方式来获取这个视频。之后,server会将视频文件包含在一个响应报文中发给client。在client端,压缩后的视频(bits)会先存放在client application buffer中。一旦buffer中的字节数量到预设阈值的时候,client端的应用就会开始播放视频。

尽管HTTP streaming 得到极为广泛地应用,但它仍然有一个缺点:即所有的client都会收到同样比特率的视频。这样的特点没有充分考虑到不同client目前可用带宽的大小。因此也促成 DASH(Dynamic Adaptive Streaming HTTP)技术 的发展。动态自适应流媒体技术将视频编码成多个比特率的版本,client会动态地请求一段长度的视频片段。根据目前的带宽选择响应比特率版本的视频。

DASH技术的出现使不同网络状况的clients能够在不同编码率下享受流媒体视频中的内容。同时DASH也支持在会话中根据网络状况动态地选择不同比特率版本。

有了DASH,每种不同比特率版本的视频都将存放在HTTP的服务器中,每种视频都与单一的URL相对应。而这些URL信息可以在HTTP服务器中的 清单文件(manifest file) 中看到。

我们最后再来在宏观上看看video是如何在server-client上传输的:

  1. 客户端初始化与服务器的TCP连接;
  2. TCP连接建立完成,客户端使用HTTP GET 方法请求想要的video文件的一个chunk;
  3. 服务器将清单文件(manifest file)发送给客户端;
  4. 客户端根据目前的网络状况在清单文件中选择合适的video chunk;
  5. 服务器端压缩video chunk成bit stream并发送给客户端;
  6. 客户端解码bit stream成video chunk并隔一段时间跳转到第3步。

2.6.3 Content Distribution Networks

当今,许多流媒体视频公司需要将这些视频内容分布式地放在世界各地来时时刻刻为百万计的用户提供稳定的视频播放。

那么如何为这些用户提供稳定的视频播放呢?我们可以建造一个巨型的数据中心(mega data center),将所有的视频都存放在数据中心里。但这样做会造成很多问题:

  1. 当client物理距离过大时,传播时延(Propagation Delay)会变得很大,在这种情况下,视频可能会经由多个ISP才能送到client端。如果在传输过程中有一条线路的吞吐率小于视频的比特率,回想我们第一章中了解的bottleneck link,这时的线路最大吞吐率将由这条bottleneck link决定!
  2. 服务器会重复发送更加流行的视频,这样会浪费网络带宽。
  3. 由于单一数据中心并没有备用服务器作为备份,如果这个数据中心瘫痪或和其连接的线路故障,那么data center就不会往外发送任何视频流了。

为应对百万计clients的视频流请求,现在大部分公司都会采用Content Distribution Networks(CDNs)内容分发网络通过管理分布在不同位置的服务器,将video、其他Web内容拷贝到不同的服务器(服务器农场)中。每当有用户发起内容请求,CDN就会让用户与最合适的data center进行通信,以获得最好的体验。CDN可以是私有(Private CDN)的,只为内容提供商所有。CDN也可以是第三方的(third-party CDN),由多个内容提供商分发内容。
A very readable overview of modern CDNs is [Leighton 2009; Nygren 2010].

CDNs的分布有两种不同的设计哲学:

  • Enter Deep*:这种策略由Akamai首创,它涉及将服务器集群部署在全球各地互联网服务提供商的接入网络中,深入到用户所在的位置。(To get close to users)
  • Bring Home/Edge-based Plasement*:在这种方法中,CDN服务器被放置在网络的边缘,更靠近最终用户。这样做可以减少内容传输的延迟,加快内容交付速度。(To bring the ISPs home)

我们做假设来理解这两种策略:假设圆的内部是network core,圆上是network edge。那么在enter deep方式中,CDN靠近一个圆的圆心,它平等的到每个client都近。而bring home更像是将CDN放在圆上,这样对一部分用户更近,但是对另外一些用户来说却更远。

CDN Operation

了解完CDNs的两种分布策略之后,我们现在看看CDN是任何发挥作用的。当client host想要获得一个video时,CDN必须拦截(intercept)该请求并根据请求(1)决定此时刻最适合该client的CDN服务器集群;(2)将client的请求引导发送到那个集群中的服务器。

下面,我们用一张图直观地理解CDN的作用。我们假设有一个内容提供商(NetCinema)从 KingCDN 租用了一个第三方的CDN来完成其视频分发。在 NetCinema 网页中,每个视频都对应一个URL与其对应。在用户想要观看某个视频且视频流被送到用户的host上前,发生了以下事件:

  1. 用户访问 NetCinema 网页;
  2. 在用户点击链接 \http://video.netcinema.com/XXXXX\ 后,用户host发送关于 video.netcinema.com DNS query 报文;
  3. 用户的 Local DNS Server(LDNS)向 NetCinema 的权威DNS服务器发送 DNS query 报文。由于 NetCinema 的视频资源都由第三方 KingCDN 管理,所以 NetCinema 的权威DNS服务器会将 KingCND的域名,如 xxxxx.kingcdn.com 交给 LDNS。
  4. 知道了KingCND的域名,现在我们的发出的 DNS query 就可以进到 KingCDN 的私人 DNS infrastructure中了。然后,用户的 LDNS 发送关于 xxxxx.kingcdn.com的 DNS query 之后 LDNS 收到 KingCDN 的内容服务器的IP地址。
  5. 有了 KingCDN 内容服务器的IP地址,LDNS 将IP地址转发给client host。
  6. Client host通过IP地址与目标服务器建立TCP连接,之后使用HTTP GET来获得想要的内容。如果使用 DASH ,服务器还会先将包含一连串URLs的清单文件发送给 client host ,然后 client host 通过目前的网络状态动态地选择需要的视频chunk文件。
    Pasted image 20240622153238.png
Cluster Selection Strategies

在CND分发内容的时候,往往要考虑怎么选择合适的 cluster selection strategy。集群选择策略是在client host 进行查询时,CDN通过 LDNS 的IP地址来为其推荐最合适的服务器集群(cluster)。

CDN往往采用专有的集群选择策略。一种笨想的策略就是 geographically closest,就是根据物理距离的长短让距离 client host 最近的服务器集群(也就是数据中心)负责向 client host 传输内容信息。Content provider 会使用商业性的 geo-location databases 来获取地理位置上的信息。这种方式对与大多数用户而言都是相对较优的,但对 “Geographically close, driven far” 的情况来说,这种策略的体验可能会很差。因为地理距离上的cluster可能在跳数(number of hops)上可能不是最小的。

Tip*在下图的例子就很好地说明了这个问题,虽然两地相距120+km,但是开车确需要1300+km。
Pasted image 20240622164243.png

为了解决这种问题,基于client host当前的网络状况来选择最佳的集群,CDNs可以使用 real-time measurements 策略周期性地检查与client host之间的延时(如使用ping命令)等来选择cluster。

2.6.4 Case Studies: Netflix and YouTube

2.6.4小节作为了解性内容。

2.7 Socket Programming

回顾2.1节,我们讨论了由两部分组成的网络应用——客户端程序和服务器程序——运行在两台end systems上。当程序一旦被运行,客户端进程和服务器进程就会被创建,然后这些进程靠sockets进行客户端-服务器之间的读、写操作。要进行这样的客户机-服务器间的通信,程序员就要编写客户端程序和服务器端程序。

我们可以使用现成的[RFC]文档或其他开源的标准文档提供的协议标准来完成我们的网络应用程序(如使用HTTP[RFC 2616]来作为服务器和客户端程序的指导文档)。依这种方式,客户端程序和服务器程序的读、写操作的实现就需要安装文档规定的方式实现。
之外,我们还可以使用自己规定的读写接口协议来实现我们的客户端程序和服务器程序。这种方式下实现的网络应用往往是闭源私有的。

我们再回顾TCP和UDP的特点。我们知道,TCP是面向连接(connection oriented) 的网络层协议,为两台终端设备之间提供可靠的字节流信道。
而UDP是无连接的,它提供不可靠的、以独立传输包(independent packets)方式传输的,对传输过程中发生的意外不做任何保证。

2.7.1 Socket Programming with UDP

我们将主机比做街道,应用进程比作房子,进程的端口号/sockets用房子的门牌号来表示,模拟数据包传输为快递运送的过程。
我们来模拟一次数据包传输的过程:现在有一个快递包需要从房子A邮递到房屋B,我们需要在快递包上写很多邮寄信息,如送到哪个街道(主机)的哪个房子(进程)门牌号(端口号)是多少。快递员会根据这些信息将快递包送到指定的房子。

作为生活在房子里面的程序员,我们完全有随意布置房间和发送什么快递包的权力。但是我们不能影响到房间外快递员是如何配送包裹的。这也就是我们所说的开发者只对socket接口的application-layer side有完全的掌握,但对传输层一侧几乎没有控制。

如果还是不理解,不妨从网络视角再看看数据包传输的问题。在网络中,路由器会解析IP地址并将数据转发到正确的主机上,而主机上有许多进程,一个进程又可以有多个sockets,因此我们需要端口号来对这些socket进行标识。因而,有了IP地址和端口号,我们就可以将数据包送去目标主机了。

我们用下面的客户机-服务器应用的规则来演示一下UDP和TCP上的socket编程:

  • client从键盘读入一行数据(characters)并将其转发给server;
  • server收到数据并将其转化成大写形式;
  • server将修改后的数据发给client;
  • client收到修改后的数据并将其显示到屏幕上。

我们在下图中用高亮表示客户端-服务器基于UDP传输服务上的相关socket活动
Pasted image 20240704180831.png

Client UDP Programming

原书用 python 进行应用端对传输层协议的选择配置编程。相比 C++更容易理解和学习。

#include <iostream> // 引入输入输出流库,用于控制台输入输出。
#include <cstring> // 引入字符串处理库,提供字符串操作函数。
#include <sys/socket.h> // 引入套接字接口库,用于网络通信。
#include <arpa/inet.h> // 引入用于IP地址转换的库。
#include <unistd.h> // 引入POSIX操作系统API库。

#define PORT 8080 // 定义服务器监听的端口号为8080。
#define BUFFER_SIZE 1024 // 定义缓冲区大小为1024字节。

int main() {
    int sockfd; // 套接字文件描述符,用于标识创建的套接字。
    char buffer[BUFFER_SIZE]; // 缓冲区数组,用于存储输入的消息和服务器的响应。
    struct sockaddr_in server_addr; // 服务器地址结构体,用于存储服务器的地址信息。

    // 创建UDP socket
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed"); // 如果创建套接字失败,打印错误信息。
        exit(EXIT_FAILURE); // 退出程序。
    }

    // 配置服务器地址
    memset(&server_addr, 0, sizeof(server_addr)); // 初始化服务器地址结构体。
    server_addr.sin_family = AF_INET; // 设置地址族为IPv4。
    server_addr.sin_port = htons(PORT); // 设置端口号,htons用于将主机字节序转换为网络字节序。
    server_addr.sin_addr.s_addr = INADDR_ANY; // 设置IP地址为任意地址,即本机的任意IP。

    while (true) { // 无限循环,直到程序被手动停止。
        std::cout << "Enter a message: "; // 提示用户输入消息。
        std::cin.getline(buffer, BUFFER_SIZE); // 从标准输入读取一行数据到缓冲区。
        std::cout << "Message has been loaded! "<< std::endl;
        // 发送数据到服务器
        sendto(sockfd, buffer, strlen(buffer), 0, (const struct sockaddr *)&server_addr, sizeof(server_addr));
        std::cout << "Message has been sent! "<<std::endl;
        // 接收服务器的响应
        int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, nullptr, nullptr); // 接收服务器响应的数据。
        buffer[n] = '\0'; // 在消息末尾添加字符串结束符。
        std::cout << "Server response: " << buffer << std::endl; // 打印服务器的响应。
    }

    close(sockfd); // 关闭套接字。
    return 0; // 程序正常退出。
}

Server UDP Programming
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <algorithm>

#define PORT 8080
#define BUFFER_SIZE 1024

void to_uppercase(char* str) {
    std::transform(str, str + strlen(str), str, ::toupper);
}

int main() {
    int sockfd;
    char buffer[BUFFER_SIZE];
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);

    // 创建UDP socket
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(PORT);

    // 绑定socket到地址
    if (bind(sockfd, (const struct sockaddr *)&
	    server_addr, sizeof(server_addr)) < 0) {
	    
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    while (true) {
        memset(buffer, 0, BUFFER_SIZE);
        // 接收数据
        int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
        buffer[n] = '\0';
        std::cout << "Received: " << buffer << std::endl;

        // 转换为大写
        to_uppercase(buffer);

        // 发送修改后的数据
        sendto(sockfd, buffer, strlen(buffer), 0, (const struct sockaddr *)&client_addr, addr_len);
        std::cout << "Sent: " << buffer << std::endl;
    }

    close(sockfd);
    return 0;
}

2.7.2 Socket Programming with TCP

与UDP提供的无连接服务不同,TCP提供有连接的服务。所以,在客户端和服务器端进程相互传递信息前,我们需要先通过握手(handshakes)建立TCP连接。然后用客户端connection socket和服务器connection socket建立起TCP连接。因为是面向连接的,因此在一端想向另一端发送信息时,只需要将信息送给connection socket,而不需要向UDP那样在数据包中额外加入IP地址和端口号等信息。

我们还要明确,在客户进程初始化TCP连接前需要:

  1. 服务器端的进程必须先运行起来;(UDP同)
  2. 服务器进程还要提供一个额外的socket用来进行连接的确定。
    简单理一下过程:(1)客户进程和服务进程先创建套接字 socket;(2)客户进程用三次握手(three-way handshake)初始化TCP连接,这一步是在运输层发生的 ,对应用进程不可见。

在服务进程上,它会一开始一直监听是否有客户进程与它建立TCP连接,一旦连接建立,服务器进程就会创建一个属于当前客户进程的connection socket。如下图所示,连接建立后,客户进程和服务器进程就会通过一个管道(pipe)来传输信息。TCP保证服务器进程/客户进程按序收到客户进程/服务器进程发送的全部字节,所以我们说TCP在客户进程和服务器进程间提供可靠的服务。
Pasted image 20240705114457.png

Client TCP Programming
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    std::string message;

    // 创建socket文件描述符
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cerr << "Socket creation error\n";
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 将地址转换为二进制形式
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/ Address not supported\n";
        return -1;
    }

    // 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection Failed\n";
        return -1;
    }

    // 从键盘读取数据并发送到服务器
    std::cout << "Enter message: ";
    std::getline(std::cin, message);
    send(sock, message.c_str(), message.length(), 0);

    // 接收服务器返回的数据并显示
    read(sock, buffer, BUFFER_SIZE);
    std::cout << "Modified message from server: " << buffer << std::endl;

    close(sock);
    return 0;
}

Server TCP Programming
#include <iostream>
#include <cstring>
#include <cctype>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define PORT 8080
#define BUFFER_SIZE 1024

void to_uppercase(char* str) {
    for (int i = 0; str[i]; i++) {
        str[i] = toupper(str[i]);
    }
}

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 创建socket文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    // 绑定端口
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    // 接受客户端连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    // 读取数据并转换为大写
    read(new_socket, buffer, BUFFER_SIZE);
    to_uppercase(buffer);
    send(new_socket, buffer, strlen(buffer), 0);
    std::cout << "Modified message sent back to client\n";

    close(new_socket);
    close(server_fd);
    return 0;
}

Pasted image 20240705115300.png