应用层

应用层

2022-05-05
计算机网络, c语言

HTTP #

  • 网页包含多个对象,如HTML文件、JPEG图片、视频文件、动态脚本等,基本HTML文件包含对其他对象引用的链接。通过URL可以进行对象的寻址。
  • HTTP协议流程:
    1. 服务器在80端口等待客户的请求
    2. 浏览器发起到服务器的TCP连接(创建套接字Socket)
    3. 浏览器与Web服务器交换HTTP消息
    4. 关闭TCP连接
  • HTTP是无状态的协议,即服务器不维护任何有关客户端过去所发请求的信息。

HTTP连接 #

  • HTTP包含非持久性连接和持久性连接
    • 非持久性连接指的是每个TCP连接最多允许传输一个对象,HTTP1.0使用非持久性连接。
    • 持久性连接指每个TCP连接可以传输多个对象,HTTP1.1默认使用持久性连接。

HTTP消息格式 #

  • HTTP有两类消息,请求消息(request)和响应消息(response)。
  • HTTP请求消息的格式如下:
  • HTTP请求格式
  • HTTP响应消息的格式如下:
  • HTTP请求格式

Web缓存 #

  • 如果浏览器缓存过数据,当下次发送相同请求时,浏览器向服务器发送HTTP请求,并带上If-modified-since: <date>
  • 如果对象未改变,则返回304 Not Modified,不反会对象,表示对象未被修改。
  • 如果对象发生改变,服务器会返回对象。

DNS #

  • DNS的是分布式数据库。提供了域名向IP地址的翻译、主机别名等功能。
  • 一般来说是本地域名解析服务器代替进行域名解析的,当主机进行DNS查询时,查询会被发送到本地域名服务器,当本地域名服务器无法解析域名时,就会访问根域名服务器。全球共13个根域名服务器。
  • 顶级域名服务器(TLD,top-level domain),负责com、org、net等顶级域名和国家顶级域名,如cn、uk等。
  • 权威域名服务器是组织的域名解析服务器,提供组织内部服务器的解析服务。
  • cis.poly.edu想获取gaia.cs.umass.edu的IP地址时,迭代查询流程如下
  • 迭代查询流程
  • 递归查询的流程如下(将域名解析的任务交给所联系的服务器)
  • 递归查询流程
  • 只要域名解析服务器获得域名时,就会缓存这一映射,一段时间后缓存条目才会失效。

DNS记录 #

  • 资源记录格式为(name, value, type, ttl),类型如下
  • type=A,Name为主机域名,Value为IP地址
  • type=NS,Name为域(如edu.cn),value为该域权威域名解析服务器的主机域名。
  • type=CNAME,name为某一真实域名的别名,value为真实域名
  • type=MX,value是与name对应的邮件服务器。

DNS协议消息格式 #

  • DNS查询(query)和回复(reply消息)的格式相同。
  • 消息头部
    • Identification:16为查询编号,回复使用相同的编号。
    • flags表示查询或回复、期望递归、递归可用、权威回答。
  • DNS查询流程

socket #

  • 对外通过IP地址+端口号表示通信端点。
  • 操作系统通过套接字描述符(socket descriptor)来管理套接字。
  • socket类似于文件,当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息。
  • socket描述符表

地址结构 #

  • 使用TCP/IP协议簇的网络应用程序声明端点地址变量时,使用结构socketaddr_in
struct sockaddr_in
{
  u_char sin_len; /*地址长度 */
  u_char sin_family; /*地址族(TCP/IP:AF_INET) */
  u_short sin_port; /*端口号 */
  struct in_addr sin_addr; /*IP地址 */
  char sin_zero[8]; /*未用(置0) */
}

socket api函数 #

socket函数 #

  • sd = socket(protofamily,type,proto); 创建套接字并返回套接字描述符。
  • 第一个参数指定协议族:protofamily=PF_INET(TCP/IP)
  • 第二个参数指定套接字类型:SOCK_STREAM(TCP), SOCK_DGRAM(UDP), SOCK_RAW(面向网络层)
  • 第三个参数指定协议号:0为默认
struct protoent *p;
p=getprotobyname("tcp");
SOCKET sd=socket(PF_INET,SOCKET_STREAM,p->p_proto);

close #

  • int close(SOCKET sd) 关闭一个描述符为sd的套接字
  • 如果多个进程共享一个套接字,调用close将套接字引用计数减1,减至0才关闭。
  • 一个进程中的多线程对一个套接字的使用无计数。
  • 返回值 0:成功,SOCKET_ERROR:失败。

bind #

  • int bind(sd,localaddr,addrlen); 绑定套接字的本地端点地址
  • 客户端一般不必调用bind函数,一般由服务端调用。
  • 一台机器可能由多个网卡,可用使用地址通配符INADDR_ANY来绑定。

listen #

  • int listen(sd,queuesize);置服务器端的流套接字处理处于监听状态。
  • 仅服务端调用,仅用于面向连接的流套接字。
  • queuesize表示连接请求的队列大小。
  • 返回值 0:成功,SOCKET_ERROR:失败。

connect #

  • connect(sd,saddr,saddrlen) 客户端调用connect函数来使客户端套接字(sd)与特定计算机的特定端口(saddr)的套接字服务进行连接。
  • 仅用于客户端,可用于TCP客户端也可以用于UDP客户端。

accept #

  • newsock = accept(sd,caddr,caddrlen);服务程序调用accept函数从处于监听状态的流套接字sd的客户端连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道。
  • 仅用于TCP套接字,仅用于服务器。
  • 服务器会利用新创建的套接字(newsock)与客户端通信。

send #

  • send(sd,*buf,len,flags);
  • sendto(sd,*buf,len,flags,destaddr,addrlen);
  • send:发送数据(用于TCP套接字或连接模式(调用了connect函数)的客户端UDP套接字)
  • sendto函数用于UDP服务器端套接字与未调用connect函数的UDP客户端套接字发送数据

recv #

  • recv(sd,buffer,len,flags);
  • recvfrom(sd,buf,len,flags,senderaddr,saddrlen);
  • recv函数从TCP连接的另一端接收数据,或者从调用了connect函数的UDP客户端套接接收服务器发来的数据
  • recvfrom函数用于从UDP服务器端套接字与未调用connect函数的UDP客户端套接字接收对端数据

setsockopt,getsockopt #

  • int setsockopt(int sd, int level, int optname, *optval, int optlen);
  • int setsockopt(int sd, int level, int optname, *optval, int optlen);
  • setsockopt()函数用来设置套接字sd的选项参数
  • getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval

网络字节序 #

  • 网络字节序采用大端排序方式(低位低地址,高位高地址)
  • 某些Socket API函数的参数需要存储为网络字节顺序(如IP地址、端口号等)
  • 转换函数
    • htons: 本地字节顺序→网络字节顺序(16bits)
    • ntohs: 网络字节顺序→本地字节顺序(16bits)
    • htonl: 本地字节顺序→网络字节顺序(32bits)
    • ntohl: 网络字节顺序→本地字节顺序(32bits)

解析服务器IP地址 #

  • 客户端可能使用域名或IP地址标识服务器,IP协议需要使用32为二进制IP地址,需要将函数名或IP地址转换为32为IP地址。
  • inet_addr可用实现点分十进制IP地址到32位IP地址转换。
  • gethostbyname实现域名到32位IP地址转换。会返回一个指向结构hostent的指针。
struct hostent {
 char FAR* h_name; /*official host name */
 char FAR* FAR* h_aliases; /*other aliases */
 short h_addrtype; /*address type */
 short h_lengty; /*address length */
 char FAR* FAR* h_addr_list; /*list of address */
};
#define h_addr h_addr_list[0] 

解析服务器端口号 #

  • 客户端可能使用服务名(如HTTP)标识服务器端口,需要将服务名转换为熟知端口号
  • getservbyname会返回一个指向结构servent的指针。
struct servent {
 char FAR* s_name; /*official service name */
 char FAR* FAR* s_aliases; /*other aliases */
 short s_port; /*port for this service */
 char FAR* s_proto; /*protocol to use */
}; 

解析协议号 #

  • 客户端可能使用协议名来指定协议,需要将协议名转换为协议号
  • 函数getprotobyname实现协议名到协议号的转换。会返回一个protoent的指针。
struct protoent {
 char FAR* p_name; /*official protocol name */
 char FAR* FAR* p_aliases; /*list of aliases allowed */
 short p_proto; /*official protocol number*/
 }; 

TCP客户端软件流程 #

  1. 确定服务器IP地址和端口号
  2. 创建套接字
  3. 分配本地端点地址(可选)
  4. 连接服务器(套接字)
  5. 遵循应用层协议进行通信
  6. 关闭/释放连接

UDP客户端软件流程 #

  1. 确定服务器IP地址与端口号
  2. 创建套接字
  3. 分配本地端点地址(IP地址+端口号)
  4. 指定服务器端点地址,构造UDP数据报
  5. 遵循应用层协议进行通信
  6. 关闭/释放套接字