《信息安全系统设计基础》第13周学习总结
20145336 《信息安全系统设计基础》第十三周学习总结
教材学习内容总结
网络编程
客户端-服务器编程模型
- 一个应用是由一个服务器进程和一个或多个客户端进程组成。
- 服务器管理某种资源,并且通过操作这种资源来为它的客户端提供某种服务。
- 基本操作是事务。
一个客户端-服务器事务由四步组成:
- 当一个客户端需要服务时,向服务器发送一个请求,发起一个事务。
- 服务器收到请求后,解释它,并以适当的方式操作它的资源。
- 服务器给客户端发送一个相应,并等待下一个请求。
- 客户端收到响应并处理它。
网络
- 物理上,网络是一个按照地理远近组成的层次系统:最低层是LAN(局域网),迄今为止,最流行的局域网技术是以太网。
-
一个以太网段包括一些电缆和集线器。每根电缆都有相同的最大位带宽,集线器不加分辩地将一个端口上收到的每个位复制到其他所有的端口上,因此每台主机都能看到每个位。每个以太网适配器都有一个全球唯一的48位地址,存储在适配器的非易失性存储器上。一台主机可以发送一段位,称为帧,到这个网段内其它任何主机。每个帧包括一些固定数量的头部位,用来标识此帧的源和目的地址以及此帧长苏,和数据位的有效载荷。每个主机都能看到这个帧,但是只有目的主机能读取。使用电缆和网桥,多个以太网段可以连接成较大的局域网,称为桥接以太网。这些电缆的带宽可以是不同的。
-
多个不兼容的局域网可以通过叫做路由器的特殊计算机连接起来,组成一个internet互联网络。
-
互联网重要特性:由采用不同技术,互不兼容的局域网和广域网组成。
-
运行在每台主机和路由器上的协议软件,消除不同网络的差异。
协议提供的两种基本能力
- 命名机制:每台主机会被分配至少一个这种互联网地址,该地址唯一的标示一台主机。
- 传送机制:定义一种把数据位捆扎成不连续的片的统一方式
全球IP因特网
因特网的客户端和服务器混合使用套接字接口函数和UnixI/O函数进行通信。
把因特网看做一个世界范围的主机集合,满足以下特性:
- 主机集合被映射为一组32位的IP地址。
- 这组IP地址被映射为一组称为因特网域名的标识符。
- 因特网主机上的进程能够通过连接和任何其他主机上的进程。
套接字接口
套接字地址结构
sin_family成员是AF_INET
sin_port成员是一个16位的端口号
sin_addr成员是一个32位的IP地址。
IP地址和端口号总时以网络字节顺序(大端法)存放的。
_in是互联网络的缩写,不是输入input的缩写。
Web服务器
- Web客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫做 HTTP (Hypertext Transfer Protocol,超文本传输协议). HTTP 是一个简单的协议。一个 Web 客户端(即浏览器) 打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容,然后关闭连接。浏览器读取这些内容,并把它显示在屏幕上。
Web 服务器以两种不同的方式向客户端提供内容:
- 取一个磁盘文件,并将它的内容返回给客户端。磁盘文件称为静态内容 (static content), 而返回文件给客户端的过程称为服务静态内容 (serving static content)。
- 运行一个可执行文件,并将它的输出返回给客户端。运行时可执行文件产生的输出称为态内容 (dynamic content),而运行程序并返回它的输出到客户端的过程称为服务动态内容 (serving dynamic content)。
CGI标准提供了一组规则,来管理客户端如何将程序参数传递给服务器。服务器如何将这些参数以及其他信息传递给子进程,以及子进程如何将它的输出发送回客户端。
并发编程
如果逻辑控制流在时间上重叠,那么他们就是并发的。
应用级并发在以下情况中发挥作用:
访问慢速I/O设备。
与人交互。
通过推迟工作以降低延迟。
服务多个网络客户端。
在多核机器上进行并行计算。
-
使用应用级并发的应用程序称为并发程序。现代操作系统提供了三种基本的构造并发程序的方法:
进程每个逻辑控制流都是一个进程,由内核来调度和维护。控制流使用显式的进程间通信(IPC)机制。 I/O多路复用应用程序在一个进程的上下文中显式地调度他们自己的逻辑流。所有的流都共享同一个地址空间。 线程线程是运行在一个单一进程上下文中的逻辑流,由内核进行调度。
基于进程的并发编程
-
基于进程的并发服务器,需注意:
-
使用SIGCHLD处理程序来回收僵死子进程的资源。
- 父进程必须关闭他们各自的connfd拷贝(已连接的描述符),避免存储器泄露。
-
因为套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。
-
在父子进程之间共享状态信息,通过共享文件表,但是不共享用户地址空间。
-
使用显式的进程间通信(IPC)机制。但开销很高,往往比较慢。
-
使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
int select(int n,fd_set *fdset,NULL,NULL,NULL); 返回已经准备好的描述符的非0的个数,若出错则为-1。
select函数处理类型为fd_set的集合,叫做描述符集合,看做一个大小为n位的向量:
bn-1,......,b1,b0。
-
I/O多路复用可以用作事件并发驱动程序的基础。
-
自循环:同一输入和输出状态之间的转移。
-
I/O多路复用技术的优点:1)它比基于进程的设计给了程序员更多的对程序行为的控制。2)相比基于进程的设计给了程序员更多的对进程行为的控制,运行在单一进程上下文中,每个逻辑流都能访问全部的地址空间,在流之间共享数据很容易。
-
I/O多路复用技术的缺点:编码复杂,随着并发粒度的减小,复杂性还会上升。粒度:每个逻辑流每个时间片执行的指令数量。
基于线程的并发编程
-
线程:运行在进程上下文中的逻辑流,由内核自动调度,有自己的线程上下文,包括一个唯一的整数线程ID,栈、栈指针、程序计数器、通用目的寄存器和条件码。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间。
-
每个进程开始生命周期时都是单一线程(主线程),在某一时刻创建一个对等线程,从此开始并发地运行,最后,因为主线程执行一个慢速系统调用,或者被中断,控制就会通过上下文切换传递到对等线程。
-
Posix线程是C语言中处理线程的一个标准接口,允许程序创建、杀死和回收线程,与对等线程安全的共享数据。
-
线程的代码和本地数据被封装在一个线程例程中。
创建线程
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。
- 一个线程是通过以下方式之一来终止的。
- 当顶层的线程例程返回时,线程会隐式地终止。
- 通过调用pthreadexit函数,线程会显式地终止 void pthreadexit(void *thread_return);
- 某个对等线程调用Unix的exit函数,该函数终止进程以及所有与该进程相关的线程。
- 另一个对等线程通过以当前线程ID作为参数调用pthread_cancle函数来终止当前进程。
include
回收已终止线程的资源
void pthread_cancle(pthread_t tid);
int pthreadjoin(pthreadt tid,void **thrad_return);
- 分离线程 在任何一个时间点上,线程是可结合或可分离的。一个可结合的线程能够被其他线程收回其资源和杀死,在被回收之前,它的存储器资源是没有被释放的。分离的线程则相反,资源在其终止时自动释放。
int pthreaddeacth(pthreadt tid); 成功则返回0,出错则为非零
- 初始化线程 pthreadonce允许初始化与线程例程相关的状态。 pthreadoncet oncecontrol=PTHREADONCEINIT; int pthreadonce(pthreadonce_t oncecontrol,void (initroutine)(void)); 总是返回0
多线程程序中的共享变量
- 一个变量是共享的。当且仅当多个线程引用这个变量的某个实例。
- 每个线程都有自己独立的线程上下文,包括一个唯一的整数线程ID,栈、栈指针、程序计数器、通用目的寄存器和条件码。
- 寄存器是从不共享的,而虚拟存储器总是共享的。
- 各自独立的线程栈被保存在虚拟地址空间的栈区域中,并且通常是被相应的线程独立地访问的。
- 将变量映射到存储器
全局变量:定义在函数之外的变量
本地自动变量:定义在函数内部但是没有static属性的变量。
本地静态变量:定义在函数内部并有static属性的变量。
- 当且仅当变量的一个实例被一个以上的线程引用时,就说变量是共享的。
用信号量同步线程
- 共享变量的同时引入了同步错误,即没有办法预测操作系统是否为线程选择一个正确的顺序。
- 进度图:将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线,将指令模型化为从一种状态到另一种状态的转换。
- 信号量
P(s):如果s是非零的,那么P将s减一,并且立即返回。如果s为零,那么就挂起这个线程,直到s变为非零。
V(s):将s加一,如果有任何线程阻塞在P操作等待s变为非零,那么V操作会重启线程中的一个,然后该线程将s减一,完成他的P操作。
- 信号量不变性:一个正确初始化了的信号量有一个负值。
- 信号量操作函数:
int seminit(semt sem,0,unsigned int value);//将信号量初始化为value int semwait(semt s);//P(s) int sempost(semt *s);//V(s)
- 使用信号量来实现互斥 二元信号量(互斥锁):将每个共享变量与一个信号量s联系起来,然后用P(s)(加锁)和V(s)(解锁)操作将相应的临界区包围起来。 禁止区:s<0,因为信号量的不变性,没有实际可行的轨迹线能够直接接触不安全区的部分
使用线程来提高并行性
- 并行程序的加速比通常定义为:Sp=T1/Tp,其中,p为处理器核的数量,T为在p个核上的运行时间。
其他并发问题
-
定义四个(不相交的)线程不安全函数类: 不保护共享变量的函数。 保持跨越多个调用状态的函数。 返回指向静态变量指针的函数。 调用线程不安全函数的函数。
-
当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达他的控制流x点时,就会发生竞争。
- 为消除竞争,我么可以动态地为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针。
- 死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
程序员使用P和V操作顺序不当,以至于两个信号量的禁止区域重叠。
重叠的禁止区域引起了一组称为死锁区域的状态。 死锁是一个相当难的问题,因为它是不可预测的。
代码调试中的问题和解决过程
hello_multi.c
线程编译时加上-lpthread
首先在屏幕上输出一个world和一个helloworld,再换行每隔一秒打印helloworld切每次换行进行三次打印,最后输出程序结束(hellot1, t2 finished )。
hello_multi1.c
线程编译时加上-lpthread
两个线程输出9个hello22,16个hello25以及100个hello99
hello_single.c
每隔一秒在屏幕上打印一个hello,总共打印5个hello一个world后换行,再打印四个world并且每个都换行
incprint.c
每隔一秒在屏幕上打印count=1,2,3,4,5
twordcount1.c
统计两个file的words数
twordcount2.c
twordcount3.c
twordcount4.c
condvar.c
mutex用于保护资源,wait函数用于等待信号,signal函数用于通知信号,wait函数中有一次对mutex的释放和重新获取操作,因此生产者和消费者并不会出现死锁。
不断循环
cp_t.c
将源文件复制到目的文件,并在其中创建num个线程
createthread.c
打印进程和线程ID主函数中先利用pthreadcreate()函数创建一个线程,接着调用printids函数(打印标识符的函数)打印主线程号,最后线程函数thrfn中打印出新建的线程号。
share.c
获得线程的终止状态
threadexit.c
输出3个线程的状态
本周代码托管截图
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第二周 | 0/0 | 1/2 | 19/20 | |
第三周 | 80/80 | 1/3 | 25/44 | |
第四周 | 110/190 | 1/4 | 23/67 | |
第五周 | 60/250 | 2/6 | 26/93 | |
第六周 | 80/330 | 2/8 | 25/118 | |
第七周 | 60/390 | 1/9 | 25/133 | |
第八周 | 0/390 | 2/11 | 22/155 | |
第九周 | 70/460 | 2/13 | 23/178 | |
第十周 | 375/835 | 2/15 | 22/200 | |
第十一周 | 880/1715 | 2/17 | 26/226 | |
第十二周 | 0/1715 | 3/20 | 25/251 | |
第十三周 | 600/2315 | 1/21 | 22/273 |