客户端-服务器编程模型,每一个网络应用都是基于客户端-服务器模型。html
一个应用是由一个服务器进程和一个或者多个客户端进程组成。linux
服务器管理某种资源,并经过操做资源来为客户端提供某种服务。git
基本操做是事务。程序员
四个步骤:编程
- 当客户端须要服务时,向服务器发送请求,发起一个事务。 - 服务器收到请求后,解释它,并以适当的方式操做它的资源。 - 服务器给客户端发送一个响应,并等待下一个请求。 - 客户端收到响应并处理它。
客户端和服务器一般运行在不一样的话主机上,而且经过计算机网络的硬件和软件资源来通讯。浏览器
网络的层次系统的最低层时LAN(局域网),其技术为以太网安全
每一个以太网适配器都有一个全球惟一的48未地址。服务器
运行在每台主机和路由器上的协议软件能够解决不兼容的问题。网络
两种基本能力:命名机制、传送机制多线程
主机和路由器如何使用互联网协议在不兼容的局域网间传送数据的示例与步骤。(p617)
每台因特网主机都运行实现TCP/IP协议的软件。
因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数来进行通讯。
TCP/IP实际是一个协议组,其中每个都提供不一样的功能。
IP地址
- htonl函数将32位整数由主机字节顺序转换成网络字节顺序。 - ntohl函数将32位整数从网络字节顺序转换成主机字节。 - htons函数和ntohs为16位的整数执行相应的转换。 - 可使用hostname -i来肯定本身主机的点分十进制地址
因特网域名
- 对于IP地址人性化的命名为域名。 - DNS域名系统 - 能够用hostinfo程序来挖掘一些DNS映射的特性。 - hostname肯定本身的主机域名。
因特网连接
- 因特网客户端和服务器经过在链接上发送和接收字节流来通讯。 - 一个套接字是链接的一个断点,每一个套接字都由相应的套接字地址,是由一个因特网地址和一个16位的整数端口组成的,用“地址:端口”来表示。 - 在Unix机器上,文件/etc/services包含一张这台机器提供的服务以及它们的知名端口号的综合列表。 - 一个连接是由它两端的套接字地址惟一肯定的,称为套接字对。
套接字接口
- sockaddr_in的16字节结构 - sin_family成员是AF_INET - sin_port成员是一个16位的端口号 - sin_addr成员是一个32位的IP地址。 - IP地址和端口号总时以网络字节顺序(大端法)存放的。 - _in是互联网络的缩写,不是输入input的缩写。
Web服务器
- Web服务器使用HTTP协议和它们的客户端(浏览器等)彼此通讯。 - 浏览器向服务器请求静态或者动态的内容 - 对静态内容的请求是经过从服务器磁盘取得文件并把它返回给客户端来服务的。 - 对动态内容的请求时经过在服务器上一个子进程的上下文中运行一个程序并将它的输出返回给客户端来服务的。
CGI标准提供了一组规则,来管理客户端如何将程序参数传递给服务器。服务器如何将这些参数以及其余信息传递给子进程,以及子进程如何将它的输出发送回客户端。
并发:逻辑控制流在时间上重叠
并发程序:使用应用级并发的应用程序称为并发程序。
三种基本的构造并发程序的方法:
- 进程,用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通讯机制。
- I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
- 线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。
构造并发服务器的天然方法就是,在父进程中接受客户端链接请求,而后建立一个新的子进程来为每一个新客户端提供服务。
由于父子进程中的已链接描述符都指向同一个文件表表项,因此父进程关闭它的已链接描述符的拷贝是相当重要的,并且由此引发的存储器泄露将最终消耗尽可用的存储器,使系统崩溃。
基于进程的并发echo服务器的重点内容:
- 须要一个SIGCHLD处理程序,来回收僵死子进程的资源。 - 父子进程必须关闭各自的connfd拷贝。对父进程尤其重要,以免存储器泄露。 - 套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的链接才会终止。
进程的模型:共享文件表,但不是共享用户地址空间。
Unix IPC是指全部容许进程和同一台主机上其余进程进行通讯的技术,包括管道、先进先出(FIFO)、系统V共享存储器,以及系统V信号量。
echo服务器必须响应两个相互独立的I/O时间: 网络客户端发起链接请求、用户在键盘上键入命令行
I/O多路复用技术的基本思路:使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
echo函数:未来自科幻段的每一行回送回去,直到客户端关闭这个连接。
状态机就是一组状态、输入事件和转移,转移就是将状态和输入时间映射到状态,自循环是同一输入和输出状态之间的转移。
事件驱动器的设计优势:
- 比基于进程的设计给了程序员更多的对程序行为的控制 - 运行在单一进程上下文中,所以,每一个逻辑流都能访问该进程的所有地址空间,使得流之间共享数据变得很容易。 - 不须要进程上下文切换来调度新的流。
缺点: 编码复杂、不能充分利用多核处理器
线程:运行在进程上下文中的逻辑流。
线程有本身的线程上下文,包括一个惟一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。全部运行在一个进程里的线程共享该进程的整个虚拟地址空间。
主线程:每一个进程开始生命周期时都是单一线程。
对等线程:某一时刻,主线程建立的对等线程 。
线程与进程的不一样:
- 线程的上下文切换要比进程的上下文切换快得多; - 和一个进程相关的线程组成一个对等池,独立于其余线程建立的线程。 - 主线程和其余线程的区别仅在于它老是进程中第一个运行的线程。
对等池的影响: 一个线程能够杀死它的任何对等线程;等待它的任意对等线程终止;
每一个对等线程都能读写相同的共享资源。
线程例程:线程的代码和本地数据被封装在一个线程例程中。每个线程例程都以一个通用指针做为输入,并返回一个通用指针。
建立线程
pthread create函数建立一个新的线程,并带着一个输入变量arg,在新线程的上下文中运行线程例程f。新线程能够经过调用pthread _self函数来得到本身的线程ID。
终止线程
- 一个线程的终止方式: 1. 当顶层的线程例程返回时,线程会隐式的终止; 2. 经过调用pthread _exit函数,线程会显示地终止。若是主线程调用pthread _exit,它会等待全部其余对等线程终止,而后再终止主线程和整个进程。
回收已终止线程的资源
pthread _join函数会阻塞,直到线程tid终止,回收已终止线程占用的全部存储器资源。pthread _join函数只能等待一个指定的线程终止。
分离线程
在任何一个时间点上,线程是可结合的或者是分离的。一个可结合的线程可以被其余线程收回其资源和杀死;一个可分离的线程是不能被其余线程回收或杀死的。它的存储器资源在它终止时有系统自动释放。
默认状况下,线程被建立成可结合的,为了不存储器漏洞,每一个可集合的线程都应该要么被其余进程显式的回收,要么经过调用pthread _detach函数被分离。
初始化线程
pthread _once函数容许初始化与线程例程相关的状态。
once _control变量是一个全局或者静态变量,老是被初始化为PTHREAD _ONCE _INIT.
每一个线程和其余线程一块儿共享进程上下文的剩余部分。包括整个用户虚拟地址空间,是由只读文本、读/写数据、堆以及全部的共享库代码和数据区域组成的。线程也共享一样的打开文件的集合。
任何线程均可以访问共享虚拟存储器的任意位置。寄存器是从不共享的,而虚拟存储器老是共享的。
将变量映射到存储器
- 全局变量:虚拟存储器的读/写区域只会包含每一个全局变量的一个实例。 - 本地自动变量:定义在函数内部但没有static属性的变量。 - 本地静态变量:定义在函数内部并有static属性的变量。
共享变量
- 变量v是共享的,当且仅当它的一个实例被一个以上的线程引用。
用信号量同步线程
- 共享变量引入了同步错误的可能性。 - 线程i的循环代码分解为五部分: Hi:在循环头部的指令块 Li:加载共享变量cnt到寄存器%eax的指令,%eax表示线程i中的寄存器%eax的值 Ui:更新(增长)%eax的指令 Si:将%eaxi的更新值存回到共享变量cnt的指令 Ti:循环尾部的指令块。
进度图
- 进度图将指令执行模式化为从一种状态到另外一种状态的转换。转换被表示为一条从一点到相邻点的有向边。合法的转换是向右或者向上。 - 临界区:对于线程i,操做共享变量`cnt`内容的指令构成了一个临界区。 - 互斥的访问:确保每一个线程在执行它的临界区中的指令时,拥有对共享变量的互斥的访问。 - 安全轨迹线:绕开不安全区的轨迹线 不安全轨迹线:接触到任何不安全区的轨迹线就叫作不安全轨迹线 - 任何安全轨迹线都能正确的更新共享计数器。
使用信号量来实现互斥
二元信号量:将每一个共享变量与一个信号量s联系起来,而后用P(S)和V(s)操做将这种临界区包围起来,这种方式来保护共享变量的信号量。
互斥锁:以提供互斥为目的的二元信号量
加锁:一个互斥锁上执行P操做称为对互斥锁加锁,执行V操做称为对互斥锁解锁。对一个互斥锁加了锁但尚未解锁的线程称为占用了这个互斥锁。
计数信号量:一个呗用做一组可用资源的计数器的信号量
竞争
- 竞争:当一个程序的正确性依赖于一个线程要在另外一个线程到达y点以前到达它的控制流中的x点时,就会发生竞争。 - 线程化的程序必须对任何可行的轨迹线都正确工做。 - 消除方法:动态的为每一个整数ID分配一个独立的块,而且传递给线程例程一个指向这个块的指针
死锁
- 死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
- 程序员使用P和V操做不当,以致于两个信号量的禁止区域重叠。
- 重叠的禁止区域引发了一组称为死锁区域的状态。
- 死锁是不可预测的。
注意:本章代码是多线程编译,pthread
库不是linux
系统默认的库,所以在编译的时候须要加上-lpthread
参数。
PTHREAD_MUTEX_INITIALIZER
来静态初始化互斥锁。先建立tidA
线程后运行doit函数
,利用互斥锁锁定资源,进行计数,执行完毕后解锁。后建立tidB
,与tidA
交替执行。因为定义的NLOOP值为5000,因此程序最后的输出值为10000.程序的最后还须要分别回收tidA
和tidB
的资源。
mmap函数void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
将一个文件或者其余对象映射进内存。文件被映射到多个页上,若是文件的大小不是全部页的大小之和,最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中做用很大。
成功执行时,mmap()
返回被映射区的指针,munmap()
返回0.失败时,mmap()
返回MAP_FAILED
,munmap
返回-1.
pthread_create()
函数的使用,用来打印进程和线程的IDsem_init函数sem_init(sem_t *sem, int pshared, umsigned int value);
函数初始化一个定位在sem的匿名信号量;pshared参数为0指明信号量是由进程内线程共享,若为非0值则信号量在进程之间共享;value参数指定信号量的初始值。
sem_init()成功时返回0;错误时返回-1,并把errno设置为合适的值。
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | 掌握核心的linux命令,了解了linux操做系统 |
第二周 | 300/500 | 2/4 | 18/38 | 学会了虚拟机上的C编程 |
第三周 | 500/1000 | 3/7 | 22/60 | 初步学习计算机中各类数的表示和运算 |
第五周 | 300/1300 | 2/9 | 30/90 | 经过学习汇编,了解逆向的思想应用 |
第六周 | 300/1500 | 2/11 | 28/110 | 安装了Y86处理器,了解了ISA抽象 |
第七周 | 150/1700 | 2/13 | 30/130 | 学习了存储器的体系结构| |
第八周 | 200/1900 | 2/15 | 30/150 | 复习前7章知识 |
第九周 | 340/2100 | 1/17 | 31/170 | 学习了系统级I/O |
第十周 | 599/2700 | 1/19 | 33/200 | 学习了Linux命令 |
第十一周 | 1016/3300 | 1/21 | 35/240 | 学习了异常控制流 |
第十二周 | 300/3500 | 3/25 | 30/280 | 复习前三周代码知识点和实验 |
第十三周 | 300/4000 | 2/26 | 34/320 | 学习了网络编程和并发编程 |