20145320《信息安全系统设计基础》第十三周学习总结

第 11 章 网络编程

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

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

  • 当一个客户端须要服务时,它向服务器发送一个请求,发起一个事务。web

  • 服务器收到请求后,解释它,并以适当的方式操做它的资源。算法

  • 服务器给客户端发送一个响应,并等待下一个请求。数据库

  • 客户端收到响应并处理它。编程

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

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 地址。

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

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

  • 一旦一个组织获得了一个二级域名,那么它就能够在这个子域中建立任何新的域名了。

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

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

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)

第十二章 并发编程

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

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

    • I/O多路复用

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

    • 线程

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

12.1 基于进程的并发编程

  • 基于进程的并发服务器

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

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

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

  • 注意

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

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

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

  • 关于独立地址空间

    优势:防止虚拟存储器被错误覆盖

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

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

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

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_contro,
void (*init_routine)(void));
老是返回0

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

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

  • 1、线程存储器模型

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

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

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

  • 2、将变量映射到存储器

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

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

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

  • 3、共享变量

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

12.5用信号量同步线程

  • 转换规则:

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

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

  • 信号量

    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.隐式可重入的:

调用线程当心的传递指向非共享数据的指针。

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

  • 竞争

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

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

  • 死锁:

1.定义

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

2.解决死锁的方法
a.不让死锁发生:

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

b.让死锁发生:

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

代码运行

  • hello_multi.c

先打印world换行打印hello,间隔1秒再打印相同内容,一共打印5次,最后输出t1,t2 finished

  • hello_multi1.c

  • hello_single.c

打印一个hello,以后每间隔1秒打印一 个hello,共5个;而后打印一个world并换行,以后每间隔1秒打印一个world,共5个

  • incprint.c

在屏幕上换行输出count=1,2,3,4,5,间隔1秒

  • twordcount1.c

统计两个file的words数

  • twordcount2.c

引入pthread_mutex_t,统计两个file的words数

  • twordcount3.c

先统计两个file各自的words数,再统计两个file的总words数

  • twordcount4.c

先输出两个file各自的信息和words数,再统计两个file的总words数

  • condvar.c

消费者等待生产者产出产品后才打印,不然消费者阻塞等待生产者生产。

  • count.c

建立两个线程共享同一变量,都实现加一操做

  • countwithmutex.c

引入互斥锁(Mutex)解决访问冲突的问题

  • createthread.c

打印进程和线程ID

  • share.c

得到线程的终止状态

  • threadexit.c

退出并输出线程的状态

相关文章
相关标签/搜索