【操作系统】网络编程
承接Unix I/O这一章节,了解网络如何构建以及客户端和服务器如何通信,socket函数相关内容的学习
CSAPP Ch-11 笔记
CS编程模型
客户端服务器模型
一个服务器进程,多个客户端进程
基本操作是事务transaction
有四步
- 客户端向服务器发送请求,发起一个事务
- 服务器解释请求,用响应的方式操作资源
- 服务器返回响应,等待下一个请求
- 客户端接受响应并处理
网络
网络是一种IO设备,是数据源和数据接收方
网络数据接收后经IO和内存总线复制到内存,双向传输
以太网Ethernet是局域网技术
电缆链接主机和集线器,集线器广播每个端口的信息到所有主机
主机发送帧frame开头的信息,包括header,随后是有效载荷,payload,网内每个主机都可以见,只有目的主机读取
桥接以太网,主机…-集线器-桥-桥-集线器-…主机,网桥会根据通信需要来选择是否转发帧到其他网桥
连接多个局域网使用路由器
协议解决网络传输中的差异,提供命名机制(网络地址)和传输机制(包头+有效载荷)
数据发送过程:
- 主机A通过系统调用从虚存加载数据到内核缓冲区
- 主机A上的协议软件添加互联网包头和接口帧头,互联网包头指向主机B,接口帧头指向主机LAN1。是封装关系。 帧头的包数据为互联网包头以及数据,帧头为其自己的互联网包头
- LAN1适配器复制数据到网络
- 路由器读取数据猴传送到协议软件
- 协议软件读取目标互联网地址,作为路由表的索引转发到对应的LAN2适配器
- LAN2适配器复制帧到网络
- 到达主机B,从适配器读取帧,传输到协议软件
- 协议软件去除包头和帧头,读取数据,主机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查看主机地址
|
|
域名
一级域名mil,edu,gov,com,cn
二级域名whut.edu
hosts文件手工维护ip和域名的映射
DNS,domain name system
Linux使用nslookup查看域名ip
|
|
通常域名和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_INET
,sin_port
为16位端口号,sin_addr
为32位ip地址,都是以大端法存放
socket函数
客户端和服务器使用socket函数创建socket描述符
|
|
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返回给客户端
与监听描述符不太,监听描述符是客户端连接请求的端点,只创建一次,存在于服务器的整个生命周期
已连接描述符为客户端和服务器直接已经建立连接的端点,服务器每次接受请求都会创建
可以理解为后端程序当前已经监听系统的某个端口,此时客户端请求后后端在该基础上创建一个线程去处理该请求
主机和服务的转换
getaddrinfo
将主机名、主机地址、服务名端口号转为socket地址结构 返回result为一个addrinfo链表,每个结构对应一个host和service的socket地址结构 客户端调用之后,遍历链表,尝试每个地址,直到socket和connect成功,建立连接 服务器会遍历每个地址,直到socket和bind成功 避免内存泄露,最后调用freeaddrinfo释放链表 参数讲解跳了getnameinfo
将socket地址转换为主机和服务名字符串
socket接口辅助函数
以上接口的整合封装
- open_clientfd
int open_clientfd(char *hostname, char *port)
open_clientfd建立和服务器的连接,返回描述符,可以直接用于Unix IO函数读写
流程:getaddrinfo,返回addrinfo链表,遍历链表尝试建立连接,失败则关闭描述符,成功后释放链表将描述符(connfd)返回给客户端
- 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服务器执行事务
|
|
HTTP请求
请求行method URI version
0个或多个请求报头
一个空的文本行终止报头列表
body
|
|
请求方法GET、POST、OPTIONS、HEAD、PUT、DELETE、TRACE
URI是相应URL的后缀,包括文件名和参数
version为http版本
header可以存放多种信息,比如cookie等
host请求头在HTTP 1.1位必须,1.0中不必须。代理缓存proxy cache会使用host报头,理解为反向代理?
客户端和服务器直接可以由代理,代理链
HTTP响应组成:
|
|
响应行格式
version status-code status-message
version对于HTTP版本,code为
|
|
响应头中应有Content-Type,告知客户端主体内容的MIME类型,Content-Length,告知主体的字节大小
- 传递参数
URI或者请求主体 2. 服务器传递参数给子进程
fork一个子进程,调用execve在子进程上下文中执行对应的程序,通过环境变量,如QUERY_STRING, REQUEST_METHOD等信息,程序做出对应的处理
- 子进程的输出到哪里? CGI程序(通用网关接口Common Gateway Interface),将内容发送到标准输出,在子进程调用CGI程序之前,使用Linux dup2函数将标准输出重定向到和客户端相连的已连接描述符,CGI程序写入到标准输出的内容都会直接到达客户端
实现一个Web服务器
使用c配合CSAPP提供的封装搭建一个简易的web服务器
总结
本章主要了解了Unix如何建立一个网络连接,以及数据传输的具体过程,socket的设计巧妙之处,以及Linux一切皆文件的进一步理解。