【操作系统】网络编程

承接Unix I/O这一章节,了解网络如何构建以及客户端和服务器如何通信,socket函数相关内容的学习

CSAPP Ch-11 笔记

CS编程模型

客户端服务器模型

一个服务器进程,多个客户端进程

基本操作是事务transaction

有四步

  1. 客户端向服务器发送请求,发起一个事务
  2. 服务器解释请求,用响应的方式操作资源
  3. 服务器返回响应,等待下一个请求
  4. 客户端接受响应并处理

网络

网络是一种IO设备,是数据源和数据接收方

网络数据接收后经IO和内存总线复制到内存,双向传输

以太网Ethernet是局域网技术

电缆链接主机和集线器,集线器广播每个端口的信息到所有主机

主机发送帧frame开头的信息,包括header,随后是有效载荷,payload,网内每个主机都可以见,只有目的主机读取

桥接以太网,主机…-集线器-桥-桥-集线器-…主机,网桥会根据通信需要来选择是否转发帧到其他网桥

连接多个局域网使用路由器

协议解决网络传输中的差异,提供命名机制(网络地址)和传输机制(包头+有效载荷)

数据发送过程:

  1. 主机A通过系统调用从虚存加载数据到内核缓冲区
  2. 主机A上的协议软件添加互联网包头和接口帧头,互联网包头指向主机B,接口帧头指向主机LAN1。是封装关系。 帧头的包数据为互联网包头以及数据,帧头为其自己的互联网包头
  3. LAN1适配器复制数据到网络
  4. 路由器读取数据猴传送到协议软件
  5. 协议软件读取目标互联网地址,作为路由表的索引转发到对应的LAN2适配器
  6. LAN2适配器复制帧到网络
  7. 到达主机B,从适配器读取帧,传输到协议软件
  8. 协议软件去除包头和帧头,读取数据,主机B通过系统调用拷贝数据到虚拟地址空间

全球IP

客户端(用户)-【socket接口系统调用】-TCP/IP(内核)-【硬件接口,中断】-网络适配器(硬件)-全局IP因特网

使用socket接口函数和Unix IO函数通信,socket函数为系统调用,调用内核的TCP/IP函数

TCP/IP是一个协议族,IP协议提供命名方法和传递机制,数据报

IP机制不可靠,数据报丢失不会自动恢复

UDP UNreliable datagram protocol,不可靠数据报协议对其进行了扩展,包可以在进程之间传递,而不是主机之间

TCP是基于IP的协议,进程间可靠的全双工连接

因特网的特性:

  • 主机集合映射到32位ip地址
  • ip地址映射为一组域名
  • 主机上的进程能够通过连接其他主机的进程通信

ipv4和ipv6:

  • version 4:32位地址
  • version 6:128位地址

ip地址

32位无符号整数,8*4

TCP/IP统一了字节顺序,大端法

使用点分十进制表示,Linux查看主机地址

1
hostname -i

域名

一级域名mil,edu,gov,com,cn

二级域名whut.edu

hosts文件手工维护ip和域名的映射

DNS,domain name system

Linux使用nslookup查看域名ip

1
nslookup baidu.com

通常域名和ip一一对应,多个域名也可以对应同一ip,泛域名cs.whut.edu, ee.whut.edu

多个域名可以映射到同一组的多个ip,如www.twitter.com和twitter.com映射到199.16.156.*

因特网连接

客户端和服务器在连接上发送和接收字节流来通信,点对点,全双工,可以同时双向流动

socket是连接的端点,每个socket都有socket地址,由地址:端口构成,十六位端口号0-65535

客户端发起请求时,客户端socket地址端口由内核自动分配,临时端口;服务器的为默认端口

web使用80(http),电子邮件地址使用25(SMTP)

可以通过/etc/services看查知名服务默认端口

一个连接两端的socket地址是唯一确定的,socket pair,格式(cliaddr:cliport, servaddr:servport),即(客户端IP:客户端端口,服务器IP:服务器端口)

socket接口

一组函数实现,和Unix IO配合创建网络应用

流程:

客户端getaddrinfo->socket->connect发送连接请求->rio_writen->rio_readlineb->close发送EOF

服务器getaddrinfo->socket->bind->listen->accept接受请求->rio_readlineb读客户端的write->rio_writen写入response->rio_readlineb读取EOF-close

socket地址结构

对于Linux内核,socket就是一个有描述符的打开文件

socket地址存放与sockaddr_in,包含AF_INETsin_port为16位端口号,sin_addr为32位ip地址,都是以大端法存放

socket函数

客户端和服务器使用socket函数创建socket描述符

1
clientfd = Socket(AF_INET, SOCK_STREAM, 0);

AF_INET表示使用32位IP地址,SOCK_STREAM表示该socket为连接端点

该函数返回的描述符为部分打开的,不能读写,将有客户端完成打开socket的工作

connect函数

客户端调用connect函数建立和服务器连接

connect函数会阻塞一直到连接成功建立或者错误,成功后clientfd为可读写状态

由getaddrinfo函数获取connect的参数

bind函数

bind函数通知内核将addr中的服务器socket地址和socket描述符socketfd联系到一起

由getaddrinfo函数获取bind的参数

listen函数

客户端是发起请求的主动实体,服务器是被动实体

默认,内核认为socket函数创建的描述符为主动socket,服务器通过listen函数通知内核描述符是被服务器使用而不是客户端

将socketfd从主动socket转化为监听socket,监听socket可以接受客户端的连接请求

参数backlog为队列中未完成的请求数量,一般设置较大的数

accept函数

服务器使用accept函数等待客户端的连接请求

已经获得监听描述符connfd,listenfd,返回一个已连接描述符,该描述符可以被Unix IO读写,可以和客户端通信,将connfd返回给客户端

与监听描述符不太,监听描述符是客户端连接请求的端点,只创建一次,存在于服务器的整个生命周期

已连接描述符为客户端和服务器直接已经建立连接的端点,服务器每次接受请求都会创建

可以理解为后端程序当前已经监听系统的某个端口,此时客户端请求后后端在该基础上创建一个线程去处理该请求

主机和服务的转换

  1. getaddrinfo 将主机名、主机地址、服务名端口号转为socket地址结构 返回result为一个addrinfo链表,每个结构对应一个host和service的socket地址结构 客户端调用之后,遍历链表,尝试每个地址,直到socket和connect成功,建立连接 服务器会遍历每个地址,直到socket和bind成功 避免内存泄露,最后调用freeaddrinfo释放链表 参数讲解跳了
  2. getnameinfo 将socket地址转换为主机和服务名字符串

socket接口辅助函数

以上接口的整合封装

  1. open_clientfd int open_clientfd(char *hostname, char *port)

open_clientfd建立和服务器的连接,返回描述符,可以直接用于Unix IO函数读写

流程:getaddrinfo,返回addrinfo链表,遍历链表尝试建立连接,失败则关闭描述符,成功后释放链表将描述符(connfd)返回给客户端

  1. open_listenfd 服务器创建监听描述符

int open_listenfd(char *port)

遍历链表直到调用socket和bind成功

echo实例

简单的单线程处理服务器

EOF概念,并不是字符,而是内核检测的一个条件,read函数返回0时,程序检测到EOF条件。磁盘文件读写,文件位置超出长度,判断EOF。网络连接,进程关闭连接其中一端,触发EOF。尝试读取通信中字节流的最后一个字节之后的字节,触发EOF。

Web服务器

Web基础

HTTP协议,Hypertext Transfer Protocol超文本传输协议

Web内容

内容是MIME,多用途的网际邮件扩充协议,类型相关的字节序列

  • 读取一个磁盘文件,将内容返回给客户端,磁盘文件为静态内容
  • 运行可执行文件,将输出返回

使用URL标识每个文件

根据目录指定返回规则

根目录为网页静态内容的根目录

后缀为/时服务器自动补全默认文件名

HTTP事务

使用Linux的Telnet和任何web服务器执行事务

1
telnet url port

HTTP请求

请求行method URI version

0个或多个请求报头

一个空的文本行终止报头列表

body

1
2
GET / HTTP/1.1  
Host: www.baidu.com

请求方法GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE

URI是相应URL的后缀,包括文件名和参数

version为http版本

Mozilla文档

header可以存放多种信息,比如cookie等

host请求头在HTTP 1.1位必须,1.0中不必须。代理缓存proxy cache会使用host报头,理解为反向代理?

客户端和服务器直接可以由代理,代理链

HTTP响应组成:

1
2
3
4
响应行
0个或多个响应头
终止报头的空行
响应主体body

响应行格式

version status-code status-message

version对于HTTP版本,code为

1
2
3
4
5
6
7
8
1**
200
301 
400
403
404
501
505

更多状态码

响应头中应有Content-Type,告知客户端主体内容的MIME类型,Content-Length,告知主体的字节大小

  1. 传递参数

URI或者请求主体 2. 服务器传递参数给子进程

fork一个子进程,调用execve在子进程上下文中执行对应的程序,通过环境变量,如QUERY_STRING, REQUEST_METHOD等信息,程序做出对应的处理

  1. 子进程的输出到哪里? CGI程序(通用网关接口Common Gateway Interface),将内容发送到标准输出,在子进程调用CGI程序之前,使用Linux dup2函数将标准输出重定向到和客户端相连的已连接描述符,CGI程序写入到标准输出的内容都会直接到达客户端

实现一个Web服务器

使用c配合CSAPP提供的封装搭建一个简易的web服务器

总结

本章主要了解了Unix如何建立一个网络连接,以及数据传输的具体过程,socket的设计巧妙之处,以及Linux一切皆文件的进一步理解。

0%