教材学习内容总结
第十一章 网络编程
客户端-服务器编程模型
每个网络应用都是基于客户端-服务器模型。
一个应用是由一个服务器进程和一个或者多个客户端进程组成。
服务器管理某种资源,并通过操作资源来为客户端提供某种服务。
基本操作是事务。
四个步骤:
当客户端需要服务时,向服务器发送请求,发起一个事务。
服务器收到请求后,解释它,并以适当的方式操作它的资源。
服务器给客户端发送一个响应,并等待下一个请求。
客户端收到响应并处理它。
网络
客户端和服务器通常运行在不同的话主机上,并且通过计算机网络的硬件和软件资源来通信。
网络的层次系统的最低层时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的缩写。
Web服务器
Web服务器使用HTTP协议和它们的客户端(浏览器等)彼此通信。
浏览器向服务器请求静态或者动态的内容
对静态内容的请求是通过从服务器磁盘取得文件并把它返回给客户端来服务的。
对动态内容的请求时通过在服务器上一个子进程的上下文中运行一个程序并将它的输出返回给客户端来服务的。
CGI标准提供了一组规则,来管理客户端如何将程序参数传递给服务器。服务器如何将这些参数以及其他信息传递给子进程,以及子进程如何将它的输出发送回客户端。
第十二章 并发编程
第一节 基于进程的并发编程
构造并发程序常用函数:
fork
exec
waitpid
独立地址空间
1.优点:防止虚拟存储器被错误覆盖
2.缺点:开销有时会很高,共享状态信息才需要IPC机制
第二节 基于I/O多路复用的并发编程
事件驱动程序:将逻辑流模型化为状态机。
状态机:状态、输入事件、转移
流程:
1.select函数检测到输入事件
2.add_client函数创建新状态机
3.check_clients函数执行状态转移,并且完成时删除该状态机。
相关函数:
init_pool()
add_client()
check_clients()
第三节 基于线程的并发编程
一、线程执行模型
1.主线程
2.对等线程
3.主线程切换到对等线程
二、Posix线程
Posix线程是C程序中处理线程的一个标准接口。
万能函数:
void func(void parameter)
typedef void (uf)(void para)
三、创建线程
1.创建线程:
pthread_create函数
#include <pthread.h>
typedef void *(func)(void *);
int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
功能:创建一个新的线程,带着一个输入变量arg,在新线程的上下文运行线程例程f。
2.查看线程ID
pthread_self函数
#include <pthread.h>
pthread_t pthread_self(void);
功能:返回调用者的线程ID(TID)
四、终止线程
1.终止线程的方式:隐式终止、显示终止
2.pthread_exit函数
#include <pthread.h>
void pthread_exit(void *thread_return);
3.pthread_cancle函数
#include <pthread.h>
void pthread_cancle(pthread_t tid);
五、回收已终止线程的资源
pthread_join函数:
#include <pthread.h>
int pthread_join(pthread_t tid,void **thrad_return);
六、分离线程
pthread_detach函数
#include <pthread.h>
void pthread_detach(pthread_t tid);
功能:分离可结合线程tid。
七、初始化线程
pthread_once函数
#include <pthread.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
第四节 多线程程序中的共享变量
一、线程存储器模型
二、将变量映射到存储器
三、共享变量
第五节 用信号量同步线程
一、进度图
进度图转换规则:
合法的转换是向右或者向上,即某一个线程中的一条指令完成
两条指令不能在同一时刻完成,即不允许出现对角线
程序不能反向运行,即不能出现向下或向左
而一个程序的执行历史被模型化为状态空间中的一条轨迹线。
线程循环代码的分解:
H:在循环头部的指令块
L:加载共享变量cnt到线程i中寄存器%eax的指令。
U:更新(增加)%eax的指令
S:将%eax的更新值存回到共享变量cnt的指令
T:循环尾部的指令块
临界区使用原则:有空让进、无空等待、多中择一、让权等待
二、信号量
信号量定义:
type semaphore=record
count: integer;
queue: list of process
end;
var s:semaphore;
定义对信号量的两个原子操作——P和V
P(wait)
wait(s)
s.count :=s.count-1;
if s.count<0 then
begin
进程阻塞;
进程进入s.queue队列;
end;
V(signal)
signal(s)
s.count :=s.count+1;
if s.count ≤0 then
begin
唤醒队首进程;
将进程从s.queue阻塞队列中移出;
end;
【每个信号量在使用前必须初始化】
三、使用信号量来实现互斥
第七节 并发问题
一、线程安全性
四个不相交的线程不安全函数类以及应对措施:
不保护共享变量的函数——用P和V这样的同步操作
保持跨越多个调用的状态的函数——重写
返回指向静态变量的指针的函数——①重写;②使用加锁-拷贝技术。
二、可重入性
1.显式可重入的
所有函数参数都是传值传递,没有指针,并且所有的数据引用都是本地的自动栈变量,没有引用静态或全剧变量。
2.隐式可重入的
调用线程小心的传递指向非共享数据的指针。
三、竞争
竞争发生的原因:
一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点。也就是说,程序员假定线程会按照某种特殊的轨迹穿过执行状态空间,忘了一条准则规定:线程化的程序必须对任何可行的轨迹线都正确工作。
消除方法:
动态的为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针
四、死锁
一组线程被阻塞了,等待一个永远也不会为真的条件。
代码托管
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 30/30 | |
第二周 | 300/500 | 1/3 | 20/50 | |
第三周 | 100/600 | 1/4 | 20/70 | |
第五周 | 300/900 | 1/5 | 30/100 | |
第六周 | 136/1036 | 1/6 | 20/120 | |
第七周 | 124/1160 | 1/7 | 20/140 | |
第八周 | 0/1160 | 3/10 | 20/160 | |
第九周 | 338/1498 | 3/13 | 25/185 | |
第十周 | 505/2003 | 2/15 | 25/210 | |
第十一周 | 1079/3082 | 1/16 | 30/240 | |
第十二周 | 15/3097 | 2/18 | 10/250 | |
第十三周 | 1025/4122 | 1/19 | 10/260 |