第二章 应用层
网络应用是计算机网络存在的理由。就像我们出门有网络结果没带手机一样,肯定是折磨的。 这一节会把应用层的一些常用协议细细的切成臊子,讲解它们的原理和运用。
2.1 网络应用原理
试想你是一个成功的哈工大人,成功就业或者深造。为了完成老板的任务,你现在要写一个网络应用。
现在是你写程序的时候了,你发现你只要用某种高级编成语言写一个软件并调用其他层提供的服务就行,显然,分层发力了。所以,我们现在要了解到的是哪些呢?
2.1.1 网络应用体系结构
应用体系结构规定了如何在多个端系统上组织该应用程序。说人话,就是多个端系统上的应用程序怎么合作的。
目前流行的有两种
- 客户-服务端体系结构
- 实际上,目前大部分应用程序都是这么组织的。在这个结构中,有一个必须总是打开的端系统————服务器,它接受其他被称为客户的主机的请求并进行设计好的响应。
在这种体系结构下,客户不直接通信,而是经服务器过手。
另外,服务器一般有固定的地址,被叫做IP地址
Web,FTP,Telent和电子邮件都是基于客户-服务器结构体系
- 实际上,目前大部分应用程序都是这么组织的。在这个结构中,有一个必须总是打开的端系统————服务器,它接受其他被称为客户的主机的请求并进行设计好的响应。
- P2P体系结构
- 而P2P体系结构就截然不同了,在它的体系里只有对等方,每个主机既是服务器也是客户(很显然P2P没有或几乎不依赖服务器)
这种体系结构提供了很有魅力的特性————自拓展性,简单来说,对等越多,客户越多,但是服务器也越多,所以提供服务的能力也会加强。
- 而P2P体系结构就截然不同了,在它的体系里只有对等方,每个主机既是服务器也是客户(很显然P2P没有或几乎不依赖服务器)
2.1.2 进程通信
用操作系统的说法,进行通信的其实是进程,简单来说就是运行在端系统的程序。那我们的网络应用肯定是要在不同的端系统上面通信。
不同端系统上面的进程通过交换报文进行交互,一方面,发送进程生成并发送报文,另一方面,接收进程接收报文并可能通过报文回应。
首先,我们可以发现,无论什么体系结构,都有发起通信的客户角色和等待联系的服务器。网络应用程序是成对存在的进程组成的。
然后呢,我们的报文需要进程通过套接字的软件接口发送和接收。我们可以想象进程是一个房子,那么套接字就是门,报文进出必须经过它。
套接字作为应用程序和网络之间的应用程序接口(API),你可以通过编程控制它应用层的一切,但在运输层,你最多选择运输层协议和设定一些参数(最大缓存之类的)
接下来,报文被通过套接字丢出门了,我们需要明确它被运到哪里。显然,每个主机刚好有自己的IP地址,好巧不巧,我们的主机又有不同的端口号来放不同的套接字,接受不同网络服务的报文。
2.1.3 可供应用程序使用的运输服务
一个运输层协议一般从四个方面被要求
- 可靠数据传输: 能保证报文无差错地到达其他端系统说是(容忍丢失的应用不需要它)
- 吞吐量: 交付比特的速率(带宽敏感的应用要求一定的带宽才能跑,而弹性应用不需要)
- 定时:确保延迟在一定范围内(比如网游和网络电话)
- 安全性:数据不被偷
2.1.4 因特网提供的运输服务
一般来说两种TCP服务和UDP服务
TCP服务提供一些优势
- 面向连接的服务:在报文发送前会先建立TCP连接
- 可靠的数据传输服务:它提供可靠数据传输(废话)
- 拥塞控制服务:当网络拥塞时,抑制发送进程,同时控制TCP连接公平共享带宽
- 运输层安全(TLS):可以给TCP加上TLS,使发送的报文被加密,受到的报文被解密
UDP就不一样,它不提供不必要的服务,只是一味地发送报文
我们可以发现,两种服务加在一起只涉及了可靠数据运输和安全性,那我们的定时和吞吐量呢?实际上,并不提供,因为因特网证明了目前影响不大。
2.1.5 应用层协议
应用层协议规定了不同进程怎么互相传递报文
- 交换的报文类型
- 各种报文类型的语法
- 字段的语义
- 发送和响应报文的规则
应用层协议是应用程序的重要部分。
2.1.6 本书涉及的网络应用
接下来,我们会介绍5种重要应用
- Web
- 电子邮件
- 目录服务
- 流式视频
- P2P
2.2 Web和HTTP
你知道的,现象的我们每个人都已经离不开浏览器启动了(这里指浏览Web页面),它的按需操作为它打下了一片江山。
2.2.1 HTTP概述
Web的应用层协议是超文本传输协议(HTTP)
让我们先过一些术语:
- Web页面:我们看到的网页
- 对象:页面的组成
- HTML基本文件:页面的底子,规定了其他对象怎么用
- URL:对象在的主机名+路径
- Web浏览器:实现HTTP的客户端
- Web服务器:实现HTTP的服务器端
HTTP定义了Web客户向Web服务器请求Web页面的方式以及服务器向客户传输页面的方式
简单描述进入一个链接的过程
- HTTP客户向服务器发起TCP连接
- 连接建立后,HTTP报文借助TCP连接运输
目前HTTP有HTTP/1.0(初始版本)HTTP/1.1(最常用)HTTP/2(全新版本) HTTP是一个无状态协议,不会记录客户的信息,那么我们为什么有些网页可以保留我们的登陆信息之类的呢?(以后说)
2.2.2 非持续连接和持续连接
前者每个对象开一个TCP连接,后者只用一个TCP连接
采用非持续连接的HTTP实际上就是HTTP/1.0用的,我们知道,进入一个链接要先建立TCP连接,再发送请求报文,服务器再发送所需文件。 我们将除了服务器发送文件的其他步骤叫做三次握手
- 客户发送TCP报文段请求连接
- 服务器对客户发送报文段进行确认和回应
- 客户对服务器进行确认的确认并顺便发送HTTP请求报文
我们用**往返时间(RTT)**定义一次客户和服务器互动用的时间,所以三次握手+服务器响应HTTP请求报文一共是2个RTT
我们可以发现,非持续连接需要不停建立TCP连接,而这不仅使每个对象需要两倍RTT的交付时延,而且会对服务器造成负担(服务器需要給大量的客户端建立大量的TCP连接)
而我们HTTP/1.1就运用了持续连接的操作。它在建立一个TCP连接后不断复用,其他对象甚至其他页面继续使用这个TCP连接,一段时间不使用后断开。
2.2.3 HTTP报文形式
下面看一段HTTP响应报文
GET /somedir/page.html HTTP/1.1
Host: www.someschool.edu
Connection: close
User-agent: Mozilla/5.0
Accept-language: zh
报文的第一段是请求行,包含三个字段
- 方法字段:有GET POST HEAD 等,表示对URL指向的服务器对象的操作
- GET 获取对象
- POST 发送字段
- HEAD 要求服务器只用HTTP报文响应
- PUT 上传文件
- DELETE 删除文将
- URL字段
- HTTP版本字段
接下来是首部行
- Host指明了对象所在的主机
- Connection指明了请求完对象关闭TCP连接
- User-agent指明了用户代理(用的什么浏览器)
- Accept-language指明了用户需要什么语言的页面(没有就用默认)
- 实际上还有很多其他内容
接下来是空行和实体体 实体体里面的内容通常是客户输入的内容,比如向搜索框里面打的字
接下来看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)
第一行是初始状态行,它包含了
- 协议版本字段
- 状态码
- 相应状态信息,常见的有
- 200 OK:请求成功
- 301 Moved Permanently:对象被永久转移了
- 400 Bad Request:无法理解请求
- 404 Not Found:服务器没有该HTML
- 505 HTTP Version Not Supported: 不支持对应的HTTP版本
接下来看首部行
- Connection指明了请求完对象关闭TCP连接
- Date表示发送报文的时间
- Server表明了服务器的类型
- Last-Modified表明对象最后修改的时间
- Content-Length表明对象的字节数
- Content-Type表明对象的类型
2.2.4 用户和服务器的交互:cookie
欸,前面说过,服务器是无状态的,那么我们用户是怎么被跟踪并提供个性化服务的呢?
想必大家都在进入一些网站时被要求“接受饼干”
这个cookie有四部分
- HTTP响应报文的cookie首部行
- HTTP请求报文的cookie首部行
- 客户端浏览器管理的cookie文件
- Web站点的数据库
cookie的工作原理大概这样:
- kajimi第一次进入xxx.com,xxx.com的服务器在响应报文中加入
Set-Cookie: 识别码首部行 - kajimi的浏览器看到Set-Cookie,在cookie文件里加入一行,包括主机名和识别码
- 接下来在访问对应主机时,浏览器都会把cookie文件里面的内容提取并在HTTP请求报文里面加入
Cookie: 识别码 - 服务器拿到识别码,从数据库里面拿到kajimi的信息,进行个性化服务
2.2.5 Web缓存
Web缓存器也叫代理服务器,它可以保存用户打开过的界面,改善应用程序的性能。 简单描述它的用法
- 浏览器建立到Web缓存器的TCP连接,发送HTTP请求报文
- Web缓存器检查有无对象副本,有则返回
- 如果没有,Web缓存器建立和对应服务器的TCP连接,发送HTTP请求报文
- 拿到对象后,生成副本并向用户返回对象
可以注意到,Web缓存器,一方面提高了用户访问页面的速度,一方面降低了服务器的压力。
通过内容分发网络(CDN)(在地理上搭建Web缓存器),使大量流量被分散到各个地区,从而加强了访问网站的响应速度,降低了初始服务器的压力。
有人不经发问了,那要是Web缓存器的对象是旧的怎么办?
有的兄弟有的,我们有条件GET,在请求报文里面加入If-modified-since: 日期 在Web缓存器被请求时,它会向初始服务器查询从该日期起,对象是否有改变,因为只是小的报文,该查询在没有改动的情况下几乎没有明显时延。
2.2.6 HTTP/2
回想HTTP/1.1,它的持续连接很强,但是可能出现**队首阻塞(HOL阻塞)**的情况(比如一个对象特别大,把页面其他元素的家在卡住了)。
HTTP/1.1会打开新的TCP连接来应对这一情况,但这又加大了对服务器的压力。
为了解决对应的问题,HTTP/2使用了
- HTTP/2成帧:把报文切割成小的帧,利用帧交错技术,把不同对象的帧交错发送,在达到目的地后再组装,使小对象不被大对象卡。
- 响应报文的优先次序和服务器推:一方面可以为响应分配优先级,另一方面可以提前解析HTML里面需要的对象,提前一起发送。
其实还有HTTP/3处于开发阶段,基于基于UDP的QUIC,但又有HTTP/2的能力
2.3 因特网中的电子邮件
在我们使用QQ,微信前,有一种应用程序已经存在并流行于因特网,那就是我们的电子邮件。 电子邮件有三部分,用户代理、邮件服务器、简单邮件传输协议(SMTP)
2.3.1 SMTP
SMTP是互联网电子邮件的核心,我们来描述电子邮件发送的步骤
- kajimi打开邮件代理程序,提供了mijica的邮件地址,发送报文
- kajimi的用户代理把报文发到它的邮件服务器,放入报文队列
- SMTP客户发现了邮件,建立和mijica的邮件服务器的SMTP服务器的TCP连接
- SMTP握手后,发送报文,报文被放入mijica的邮箱
- mijica闲着没事打开用户代理,发现并接受报文
在SMTP握手的过程中,发件人和收件人的信息被传递,接着报文内容被传递,我们可以看看例子:(S表示服务器,C表示客户)
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: PCRT TO: <bob@hamburger.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 Genshin Impact?
C: How about Honkai:Star Rail?
C: . //结束
S: 250 Message accepted for delivery //确认收到
C: QUIT //结束连接
S: 221 hamburger.edu closing connection //确认收到
客户发送大写的SMTP命令,而服务器用回答码和可选的英文解释。
2.3.2 邮件报文格式
而我们的邮件显然必须有一些信息给SMTP客户与服务器进行握手,比如:
From: alice@crepes.fr //寄件人
TO: bob@hamburger.edu //收件人
Subject: Searching for hobby. //主一题
现在我们考虑用户代理和服务器之间的通道,在用户代理到服务器时,运用了SMTP或HTTP协议,有人不禁发问,为什么要先到自己的服务器,而不是直接给收件地址的服务器呢?
实际上,如果收件地址的服务器不可访问,经过中转可以不断重发,直到成功或确认有问题无法抵达为止。
现在看接收方,SMTP无法用于得到报文,部分用户代理用HTTP,而其他用因特网邮件访问协议(IMAP)。通过这些协议,收件人可以管理自己的邮件,删除邮件等。
2.4 DNS:互联网的目录服务
就像我们每个人有名字,有绰号,有身份证号等,每个主机也有自己的标识方法。
一方面,主机有主机名,比如kajimi.com等,另一方面,主机也可以通过IP地址进行标识。
人类喜欢更拟人的主机名,而路由器更喜欢定长的IP地址,所以我们需要一个方法进行转化。
2.4.1 DNS提供的服务
域名系统(DNS)提供了注记名和IP地址转化的目录服务。
它是是一个由分层DNS服务器组成的分布式数据库,也是一个使得主机能够查询分布式数据库的应用层协议。
与一般的协议为应用提供服务不同,它一般为其他协议提供服务。借用一个例子描述它的工作:
- 用户意图访问kajimi.com
- 浏览器提取主机名,发送给同一台主机上的DNS应用客户端
- DNS客户向DNS服务器发送请求
- DNS服务器返回包含IP地址的报文
- 浏览器获取IP地址,用于发起TCP连接
除了这个服务外,DNS还提供其他服务
- 主机别名:因为有时候规范主机名太神秘,需要好记的主机别名,DNS可以查找主机别名对应法规范主机名和IP地址
- 邮件服务器别名:对于邮件服务器也可以进行相同操作
- 负载分配:因为一些用户多的站点有多个服务器,DNS可以循环返会这些地址,起到分摊的作用。
2.4.2 DNS工作原理概述
在开发者和用户的视角,DNS是一个黑盒子,主机名进IP出,实际上它的结构异常复杂。
DNS的一种简单设计是一个DNS服务器承担所有工作,这显然是不可能的。它不仅不具有扩展性,还容易出问题。
事实上,DNS是因特网分布式数据库的典范。
DNS服务器有3种
- 根DNS服务器
- 大概1000个遍布全球,13个不同根服务器副本,12个组织管理
- 提供TLD服务器的IP地址
- 顶级域DNS服务器(TLDDNS服务器)
- com,org,gov等以及各国的cn,uk,jp等这些顶级域的TLD服务器
- 提供归属的权威DNS服务器的IP地址
- 权威DNS服务器
- 每个拥有公共可访问主机的组织必须提供公共可访问的DNS记录,权威DNS服务器负责保存这些记录,里面包含了主机名和IP地址的关系。
- 组织可以选择自己实现权威DNS服务器,也可以付费将DNS记录存在服务商的权威服务器里。
- 本地DNS服务器
- 实际上不属于层次,一般是离主机特别近的,由ISP提供,起到代理的作用。
想象一下查询主机doc.kajimi.edu的IP地址,假设你主机叫aaa.nya.edu,你的本地DNS服务器叫dns.nya.edu
- aaa.nya.edu向dns.nya.edu发送DNS查询报文,里面有主机名doc.kajimi.edu
- dns.nya.edu向根DNS服务器发送该报文,根DNS服务器注意到edu,返回edu的TLD服务器的IP地址列表
- dns.nya.edu向edu的TLD服务器发送该报文,edu的TLD服务器注意到kajimi.edu,返回权威DNS服务器的IP地址
- dns.nya.edu向权威DNS服务器发送该报文,得到doc.kajimi.edu的IP地址
- dns.nya.edu向你的主机aaa.nya.edu返回这个IP地址
当然,很有可能不能一次直接到达包含指定DNS记录的权威DNS服务器,一般要中转几次。并且DNS查询不强制要求递归查询(把后续的查询交给其他DNS服务器,如1)或迭代查询(自己拿到IP地址再自己继续查询,如2,3,4)。
DNS缓存也是DNS系统的一个特色,和Web缓存器的用处差不多,在本地DNS服务器里面保存近期得到的DNS记录。也可以缓存TLD服务器的IP地址,绕过根DNS服务器。
2.4.3 DNS记录和报文
所有的DNS服务器存储了资源记录(RR),RR提供了主机名到IP地址的映射。DNS回答报文里有一到多条RR。
资源记录是一个4元组(Name, Value, Type, TTL)
- TTL决定了资源记录从缓存里删除的时间
- Type决定了Name和Value的意义
- Type = A Name是主机名 Value是IP地址
- Type = NS Name是域 Value是能获得该域中主机的权威DNS服务器的IP地址
- Type = CNAME Name是主机别名 Value是规范主机名
- type = MX Name是邮件服务器的别名 Value是规范主机名
DNS只有查询报文和回答报文,并且只有一种格式
- 首部区域
- 一共12字节(96比特)
- 第一个字段标识符(16比特)标识查询,会被复制到回答报文以匹配请求和回答
- 第二个字段标志位
- 查询/回答(1比特)0是查询 1是回答
- 权威的(1比特)1是权威
- 希望递归(1比特)1是希望
- 递归可用(1比特)1是可用
- 还有4个问题数 回答RR数 权威RR数 附加RR数,记录了接下来4个数据区域出现的数量
- 问题区域
- 名字字段:被查的主机
- 类型字段:Type是哪个
- 回答区域
- 最初请求名字的资源记录
- 权威区域
- 其他权威服务器的记录
- 附加区域
- 额外有帮助的信息,比如MX请求的回答报文的附加区域会有规范主机名的类型A的记录
现在我们已经知道DNS的查询的全流程了,那我们怎么往DNS数据库里面添加记录呢?
我们需要向注册登记机构提供域名与IP地址,以及基本和辅助权威DNS服务器的名字与IP地址,注册机构会把各种记录插入DNS系统里。
2.5 P2P文件分发
这里讲得非常干,简单来说,P2P体系结构可以通过自扩展性让每个对等方享受服务同时提供服务(比如P2P体系结构的文件分发应用可以让每个下载的对等同时上传已经下载好的部分给其他对等)
BitTorrent是一种流行的P2P协议,它将所有对等方叫做洪流。在洪流中,文件被拆分为块在对等方间互相运输,我们也简单描述一些该协议怎么工作
- 洪流的追踪器跟踪洪流中的对等方
- 一个新的对等方加入洪流,向追踪器注册集资,并获取追踪器提供的多个对等方IP地址,建立TCP连接,成为邻近对等方(下文用邻居简称)
- 对等方会定期查看其他邻居有哪些块,按照最稀缺优先获取最稀有的块
- 对等方会获取邻居向它发送比特的速率,选取最快的四个邻居(被叫做疏通)回馈比特,每10s一换
- 同时每30s,新对等方还会随机选择一个邻居发送数据,试图发现更好的邻居,以不断优化自己的疏通。
这种机制被叫做一报还一报,是BitTorrent的优势。
2.6 视频流和内容分发网
毫无疑问,我们需要刷视频。
2.6.1 因特网视频
视频最大的特色是大,对于平均吞吐量的要求极高,通常我们会把视频压缩成不同的画质来给不同网络条件的客户提供视频。
2.6.2 HTTP流和DASH
简单来说,视频也是通过HTTP来运输,但HTTP的运输没法动态地根据用户的带宽切换,所以我们有经HTTP的动态适应性流(DASH)
用了DASH后,HTTP服务器里面会有告示文件,里面写了不同的视频版本和需要的带宽。视频被切成块,通过测量实时带宽并通过速率选择算法决定下次拿哪个版本的块。
2.6.3 内容分发网
我们知道,现在我们有很多世界流行的视频app,这么多的视频配合世界各地的请求,要怎么保证每个人都能看视频?
几乎所有的视频流视频网站都利用**内容分发网(CDN)**来管理全球各地的服务器,把每个用户的请求定向到最适合的服务器。
CDN有两种服务器安置原则
- 深入,用高度深入的分布式设计,让大量服务器分布在世界各地,尽可能靠近所有的端系统。
- 邀请做客,把服务器大量丢在IXP,降低维护和管理开销。用户体验不如前者。
CDN对于视频在不同服务器的存储操作和前面的Web缓存器很像,也是把视频拿到服务器,同时存储副本,对于不常用的视频进行删除。
而对于重定向的操作,CDN通过DNS来截获以及重定向对视频的请求。
- 首先,每个视频的URL都有独特标识
- 当视频网站的权威DNS拿到带有独特标识的请求后,返回CDN专用的DNS基础设施。
- 向CDN专用的DNS基础设施发出请求,拿到分配的CDN服务器IP地址
- 从指定CDN服务器拿到视频。
而在步骤3中,CDN基于用户的IP地址按照集群选择策略分配了合适的CDN服务器。 通常地理上最为邻近是一个简单且实用的策略,但有时地理上邻近不代表网络上近,同时存在用的本地DNS远离客户的情况,并且忽略了网络的情况。 而一些CDN会对客户和CDN服务器之间的时延进行实时测量,选择最优解,但部分本地DNS不接受测量(
2.6.4 学习案例 Netflix Youtube
Netflix的亚马逊云会获取视频并进行处理,获得不同版本,运用DASH协议提供视频。
对于视频的分发,它会在非高峰时段自动拉取流行的视频,放入CDN服务器,而不是高速缓存未命中后现场获取。
同时它不用DNS重定向来连接客户和服务器,而是直接通过软件告知分配的CDN服务器。
Youtube使用DNS重定向,使用高速缓存未命中后现场获取。并且不运用DASH,而是让用户自己选择视频质量,用HTTP运输。
2.7 套接字编程: 生成网络应用
首先,我们知道网络应用程序按照使用的应用层协议公开与否分为两种。
- 有协议标准定义的操作实现的应用程序遵循RFC的规则,互相可以实现通信。
- 而专用的网络应用程序使用独立且不公开的协议,其他开发者无法写出与之交互代码。
2.7.1 UDP套接字编程
接下来就是读代码
#UDPClient.py
from socket import * #引入socket
serverName = 'hostname' #设置服务器
serverPort = 12000 #设置端口号
clientSocket = socket(AF_INET, SOCK_DGRAM) #创建套接字 参数分别指示使用IPv4和UDP
message = raw_inupt('Input here:') #输入
clientSocket.sendto(message.encode(),(ServerName, serverPort)) #转化输入为字节,向套接字发送分组
modifiedMessage, serverAddress = clientSocket.recvfrom(2048) #得到返回的分组和服务器地址
print(modifiedMessage.decode()) #输出大写化的输入
clientSocket.close() #关闭套接字
#UDPServer.py
from socket import * #引入socket
serverPort = 12000 #设置端口号
serverSocket = socket(AF_INET, SOCK_DGRAM) #创建套接字 参数分别指示使用IPv4和UDP
serverSocket.bind(('', serverPort)) #分配给套接字端口号
while True:
message, clientAddress = serverSocket.recvfrom(2048) #获取分组和客户IP地址
modifiedMessage = message.decode().upper() #大写化
serverSocket.sendto(modifiedMessage.encode(), clientAddress) #返回分组
2.7.2 TCP套接字编程
与UDP不同的是,TCP连接的建立会生成一个新的套接字(连接套接字),而三次握手用的是欢迎套接字 接下来就是读代码
#TCPClient.py
from socket import * #引入socket
serverName = 'hostname' #设置服务器
serverPort = 12000 #设置端口号
clientSocket = socket(AF_INET, SOCK_STREAM) #创建套接字 参数分别指示使用IPv4和TCP
clientSocket,connect((serverName,serverPort)) #建立连接
sentence = raw_input("Input here") #输入
clientSocket.send(sentence.encode()) #发送
modifiedMessage, serverAddress = clientSocket.recvfrom(2048) #获取返回的分组和服务器地址
print(modifiedMessage.decode()) #输出大写化的输入
clientSocket.close() #关闭套接字
#TCPServer.py
from socket import * #引入socket
serverPort = 12000 #设置端口号
serverSocket = socket(AF_INET, SOCK_STREAM) #创建套接字 参数分别指示使用IPv4和UDP
serverSocket.bind(('', serverPort)) #分配给套接字端口号
server.listen(1) #等待握手,最大连接数为1
while True:
connectionSocket, addr = serverSocket.accept() #得到连接套接字
sentence = connectionSocket.recv(1024).decode() #处理分组为字符串
ans = sentence.upper() #大写
connectionSocket.send(ans.encode()) #发送
connectionSocket.close( ) #关闭
2.8 小结
不想写了woc,写了一天这个博客,越写越有了
写于 2025 年 1 月 27 日 00:00