zoukankan      html  css  js  c++  java
  • 20135213——信息安全系统设计基础第十二周学习总结

    第 11 章 网络编程

    11.1 客户端-服务器编程模型

    每个网络应用都是基于客户端一服务器模型的。一个应用是由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源,并且通过操作这种资源来为它的客户端提供某种服务。
    客户端一服务器模型中的基本操作是事务,由四步组成:

    1. 当一个客户端需要服务时,它向服务器发送一个请求,发起一个事务。

    2. 服务器收到请求后,解释它,并以适当的方式操作它的资源。

    3. 服务器给客户端发送一个响应,并等待下一个请求。

    4. 客户端收到响应并处理它。

    一个客户端-服务器事务:

    11.2 网络

    客户端和服务器通常运行在不同的主机上,并且通过计算机网络的硬件和软件资源来通信。

    使用一些电缆和叫做网桥 (bridge) 的小盒子,多个以太网段可以连接成较大的局域网,称为桥接以太网 (bridged Ethernet)
    桥接以太网:

    网桥比集线器更充分地利用了电缆带宽。
    多个不兼容的局域网可以通过叫做路由器 (router)的特殊计算机连接起来,组成一个internet(互联网络)。

    Internet 和 internet 我们总是用小写字母的 internet 描述一般概念, 而用大写字母的Internet 来描述一种具体的实现,也就是所谓的全球 IP 因特网。

    11.3 全球 IP 因特网

    每台因特网主机都运行实现 TCP/IP 协议 (Transmission Control Protocol/Internet Protocol,传输控制协议/互联网络协议)的软件,几乎每个现代计算机系统都支持这个协议。

    因特网的客户端和服务器混合使用套接字接口函数和 Unix I/O 函数来进行通信。套接字函数典型地是作为会陷入内核的系统调用来实现 的,并调用各种内核模式的 TCP/IP 函数。

    11.3.1 IP地址

    一个 IP 地址就是一个 32 位无符号整数。

    htonl 函数将32 位整数由主机字节顺序转换为网络字节顺序。

    ntohl 函数将 32 位整数从网络宇节顺序转换为主机字节。

    htons和 ntohs 函数为 16 位的整数执行相应的转换。

    IP 地址通常是以一种称为点分十进制表示法来表示的。

    "n" 表示的是网络(network)。"a" 表示应用(application)。而 "to" 表示转换。

    11.3.2 因特网域名

    因特网客户端和服务器互相通信时使用的是 IP 地址。

    子树称为子域 (subdomain)。 层次结构中的第一层是 一个未命名的根节点。下一层是一组一级域名 (first-level domain name)。

    常见的第一层域名包括 com、 edu、 gov、org 和 net。

    下一层是二级 (second-level) 域名,例如 cmu. edu

    一旦一个组织得到了一个二级域名,那么它就可以在这个子域中创建任何新的域名了。

    因特网定义了域名集合和 IP 地址集合之间的映射。

    因特网应用程序通过调用 gethostbyname 和 gethostbyaddr 函数,从 DNS 数据库中检索任意的主机条目。

    • 最简单的情况下,一个域名和一个 IP 地址之间是一一映射的

    • 某些情况下,多个域名可以映射为同一个 IP 地址

    • 最通常的情况下,多个域名可以映射到多个 IP 地址

    • 某些合法的域名没有映射到任何 IP 地址

    11.3.3 因特网连接

    因特网客户端和服务器通过在连接上发送和接收字节流来通信。
    连接是点对点的。
    从数据可以同时双向流动的角度来说,它是全双工的。可靠的。

    Web 服务器通常使用端口 80,而电子邮件服务器使用端口 25。

    11.4 套接字接口

    套接字接口 (socket interface) 是一组函数,它们和 Unix I/O 函数结合起来,用以创建网络 应用。

    套接字接口描述:

    11.4.2 socket 函数

    客户端和服务器使用 socket 函数来创建一个套接字描述符

    11.4.3 connect 函数

    客户端通过调用 connect 函数来建立和服务器的连接。

    11.4.4 open_clientfd 函数

    open_clientfd 函数和运行在主机 hostname 上的服务器建立一个连接,并在知名端口 port 上监听连接请求。它返回一个打开的套接宇描述符,该描述符准备好了,可以用 Unix I/O 函数做输入和输出。

    11.4.5 bind 函数

    bind、 listen 和 accept 被服务器用来和客户端建立连接。

    bind 函数告诉内核将 my_addr中的服务器套接字地址和套接字描述符 sockfd 联系起来。参数 addrlen 就是 sizeof(sockaddr_in) 。

    11.4.6 listen 函数

    客户端是发起连接请求的主动实体。服务器是等待来自客户端的连接请求的被动实体。默认情况下,内核会认为 socket 函数创建的描述符对应于主动套接字 (active socket),它存在 于一个连接的客户端。

    listen 函数将 sockfd 从一个主动套接字转化为一个监听套接字 (listening socket),该套接字可以接受来自客户端的连接请求。

    11.4.7 open_listenfd 函数

    socket、 bind 和 listen 函数结合成一个叫做。open_listenfd 的辅助函数

    11.4.8 accept 函数

    accept 函数来等待来自客户端的连接请求

    accept 函数等待来自客户端的连接请求到达侦听描述符 listenfd,然后在 addr 中填写客户端的套接字地址,并返回一个巳连接描述符 (connected descriptor),这个描述符可被用来利用 Unix I/O 函数与客户端通信。

    监听描述符是作为客户端连接请求的一个端点。

    11.5 Web 服务器

    11.5.1 Web 基础

    Web 客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫做 HTTP (Hypertext Transfer Protocol,超文本传输协议). HTTP 是一个简单的协议。一个 Web 客户端(即浏览器) 打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容,然后关闭连接。浏览器读取这些内容,并把它显示在屏幕上。

    11.5.2 Web 内容

    每条由 Web 服务器返回的内容都是和它管理的某个文件相关联的。这些文件中的每一个都有一个唯一的名字,叫做 URL (Universal Resource Locator,通用资源定位符)。

    11.5.3 HTTP 事务

    因为 HTTP 是基于在因特网连接上传送的文本行的,我们可以使用 Unix 的TELNET程序来和因特网上的任何 Web 服务器执行事务。

    1.HTTP 请求

    一个 HTTP 请求的组成是这样的:一个请求行 (request line) (第 5 行),后面跟随零个或更多个请求报头 (request header) (第 6 行),再跟随一个空的文本行来终止报头列表

    HTTP 支持许多不同的方法,包括 GET、 POST、 OPTIONS、 HEAD、 PUT、 DELETE 和 TRACE。

    2.HTTP 晌应

    一个 HTTP 响应的组成是这样的:一个响应行 (response line) (第 8 行)后面跟随着零个或更多的响应报头 (response header) (第 9 ~ 13 行), 再跟随一个终止报头的空行(第 14 行),再跟随一个响应主体 (response body)

    状态码 (status code) 是一个三位的正整数, 指明对请求的处理。状态消息 (status message) 给出与错误代码等价的英文描述。

     

    第十二章 并发编程

    三种基本的构造并发程序的方法:

    进程

    每个逻辑控制流是一个进程,由内核进行调度,进程有独立的虚拟地址空间

    I/O多路复用

    逻辑流被模型化为状态机,所有流共享同一个地址空间

    线程

    运行在单一进程上下文中的逻辑流,由内核进行调度,共享同一个虚拟地址空间

    • 进程。每个逻辑控制流都是一个进程,由内核来调度和维护。控制流使用显式的进程间通信(IPC)机制。

    • I/O多路复用。应用程序在一个进程的上下文中显式地调度他们自己的逻辑流。所有的流都共享同一个地址空间。

    • 线程。线程是运行在一个单一进程上下文中的逻辑流,由内核进行调度。  

    12.1 基于进程的并发编程

    • 基于进程的并发服务器

    1. 使用SIGCHLD处理程序来回收僵死子进程的资源。

    2. 父进程必须关闭他们各自的connfd拷贝(已连接的描述符),避免存储器泄露。

    3. 因为套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。

    注意

    1.父进程需要关闭它的已连接描述符的拷贝(子进程也需要关闭)

    2.必须要包括一个SIGCHLD处理程序来回收僵死子进程的资源

    3.父子进程之间共享文件表,但是不共享用户地址空间。

    关于独立地址空间

    1. 优点:防止虚拟存储器被错误覆盖

    2. 缺点:开销高,共享状态信息才需要IPC机制

    12.2 基于I/O多路复用的并发编程

    使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。

    int select(int n,fd_set *fdset,NULL,NULL,NULL);
         返回已经准备好的描述符的非0的个数,若出错则为-1。

    select函数处理类型为fd_set的集合,叫做描述符集合,看做一个大小为n位的向量:

    bn-1,......,b1,b0

    对描述符集合的处理方法:

    1. 分配他们

    2. 将一个此种类型的变量赋值给另一个变量

    3. 用FD_ZERO,FD_SET,FD_CLR和FD_ISSET宏指令来修改和检查他们。

    基于I/O多路复用的并发事件驱动服务器

    1. I/O多路复用可以用作事件并发驱动程序的基础。

    2. 状态机:一组状态、输入事件、输出事件和转移。

    3. 自循环:同一输入和输出状态之间的转移。

    I/O多路复用技术的优劣

    • 相比基于进程的设计给了程序员更多的对进程行为的控制,运行在单一进程上下文中,每个逻辑流都能访问全部的地址空间,在流之间共享数据很容易。

    • 编码复杂,随着并发粒度的减小,复杂性还会上升。粒度:每个逻辑流每个时间片执行的指令数量。

    12.3 基于线程的并发编程

    线程执行模型

    每个进程开始生命周期时都是单一线程(主线程),在某一时刻创建一个对等线程,从此开始并发地运行,最后,因为主线程执行一个慢速系统调用,或者被中断,控制就会通过上下文切换传递到对等线程。

    Posix线程

    Posix线程是C语言中处理线程的一个标准接口,允许程序创建、杀死和回收线程,与对等线程安全的共享数据。

    线程的代码和本地数据被封装在一个线程例程中,

    创建线程

    线程通过调用pthread_create来创建其他线程。

    int pthread_create(pthread_t *tid,pthread_attr_t *attr,func *f,void *arg);
                                                    成功则返回0,出错则为非零

    当函数返回时,参数tid包含新创建的线程的ID,新线程可以通过调用pthread_self函数来获得自己的线程ID。

    pthread_t pthread_self(void);返回调用者的线程ID。

    终止线程

    一个线程是通过以下方式之一来终止的。

    当顶层的线程例程返回时,线程会隐式地终止。
    通过调用pthread_exit函数,线程会显式地终止

    void pthread_exit(void *thread_return);

    回收已终止的线程资源

    线程通过调用pthread_join函数等待其他线程终止。

    int pthread_join(pthread_t tid,void **thread_return);
                                               成功则返回0,出错则为非零

    分离线程

    在任何一个时间点上,线程是可结合或可分离的。一个可结合的线程能够被其他线程收回其资源和杀死,在被回收之前,它的存储器资源是没有被释放的。分离的线程则相反,资源在其终止时自动释放。

    int pthread_deacth(pthread_t tid);
                                            成功则返回0,出错则为非零

    初始化线程

    pthread_once允许初始化与线程例程相关的状态。

    pthread_once_t once_control=PTHREAD_ONCE_INIT;
    int pthread_once(pthread_once_t *once_control,void (*init_routine)(void));
                                                        总是返回0

    12.4 多线程程序中的共享变量

    一个变量是共享的,当且仅当多个线程引用这个变量的某个实例。

    一、线程存储器模型

    1. 每个线程都有自己独立的线程上下文,包括一个唯一的整数线程ID,栈、栈指针、程序计数器、通用目的寄存器和条件码。

    2. 寄存器是从不共享的,而虚拟存储器总是共享的。

    3. 各自独立的线程栈被保存在虚拟地址空间的栈区域中,并且通常是被相应的线程独立地访问的。

    二、将变量映射到存储器

    1. 全局变量:定义在函数之外的变量

    2. 本地自动变量:定义在函数内部但是没有static属性的变量。

    • 本地静态变量:定义在函数内部并有static属性的变量。

    三、共享变量
    变量v是共享的——当且仅当它的一个实例被一个以上的线程引用。

    12.5用信号量同步线程

    进度图是将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线,原点对应于没有任何线程完成一条指令的初始状态。

    当n=2时,状态比较简单,是比较熟悉的二维坐标图,横纵坐标各代表一个线程,而转换被表示为有向边。

    转换规则:

    • 合法的转换是向右或者向上,即某一个线程中的一条指令完成
    • 两条指令不能在同一时刻完成,即不允许出现对角线
    • 程序不能反向运行,即不能出现向下或向左

    而一个程序的执行历史被模型化为状态空间中的一条轨迹线

    信号量

    1. P(s):如果s是非零的,那么P将s减一,并且立即返回。如果s为零,那么就挂起这个线程,直到s变为非零。

    2. V(s):将s加一,如果有任何线程阻塞在P操作等待s变为非零,那么V操作会重启线程中的一个,然后该线程将s减一,完成他的P操作。
      信号量不变性:一个正确初始化了的信号量有一个负值。

    信号量操作函数:

    int sem_init(sem_t *sem,0,unsigned int value);//将信号量初始化为value
    int sem_wait(sem_t *s);//P(s)
    int sem_post(sem_t *s);//V(s)

    使用信号量来实现互斥

    • 二元信号量(互斥锁):将每个共享变量与一个信号量s联系起来,然后用P(s)(加锁)和V(s)(解锁)操作将相应的临界区包围起来。

    • 禁止区:s<0,因为信号量的不变性,没有实际可行的轨迹线能够直接接触不安全区的部分

    12.6 使用线程来提高并行性

    并行程序的加速比通常定义为:
    Sp=T1/Tp

    其中,p为处理器核的数量,T为在p个核上的运行时间

    12.7 其他并发问题

    线程安全性

    一个线程是安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。

    四个不相交的线程不安全函数类以及应对措施:

    不保护共享变量的函数——用P和V这样的同步操作保护共享变量
    保持跨越多个调用的状态的函数——重写,不用任何static数据。
    返回指向静态变量的指针的函数——①重写;②使用加锁-拷贝技术。
    调用线程不安全函数的函数——参考之前三种

    可重入性

    当它们被多个线程调用时,不会引用任何共享数据。

    1.显式可重入的:
    所有函数参数都是传值传递,没有指针,并且所有的数据引用都是本地的自动栈变量,没有引用静态或全剧变量。

    2.隐式可重入的:
    调用线程小心的传递指向非共享数据的指针。

    三、在线程化的程序中使用已存在的库函数
    一句话,就是使用线程不安全函数的可重入版本,名字以_r为后缀结尾。

    竞争

    1.竞争发生的原因:
    一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点。也就是说,程序员假定线程会按照某种特殊的轨迹穿过执行状态空间,忘了一条准则规定:线程化的程序必须对任何可行的轨迹线都正确工作。

    2.消除方法:
    动态的为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针

    死锁:

    1.定义

    一组线程被阻塞了,等待一个永远也不会为真的条件。

    2.解决死锁的方法

    a.不让死锁发生:
    • 静态策略:设计合适的资源分配算法,不让死锁发生---死锁预防
    • 动态策略:进程在申请资源时,系统审查是否会产生死锁,若会产生死锁则不分配---死锁避免
    b.让死锁发生:

    进程申请资源时不进行限制,系统定期或者不定期检测是否有死锁发生,当检测到时解决死锁----死锁检测与解除

  • 相关阅读:
    8月18号心得
    题解
    考试题
    1055心得
    1055解
    1055题
    心得
    考试三道题
    2017.8.1 居然是倒数第二天了……
    1055
  • 原文地址:https://www.cnblogs.com/20135213lhj/p/5024801.html
Copyright © 2011-2022 走看看