网络编程
客户端-服务器编程模型
- 每个网络应用都是基于客户端-服务器模型。
- 一个应用是由一个服务器进程和一个或者多个客户端进程组成。
- 服务器管理某种资源,并通过操作资源来为客户端提供某种服务。
- 基本操作是事务。
- 四个步骤:
- 当客户端需要服务时,向服务器发送请求,发起一个事务。
- 服务器收到请求后,解释它,并以适当的方式操作它的资源。
- 服务器给客户端发送一个响应,并等待下一个请求。
客户端收到响应并处理它。
网络
- 四个步骤:
- 客户端和服务器通常运行在不同的话主机上,并且通过计算机网络的硬件和软件资源来通信。
- 网络的层次系统的最低层时LAN(局域网)
- 其技术为以太网
- 每个以太网适配器都有一个全球唯一的48未地址。
- 运行在每台主机和路由器上的协议软件可以解决不兼容的问题。
- 两种基本能力:
- 命名机制
- 传送机制
- 两种基本能力:
- 主机和路由器如何使用互联网协议在不兼容的局域网间传送数据的示例与步骤。(p617)
全球IP因特网
- 每台因特网主机都运行实现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的缩写。
- 以下函数刘念老师的课上都说明过,仅作记录
- socket函数 p626
- connect函数 p626
- open_clientfd函数 p627
- bind函数 p628
- listen函数 p628
- open_listenfd函数 p628
- accept函数 p629
Web服务器
- Web服务器使用HTTP协议和它们的客户端(浏览器等)彼此通信。
- 浏览器向服务器请求静态或者动态的内容
- 对静态内容的请求是通过从服务器磁盘取得文件并把它返回给客户端来服务的。
- 对动态内容的请求时通过在服务器上一个子进程的上下文中运行一个程序并将它的输出返回给客户端来服务的。
- CGI标准提供了一组规则,来管理客户端如何将程序参数传递给服务器。服务器如何将这些参数以及其他信息传递给子进程,以及子进程如何将它的输出发送回客户端。
并发编程
- 使用应用级并发的应用程序称为并发程序。
- 现代操作系统提供了三种基本的构造并发程序的方法:
- 进程
- I/O多路复用
- 线程
关于进程的优劣
- 对于在父、子进程间共享状态信息,进程有一个非常清晰的模型:共享文件表,但是不共享用户地址空间。
- 独立的抵制空间使得进程共享状态信息变得更加困难。为了共享信息,它们必须使用显式的IPC(进程间通信)机制。因此它们往往比较慢。
基于I/O多路复用的并发编程
- I/O多路复用技术
- 使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才返回控制给应用。
- select函数处理类型为fd_set的集合,也叫做描述符集合。
- select函数有两个输入
- 读集合
- 读集合的基数n(实际上是任何描述符集合的最大技术)。
- select函数会一直阻塞,直到读集合中至少有一个描述符准备好可以读。
- 当且仅当一个从该描述符读取一个字节的请求不会阻塞时,描述符k就表示准备好可以读了。
- select函数的返回值指明了准备好集合的基数。
- 我们必须每次调用select时都更新读集合。
基于I/O多路复用的并发事件驱动服务器
- 一个状态机就是一组状态、输入事件和转移
- 转移就是将状态和输入事件映射到状态。
- 自循环是同一个输入和输出状态之间的转移。
- 服务器使用I/O多路复用,借助select函数检测输入事件的发生。
- 霍东阁客户端的集合维护在一个pool结构中。
- 通过调用init_pool初始化池之后,服务器进入一个无限循环。
- 在循环的每次迭代中,服务器调用selct函数来检测两种不同类型的输入事件:
- 来自一个新客户端的连接请求到达
- 一个已存在的客户端的已连接描述符准备号可以读了。
- 当一个连接请求到达时,服务器打开连接,并调用add_client函数,将该客户端添加到池里。
- 最后服务器调用check_clients函数,把来自每个准备好的已连接描述符的一个文本行回送回去。
基于线程的并发编程
- 线程就是运行在进程上下文中的逻辑流。
- 程序都是由每个进程中一个现成组成的 。
- 每个现成都有自己的线程上下文,包括一个唯一的整数线程IP、栈、栈指针、程序计数器、通用目的寄存器和条件码。
所有的运行在一个进程里的线程共享该流程的整个虚拟地址空间。
线程执行模型
- 每个进程开始生命周期时都是单一线程,这个线程称为主线程。
- 在某个时刻,主线程创建一个对等线程,从这个时间点开始,两个线程就并发地运行。
因为主线程执行一个慢速系统调用,控制就会通过上下文切换传递到对等线程。
Posix线程
- Posix线程是在C程序中处理线程的一个标准接口。
- 线程的代码和本地数据被封装在一个线程例程中。
- 每个线程例程都以一个通用指针作为输入,并返回一个通用指针。
- 本地变量tid,可以用来存放对等线程的现成ID。
- 主线程通过调用pthread_create函数创建一个新的对等线程。
- 通过嗲用pthread_join,主线程等待对等线程终止。
- 主线程调用exit,终止当时运行在这个进程中的所有线程。
终止线程
- 一个线程是以下之一来终止的:
- 当顶层的线程例程返回时,线程会隐式地终止。
- 通过调用pthread_exit函数,线程会显示地终止。
- 如果主线程调用thread_exit,它会等待所有其他对等线程终止,然后再终止主线程和整个进程,返回值为thread_return。
- 某个对等线程嗲用Unix地exit函数,该函数终止进程以及所有与该进程相关的线程。
- 另一个对等线程通过以当前线程ID作为参数调用pthread_cancle函数来终止当前线程。
分离线程
- 在任何一个时间点上,线程是可结合地或者是分离的。
- pthread函数分离可结合线程tid。
- 线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们自己。
初始化线程
- pthread_once函数允许你初始化与线程例程相关的状态。
- once_control变量是一个全局或者静态变量,总是初始化为PTHREAD_ONCE_INIT。
将变量映射到存储器
- 线程化的C程序中变量根据它们的存储类型被映射到虚拟存储器:
- 全局变量
- 本地自动变量
- 本地静态变量
共享变量
- 一个变量可共享时,当且仅当它的一个实例被一个以上的线程引用。
- 将线程的循环代码分解成五个部分:
- Hi:循环头部的指令块
- Li:加载cnt
- Ui:更新cnt
- Si:存储cnt
- Ti:尾部的指令块
信号量 p668
- 程序通过调用sem_wait和sem_post函数来执行P和V操作。
- P中测试和减1操作是不分割旳。
线程安全
- 不保护共享变量的函数
- 保护跨越多个调用的状态的函数
- 返回指向静态变量指针的函数
调用线程不安全函数的函数
可重入性
可重入函数的特点在于当它们被多个线程调用时,不会引用任何共享数据。
竞争
- 当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点时,就会发生竞争。
死锁
- 死锁指的是一组线程被阻塞了,等待一个永远也不会为真的条件。
参考资料
- 《Computer.Systems.A.Programmer's.Perspective.2nd.CN》教材
- 《LINUX 内核中并发机制》http://blog.chinaunix.net/uid-20768928-id-3399523.html
体会
11章因为已经在javaweb课上学过了,所有比较轻松,相当于一次复习。但是12章的内容虽然觉得挺有意思的,但是依然有很多地方似懂非懂,而且内容好多啊。需要进一步消化。