一部分在客户机上,一部分在网络中的服务器上
- 客户机/服务器结构(C/S)
- 点对点结构(P2P)
- 混合结构(Hybird)
- 服务器
- 7 * 24小时提供服务
- 永久性访问地址/域名
- 利用大量服务器实现可扩展性
- 客户机
- 与服务器通信,使用服务器提供的服务
- 间歇性接入网络
- 可能使用动态IP地址
- 不会与其他客户机直接通信
-
没有永远在线的服务器
-
任一端系统/节点之间可以直接通讯
-
节点间歇性接入网络
-
节点可能改变IP地址
-
优点:高度可伸缩
-
缺点:难于管理
- Napster
- 文件传输使用P2P结构
- 文件搜索采用C/S结构——集中式
- 每个节点向中央服务器登记自己的内容
- 每个节点向中央服务器提交查询请求,查找感兴趣的内容
- 进程:主机上运行的程序
- 同一主机上运行的进程之间如何通信?
- 进程间通信机制
- 操作系统提供
- 不同主机上运行的进程间如何通信?
- 消息交换
- 客户机进程:发起通信的进程
- 服务器进程:等待通信请求的进程
- 进程间通信利用socket发送/接收消息实现
- 传输基础设施向进程提供API
- 传输协议的选择
- 参数的设置
- 不同主机上的进程间通信,那么每个进程必须拥有标识符
- 如何寻址主机?——IP地址
- 主机有了ip地址后不足以定位进程,由于可能有多个进程需要通信
- 端口号
- 为主机上每个需要通信的进程分配一个端口号
- HTTP Server: 80
- Mail Server: 25
- 进程的标识符
- IP地址 + 端口号
- 网络应用需遵循应用层协议
- 公开协议
- 由RFC定义
- 允许互操作
- HTTP, SMTP, ......
- 私有协议
- 多数P2P文件共享应用
- 消息的类型
- 请求消息
- 响应消息
- 消息的语法/格式
- 消息中包含的字段
- 每个字段的描述
- 字段的语义
- 字段中信息的含义
- 规则
- 进程何时发送/响应消息
- 进程如何发送/响应消息
- 数据丢失/可靠性
- 某些网络应用能够容忍一定的数据丢失:网络电话
- 某些网络应用要求100%可靠的数据传输:文件传输,telnet
- 时间/延迟
- 有些应用只有在延迟足够低时才“有效”
- 网络电话/网络游戏
- 带宽
- 某些应用只有在带宽达到最低要求时才“有效”:网络视频
- 某些应用能够适应任何带宽——弹性应用:email
- TCP服务
- 面向连接:客户机/服务器进程间需要建立连接
- 可靠的传输
- 流量控制:发送方不会发送速度过快,超过接收方的处理能力
- 拥塞控制:当网络负载过重时能够限制发送方的发送速度
- 不提供时间/延迟保障
- 不提供最小带宽保障
- UDP服务
- 无连接
- 不可靠的数据传输
- 不提供:
- 可靠性保障
- 流量控制
- 拥塞控制
- 延迟保障
- 带宽保障
- world wide web
- 网页
- 网页互相链接
- 网页包含多个对象
- 对象:HTML文件、JPEG图片、视频文件、动态脚本
- 基本HTML文件:包含对其他对象的引用链接
- 对象的寻址
- URL:统一资源定位器
- scheme://host:port/path
- 超文本传输协议
- C/S结构
- 客户——Browser:请求、接收、展示web对象
- 服务器——web server:响应客户端的请求,发送对象
- 使用TCP传输服务
- 服务求在80端口等待客户的请求
- 浏览器发起到服务器的TCP连接(创建套接字socket)
- 服务器接收来自浏览器的TCP连接
- 浏览器(HTTP客户端)与web服务器(HTTP服务器)交换HTTP消息
- 关闭TCP连接
- 无状态
- 服务器不维护任何有关客户端过去所发请求的信息
- 有状态的协议更复杂:
- 需维护状态(历史信息)
- 如果客户或服务器失效,会产生状态不一致,解决这种不一致代价高
- 非持久连接
- 每个TCP连接最多允许传输一个对象
- HTTP 1.0版本使用非持久性连接
- 持久性连接
- 每个TCP连接允许传输多个对象
- HTTP 1.1版本默认使用持久性连接
假定用户在浏览器中输入URLwww.someSchool.edu/someDepartment/home.index
包含文本和指向10个jpeg图片的链接
- HTTP客户端向地址为www.someSchool.edu的服务器上的HTTP服务器进程(端口80)发起TCP连接请求
- HTTP服务器在端口80等待TCP连接请求,接受连接并通知客户端
- HTTP客户端将HTTP请求消息(包含URL地址)通过TCP连接的套接字发出,消息中所含的URL表明客户端需要对象someDepartment/home.index
- HTTP服务器收到请求消息解析,产生包含所需要对象的响应消息,并通过套接字发给客户端
- HTTP服务器关闭TCP连接
- HTTP客户端收到响应消息,解析html文件,显示html文件,发现有10个指向jpeg对象的超链接
- 为每个jpeg对象重复上述步骤
- RTT
- 从客户端发送一个很小的数据包到服务器并返回锁经历的时间
- 响应时间
-
发起、建立TCP连接:1个RTT
-
发送HTTP请求消息到HTTP响应消息的前几个字节到达:1个RTT
-
响应消息中所含的文件/对象传输时间
-
Total = 2RTT + 文件发送时间
-
- 非持久性连接的问题
- 每个对象需要2个RTT
- 操作系统需要为每个TCP连接开销资源
- 浏览器会怎么做?
- 打开多个并行的TCP连接以获取网页所需对象
- 给服务器端造成什么影响?
- 持久性连接
- 发送响应后,服务器保持TCP连接的打开
- 后续的HTTP消息可以通过这个连接发送
- 无流水的持久性连接
- 客户端只有收到前一个响应之后才发送新的请求
- 每个被引用的对象耗时1个RTT
- 带有流水截止的持久性连接
- HTTP 1.1的默认选项
- 客户端只要遇到一个引用对象就尽快发出请求
- 理想情况下,收到所有的引用对象只需耗时约1个RTT
- HTTP协议有两类消息
- 请求消息
- 响应消息
- 请求消息
- ASCII:人直接可读
- POST方法
- 网页经常需要填写表格(form)
- 在请求消息的消息体中上传客户端的输入
- URL方法
- 使用GET方法
- 输入信息通过request行的URL字段上传
- HTTP/1.0
- GET
- POST
- HEAD
- 请Server不要将所请求的对象放入响应消息中
- HTTP/1.1
- GET,POST,HEAD
- PUT
- 将消息体中的文件上传到URL字段所指定的路径
- DELETE
- 删除URL字段所指定的文件
- 响应消息的第一行
- 示例
- 200 OK
- 301 Moved Permanently
- 400 Bad Request
- 404 Not Found
- 505 HTTP Version Not Supported
- HTTP协议唔状态,很多应用需要掌握客户端的状态
- cookie
- 某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)
- cookie的组件
- HTTP响应消息的cookie头部行
- HTTP请求消息的cookie头部行
- 保存在客户端主机上的cookie文件,由浏览器管理
- web服务器端的后台数据库
- cookie能够用于
- 身份认证
- 购物车
- 推荐
- web email
- 隐私问题
- 功能
- 在不访问服务器的前提下满足客户端的HTTP请求
- 为什么要发明这种技术?
- 缩短客户端请求的响应时间
- 减少机构/组织的流量
- 在大范围内(Internet)实现有效的内容分发
- web缓存/代理服务器
- 用户设定浏览器通过缓存进行web访问
- 浏览器向缓存/代理服务器发送所有的HTTP请求
- 如果所请求的对象在缓存中,缓存返回对象
- 否则,缓存服务器向原始服务器发送HTTP请求,获取对象,然后返回给客户端并保存该对象
- 缓存既充当客户端,也充当服务器
- 一般由ISP架设
- 目标:
- 如果缓存有最新的版本,则不需要发送请求对象
- 缓存:
- 在HTTP请求消息中声明所持有版本的日期
- If-modified-since:
- 服务器:
- 如果缓存的版本是最新的,则响应消息中不包含对象
- HTTP/1.0 304 Not Modified
- Email应用的构成组件
- 邮件客户端
- 邮件服务器
- SMTP协议
- 邮件客户端
- 读、写Email消息
- 与服务器交互,收、发Email消息
- Outlook, Foxmail, Thunderbird
- web客户端
- 邮件服务器
- 邮箱:存储发给该用户的Email
- 消息队列:存储等待发送的Email
- SMTP协议
- 邮件服务器之间传递消息所使用的协议
- 客户端:发送消息的服务器
- 服务器:接收消息的服务器
- 使用TCP进行email消息的可靠传输
- 端口25
- 传输过程的三个阶段
- 握手
- 消息的传输
- 关闭
- 命令/响应交互模式
- 命令:ASCII文本
- 响应:状态代码和语句
- Email消息只能包含7位ASCII码
- 持久性连接
- 要求消息必须由7位ASCII码构成
- SMTP服务器利用CRLF.CRLF确定消息的结束
- 与HTTP对比:
- HTTP:拉式
- SMTP:推式
- 都使用命令/响应交互模式
- 命令和状态代码都是ASCII
- HTTP:每个对象封装在独立的响应消息中
- SMTP:多个对象在由多个部分构成的消息中发送
- SMTP:email消息的传输/交换协议
- RFC 822:文本消息格式标准
- 头部行
- To
- From
- Subject
- 消息体
- 消息本身
- 只能是ASCII字符
- 头部行
- MIME:多媒体邮件扩展
- 通过在邮件头部增加额外的行以声明MIME的内容类型
- 邮件访问协议:从服务器获取邮件
- POP
- 认证/授权(客户端<-->服务器)和下载
- IMAP
- 更多功能
- 更加复杂
- 能够操纵服务器上存储的消息
- HTTP:163, QQ Mail等。
- POP
- 认证过程
- 客户端命令
- User:声明用户名
- Pass:声明密码
- 服务器响应
- +OK
- -ERR
- 客户端命令
- 事务阶段
- List:列出消息数量
- Retr:用编号获取消息
- Dele:删除消息
- Quit
- “下载并删除”模式
- 如果用户换了客户端软件,无法重读该邮件
- “下载并保持”模式:不同客户端都可以保留消息的拷贝
- POP3是无状态的
- 所有消息统一保存在一个地方:服务器
- 允许用户利用文件夹组织消息
- IMAP支持跨会话的用户状态:
- 文件夹的名称
- 文件夹与消息ID的映射等
- Internet主机/路由器的识别问题
- IP地址
- 域名
- 问题:域名和IP地址之间如何映射?
- 域名解析系统DNS
- 多层命名服务器构成的分布式数据库
- 应用层协议:完成名字的解析
- Internet核心功能,应用层协议实现
- 网络边界复杂
- DNS服务
- 域名向IP地址的翻译
- 主机别名
- 邮件服务器别名
- 负载均衡:web服务器
- 问题:为什么不适用集中式的DNS
- 单点失败问题
- 流量问题
- 距离问题
- 维护性问题
- 客户端想要查询www.amazon.com的IP
- 客户端查询根服务器,找到com域名解析服务器
- 客户端查询com域名解析服务器,找到amazon.com域名解析服务器
- 客户端查询amazon.com域名解析服务器,获得www.amazon.com的IP地址
- 本地域名解析服务器无法解析域名时,访问根域名服务器
- 根域名服务器
- 如果不知道映射,访问权威域名服务器
- 获得映射
- 向本地域名服务器返回映射
- 全球有13个根域名服务器
- 顶级域名服务器:负责com, org, net, edu等顶级域名和国家顶级域名,例如cn, uk, fr等
- Network Solutions维护com顶级域名服务器
- Educause维护edu顶级域名服务器
- 权威域名服务器:组织的域名解析服务器,提供组织内部服务器的解析服务
- 组织负责维护
- 服务提供商负责维护
- 不严格属于层级体系
- 每个ISP有一个本地域名服务器
- 默认域名解析服务器
- 当主机进行DNS查询时,查询被发送到本地域名服务器
- 作为代理,将查询转发给(层级式)域名解析服务器系统
- 迭代查询
- 被查询服务器返回域名解析服务器的名字
- “我不认识这个域名,但是你可以问这个服务器”
- 递归查询
- 将域名解析的任务交给所联系的服务器
- 只要域名解析服务器获得域名——IP映射,即缓存这一映射
- 一段时间过后,缓存条目失效(删除)
- 本地域名服务器一般会缓存顶级域名服务器的映射
- 因此根域名服务器不经常被访问
- 资源记录(RR)
- RR format: (name, value, type, ttl)
- Type = A
- Name: 主机域名
- Value: IP地址
- Type = NS
- Name: 域(如:edu.cn)
- Value: 该域权威域名解析服务器的主机域名
- Type = CNAME
- Name: 某一真实域名的别名
- Value: 真实域名
- Type = MX
- Value是与name相对应的邮件服务器
- DNS协议:
- 查询和回复
- 消息格式相同
- 消息头部
- Identification: 16位查询编号,回复使用相同的编号
- flags
- 查询或回复
- 期望递归
- 递归可用
- 权威回答
- 例子:你刚刚创建了一个公司Network Utopia
- 在域名管理机构注册域名networkutopia.com
- 向域名管理机构提供你的权威域名解析服务器的名字和IP地址
- 域名管理结构向com顶级域名解析服务器中插入两条记录
- (networkutopia.com, dns1.networkutopia.com, NS)
- (dns1.networkutopia.com, 212.212.212.212.1, A)
- 在权威域名解析服务器中为www.networkutopia.com加入Type A记录,为networkutopia.com加入Type MX记录
- Peer-to-Peer
- 没有服务器
- 任意端系统之间直接通信
- 节点阶段性接入Internet
- 节点可能更换IP地址
- tracker:跟踪参与torrent的节点
- torrent:交换同一个文件的文件块的节点组
- 文件划分为256KB的chunk
- 节点加入torrent
- 没有chunk,但是会逐渐积累
- 向tracker注册以获得节点清单,与某些节点(邻居)建立连接
- 下载的同时,节点需要向其他节点上传chunk
- 节点可能加入或离开
- 一旦节点获得完整的文件,它可能离开或留下
- 获取chunk
- 给定任一时刻,不同节点持有文件的不同chunk集合
- 节点定期查询每个邻居所持有的chunk列表
- 节点发送请求,请求获取缺失的chunk
- 稀缺优先
- 发送chunk:tit-for-tat
- Alice向4个邻居发送chunk:正在向其发送chunk,速率最快的4个
- 每10s重新评估top 4
- 每30s随机选择一个其他节点,向其发送chunk
- 新选择节点可能加入top 4
- “optimistically unchoke”
- Alice向4个邻居发送chunk:正在向其发送chunk,速率最快的4个
- P2P系统的索引:信息到节点位置(IP地址+端口号)的映射
- 文件共享(电驴)
- 利用索引动态跟踪节点所共享的文件的位置
- 节点需要告诉索引它拥有哪些文件
- 节点搜索索引,从而获知能够得到哪些文件
- 即时消息(QQ)
- 索引负责将用户名映射到位置
- 当用户开启IM应用时,需要通知索引它的位置
- 节点检索索引,确定用户的IP地址
- Napster最早采用这种设计
- 节点加入时,通知中央服务器
- IP地址
- 内容
- 用户从中央服务器查找所需文件
- 用户从其他用户获取请求文件
- 节点加入时,通知中央服务器
- 内容和文件传输是分布式的,但是内容定位是高度集中式的
- 单点失效问题
- 性能瓶颈
- 版权问题
- 完全分布式架构
- Gnutella采用这种架构
- 每个节点对它共享的文件进行索引,且只对它共享的文件进行索引
覆盖网站:
-
节点X与Y之间如果有TCP连接那么构成一个边
-
所有的活动节点和边构成覆盖网络
-
边:虚拟链路
-
节点一般邻居数少于10个
-
查询消息通过已有的TCP连接发送
-
节点转发查询消息
-
如果查询命中,则利用反向路径返回查询节点
- 介于集中式索引和洪泛式查询之间的方法
- 每个节点或者是一个超级节点,或者被分配一个超级节点
- 节点和超级节点间维持TCP连接
- 某些超级节点对之间维持TCP连接
- 超级节点负责跟踪子节点的内容
- 直接面向网卡编程
- 硬件相关
- 基于Packet Driver编程
- 屏蔽网卡细节
- 适用于所有网卡
- 基于NDIS网络编程
- Windows
- 基于LibPcap/WinPcap、Libnet、Libnids、Libicmp编程
- NetBIOS编程
- windows
- Socket编程
- WEB、RPC、中间件编程
应用编程接口API:就是应用进程的控制权和操作系统的控制权进行转换的一个系统调用接口。
- Berkeley UNIX操作系统定义了一种API,称为套接字接口,简称套接字、
- 微软公司在其操作系统中采用了套接字接口API,形成了一个稍有不同的API,并称之为WINSOCK。
- AT&T为其UNIX系统V定义了一种API,简写为TLI。
- 最初设计
- 面向BSD UNIX-Berkley
- 面向TCP/IP协议栈接口
- 目前
- 事实上的工业标准
- 绝大多数操作系统都支持
- Internet网络应用最典型的API接口
- 通信模型
- 客户/服务器
- 应用进程间通信的抽象机制
- 标识通信端点(对外):
- IP地址+端口号
- 操作系统/进程如何管理套接字(对内)?
- 套接字描述符
- 小整数
- 套接字描述符
- 类似于文件的抽象
- 当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息
- 返回套接字描述符
struct sockaddr_in
{
u_char sin_len;
u_char sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
- 使用TCP/IP协议簇的网络应用程序声明端点地址变量时,使用结构sockaddr_in
WSAStartup
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
- 使用socket的应用程序在使用socket之前必须首先调用WSAStartup函数
- 两个参数:
- 第一个参数指明程序请求使用的WinSock版本,其中高位字节指明福版本、低位字节指明主版本
- 十六进制整数,例如0x102表示2.1版
- 第二个参数返回实际的winsock的版本信息
- 指向WSADATA结构的指针
- 第一个参数指明程序请求使用的WinSock版本,其中高位字节指明福版本、低位字节指明主版本
WSACleanup
int WSACleanup(void);
- 应用程序在完成对请求的Socket库的使用,最后要调用WSACleanup函数
- 解除与socket库的绑定
- 释放socket库所占用的系统资源
socket
sd = socket(protofamily, type, proto);
- 创建套接字
- 操作系统返回套接字描述符
- 第一个参数(协议族):protofamily = PF_INET (TCP/IP)
- 第二个参数(套接字类型):
- type = SOCK_STREAM, SOCK_DGRAM or SOCK_RAW (TCP/IP)
- 第三个参数(协议号):0为默认
closesocket
int closesocket(SOCKET sd);
- 关闭一个描述符为sd的套接字
- 如果多个进程共享一个套接字,调用closesocket将套接字引用计数减1,减至0才关闭
- 一个进程中的多线程对一个套接字的使用无计数
- 如果进程中的一个线程调用closesocket将一个套接字关闭,该进程中其他线程也将不能访问该套接字
- 返回值:
- 0:成功
- SOCKET_ERROR:失败
bind
int bind(sd, localaddr, addrlen);
- 绑定套接字的本地端点地址
- IP地址+端口号
- 参数:
- 套接字描述符(sd)
- 端点地址(localaddr)
- 结构sockaddr_in
- 客户端程序一般不必调用bind函数
- 服务器端
- 熟知端口号
- IP地址
- 地址通配符:INADDR_ANY
listen
int listen(sd, queuesize);
- 置服务器端的流套接字处于监听状态
- 仅用于服务端调用
- 仅用于面向连接的流套接字
- 设置连接请求队列大小
- 返回值:
- 0:成功
- SOCKET_ERROR:失败
connect
connect(sd, saddr, saddrlen);
- 客户程序调用connect函数来使客户套接字与特定计算机的特定端口的套接字进行连接
- 仅用于客户端
- 可用于TCP客户端也可以用于UDP客户端
- TCP客户端:建立TCP连接
- UDP客户端:指定服务器端点地址
accept
newsock = accept(sd, caddr, caddrlen);
- 服务程序调用accept函数从处于监听状态的流套接字sd的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道
- 仅用于TCP套接字
- 仅用于服务器
- 利用新创建的套接字与客户通信
send, sendto
send(sd, \*buf, len, flags);
sendto(sd, \*buf, len, flags, destaddr, addrlen);
- send函数用于TCP套接字或调用了connect函数的UDP客户端套接字
- sendto函数用于UDP服务器端套接字与未调用connect函数的UDP客户端套接字
recv, recvfrom
recv(sd, \*buf, len, flags);
recvfrom(sd, \*buf, len, flags, senderaddr, saaddrlen);
- recv函数从TCP连接的另一端接收数据,或者从调用了connect函数的UDP客户端套接字接收服务器发来的数据
- recvfrom函数用于从UDP服务器端套接字与未调用connect函数的UDP客户端套接字接收对端数据
setsockopt, getsockopt
int setsockopt(int sd, int level, int optname, \*optval, int optlen);
int getsockopt(int sd, int level, int optname, \*optval, socklen_t \*optlen);
- setsockopt()函数用来设置套接字sd的选项参数
- getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval
- TCP:可靠、面向连接、字节流传输、点对点
- UDP:不可靠、无连接、数据报传输
- TCP/IP定义了标准的用于协议头中的二进制整数表示:网络字节顺序
- 某些socket API函数的参数需要存储为网络字节顺序(如IP地址、端口号等)
- 可以实现本地字节顺序与网络字节顺序间转换的函数
- htons:本地字节顺序->网络字节顺序(16bits)
- ntohs:网络字节顺序->本地字节顺序(16bits)
- htonl:本地字节顺序->网络字节顺序(32bits)
- ntohl:网络字节顺序->本地字节顺序(32bits)
SERVER(WSAStartup->s=socket()->bind(s)->listen(s)->ns=accept(s)) CLIENT(WSAStartup->s=socket()->connect(s)) SERVER(recv(ns)) CLIENT(send(s)->recv(s)) SERVER(send(ns)) CLIENT(closesocket(s)->WSACleanup) SERVER(closesocket(ns))
- 客户端可能使用域名或IP地址标识服务器
- IP协议需要使用32位二进制IP地址
- 需要将域名或IP地址转换为32位IP地址
- 函数inet_addr()实现点分十进制IP地址到32位IP地址转换
- 函数gethostbyname()实现域名到32位IP地址转换
- 返回一个指向结构hostent的指针
- 客户端还可能使用服务名标识服务器端口
- 需要将服务名转换为熟知端口号
- 函数getservbyname()
- 返回一个指向结构servent的指针
- 函数getservbyname()
- 客户端可能使用协议名指定协议
- 需要将协议名转换为协议号
- 函数getprotobyname()实现协议名到协议号的转换
- 返回一个指向结构protoent的指针
- 函数getprotobyname()实现协议名到协议号的转换
- 确定服务器IP地址与端口号
- 创建套接字
- 分配本地端点地址(IP地址+端口号)
- 连接服务器(套接字)
- 遵循应用层协议进行通信
- 关闭/释放连接
- 确定服务器IP地址与端口号
- 创建套接字
- 分配本地端点地址(IP地址+端口号)
- 指定服务器端点地址,构造UDP数据报
- 遵循应用层协议进行通信
- 关闭/释放连接
- 循环无连接服务器
- 循环面向连接服务器
- 并发无连接服务器
- 并发面向连接服务器
- 创建套接字
- 绑定端点地址(INADDR_ANY + 端口号)
- 反复接收来自客户端的请求
- 遵循应用层协议,构造响应报文,发送给客户
- 服务器端不能使用connect()函数
- 无连接服务器使用sendto()函数发送数据报
- 调用recvfrom()函数接收数据时,自动提取
- 创建(主)套接字,并绑定熟知端口号
- 设置(主)套接字为被动监听模式,准备用于服务器
- 调用accept()函数接收下一个连接请求(通过主套接字),创建新套接字用于与该客户建立连接
- 遵循应用层协议,反复接收客户请求,构造并发送响应(通过新套接字)
- 完成为特定客户服务后,关闭与该客户之间的连接,返回步骤3
主线程1:创建套接字,并绑定熟知端口号 主线程2:反复调用recvfrom()函数,接收下一个客户请求,并创建新线程处理该客户响应 子线程1:接收一个特定请求 子线程2:依据应用层协议构造响应报文,并调用sendto()发送; 子线程3:退出(一个子线程处理一个请求后即终止)
主线程1:创建(主)套接字,并绑定熟知端口号 主线程2:设置(主)套接字为被动监听模式,准备用于服务器 主线程3:反复调用accept()函数接收下一个连接请求(通过主套接字),并创建一个新的子线程处理该客户响应 子线程1:接收一个客户的服务请求(通过新创建的套接字) 子线程2:遵循应用层协议与特定客户端进行交互 子线程3:关闭/释放连接并退出(线程终止)