20145238 《信息安全系统设计基础》第十三周学习总结
教材学习内容总结
第十一章 网络编程
1.客户端-服务器编程模型
-
每个网络应用都是基于客户端-服务器模型的。
-
客户端-服务器模型中的基本操作是事务。
2.网络
-
对于主机,网络是一种I/O设备,作为数据源和数据接收方。
-
物理上而言,网络是一个按照地理远近组成的层次系统。最底层是LAN。
-
一个以太网段,包括电缆和集线器;每根电缆都有相同的最大位带宽;集线器不加分辩地将一个端口上收到的每个位复制到其他所有的端口上。因此,每台主机都能看到每个位。
使用电缆和网桥,多个以太网段可以连接成较大的局域网,称为桥接以太网。这些电缆的带宽可以是不同的。
-
互联网络如何实现跨过不兼容发送数据?
协议。
具备两种基本能力:命名机制、传送机制。
3.全球IP因特网
-
TCP/IP协议(协议族)。
-
因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数进行通信。
** IP地址**
-
一个IP地址就是一个32位无符号整数。
-
IP地址通常以点分十进制表示法来表示。
3.2 因特网域名
- 因特网应用程序通过调用gethostbyname函数和gethostbyaddr函数,从DNS数据库中检索任意的主机条目。
gethostbyname函数:返回和域名name相关的主机条目。
gethostbyaddr函数:返回和IP地址相关联的主机条目。
3.3 因特网链接
-
套接字是连接的端点。
-
每个套接字都有相应的套接字地址,由一个因特网地址和一个16位的整数端口组成的,用“地址:端口”来表示。
-
一个连接是由它两端的套接字地址惟一确定的。这对套接字地址叫做套接字对。
4.套接字接口
-
套接字接口是一组用来结合unit I/O函数创建网络应用的函数。
4.1套接字地址结构 -
从unit内核的角度来看,套接字就是通信的端点;从unix程序的角度来看,套接字就是一个有相应描述符的打开文件。
4.2 函数 -
sockte函数
创建一个套接字描述符。 -
connect函数
建立和服务器的连接。 -
open_clientfd函数
将socket和connect函数包装而成。客户端可以用它来和服务器建立连接。 -
bind函数
-
listen函数
-
accept函数
均被服务器用于和客户端建立连接。 -
open_listenfd函数
socket、bind和listen函数结合。用于服务器创建一个监听描述符。
5.Web服务器
5.1Web基础
- 客户端和服务器之间一个交互用的是基于文本的应用级协议——HTTP
- Web服务和常规文件检索服务区别
Web内容可以用一个叫做HTML的语言来编写。
5.2Web内容 - 以两种不同方式向客户端提供内容
服务静态内容
服务动态内容
5.3HTTP事务
响应
- HTTP请求(方法:GET POST OPTIONS HEAD PUT DELETE TRACE)
- HTTP响应
5.4服务动态内容 - 客户端如何将程序参数传递给服务器
- 服务器如何将参数传递给子进程
- 服务器如何将其他信息传递给子进程
- 子进程将它的输出发送到那儿
第十二章 并发编程
- 现在操作系统提供了三种基本的构造并发程序的方法:
进程。每个逻辑控制流都是一个进程,由内核来调度和维护。
I/O多路复用。
线程。
一、基于进程的并发编程 - 在接受连接请求之后,服务器派生出一个子进程,这个子进程获得服务器描述表完整的拷贝。子进程关闭它的拷贝中监听描述符3,父进程关闭它的已连接描述符4的拷贝,因为不需要这些描述符了。
- 通常服务器会运行很长时间,所以需要一个SIGCHLD处理程序,来回收僵死进程。因为当SIGCHLD执行时,信号是阻塞的,而UNIX信号是不排队的,所以SIGCHLD必须准备好回收多个僵死进程。
- 循环中的父进程和子进程关闭各自需要关闭的描述符。
- 进程能够共享文件表,但不共享用户地址空间。
二、基于I/O多路复用的并发编程
- 面对困境——服务器必须响应两个互相独立的I/O事件:
1)网络客户端发起的连接请求
2)用户在键盘上键入的命令 ,解决的办法是I/O多路复用技术。基本思想是,使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序
使用select函数的过程如下:
第一步,初始化fd_set集,19~22行;
第二步,调用select,25行;
第三步,根据fd_set集合现在的值,判断是哪种I/O事件,26~31行。
2.1基于I/O多路复用的并发事件驱动服务器
-
I/O多路复用可以用做并发事件驱动程序的基础,在事件驱动程序中,流是因为某种事件而前进的,一般概念是把逻辑流模型化为状态机。一个状态机就是一组状态、输入事件和转移。
-
并发事件驱动程序中echo服务器中逻辑流的状态机
三、基于线程的并发编程
-
线程运行在进程上下文中的逻辑流。线程由内核自动调度,每个线程都有它自己的线程上下文。
-
线程执行模型。多线程的执行模型在某些方面和多进程的执行模型相似。每个进程开始生命周期时都是单一线程,这个线程称为主线程。在某一时刻,主线程创建一个对等线程,从在此刻开始,两个线程就并发地运行。
3.Posix线程
-
创建线程:
-
获取自身ID:
-
终止线程:
有以下四种方式终止线程:
-
当顶层的线程例程返回时,线程会隐式终止;
-
线程调用pthread_exit函数,线程会显示终止;如果主线程调用pthread_exit,它会等到所有其他对等线程终止,然后再终止主线程和整个线程,返回值为thread_return;
-
某个对等线程调用exut函数,则函数终止进程和所有与该进程相关的线程;
-
另一个对等线程调用以当前ID为参数的函数ptherad_cancel来终止当前线程。
四、多线程程序中的共享变量
- 每个线程都有它自己独自的线程上下文,包括线程ID、栈、栈指针、程序计数器、条件码和通用目的寄存器值。
- 个线程和其他线程一起共享进程上下文的剩余部分。寄存器是从不共享的,而虚拟存储器总是共享的。
- 线程化的c程序中变量根据它们的存储器类型被映射到虚拟存储器:全局变量,本地自动变量(不共享),本地静态变量。
五、用信号量同步线程 - 共享变量引入了同步错误。
5.2信号量 - 用信号量解决同步问题,信号量s是具有非负整数值的全局变量,有两种特殊的操作来处理(P和V):
P(s):如果s非零,那么P将s减1,并且立即返回。如果s为0,那么就挂起这个线程,直到s变为非零;
V(s):V操作将s加1。
5.3使用信号量实现互斥
5.4利用信号量调度共享资源
六、其他并发问题
不安全函数;
a)可重入函数。可重入函数是线程安全函数的一个真子集,它不访问任何共享数据。可重入安全函数通常比不可重入函数更有效,因为它们不需要任何同步原语。
b)竞争。当程序员错误地假设逻辑流该如何调度时,就会发生竞争。为了消除竞争,通常我们会动态地分配内存空间。
c)死锁。当一个流等待一个永远不会发生的事件时,就会发生死锁。
实践
condvar.c
-
在pthread库中通过条件变量(Condition Variable)来阻塞等待一个条件,或者唤醒等待这个条件的线程。所以在gcc编译时一定要加上
-lpthread
-
mutex用于保护资源,wait函数用于等待信号,signal函数用于通知信号。其中wait函数中有一次对mutex的释放和重新获取操作,因此生产者和消费者并不会出现死锁。
#include <stdlib.h> #include <pthread.h> #include <stdlib.h> typedef struct _msg{ struct _msg * next; int num; } msg; msg *head; pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void *consumer ( void * p ) { msg * mp; for( ;; ) { pthread_mutex_lock( &lock ); while ( head == NULL ) pthread_cond_wait( &has_product, &lock ); mp = head; head = mp->next; pthread_mutex_unlock ( &lock ); printf( "Consume %d tid: %d ", mp->num, pthread_self()); free( mp ); sleep( rand() % 5 ); } } void *producer ( void * p ) { msg * mp; for ( ;; ) { mp = malloc( sizeof(msg) ); pthread_mutex_lock( &lock ); mp->next = head; mp->num = rand() % 1000; head = mp; printf( "Produce %d tid: %d ", mp->num, pthread_self()); pthread_mutex_unlock( &lock ); pthread_cond_signal( &has_product ); sleep ( rand() % 5); } } int main(int argc, char *argv[] ) { pthread_t pid1, cid1; pthread_t pid2, cid2; srand(time(NULL)); pthread_create( &pid1, NULL, producer, NULL); pthread_create( &pid2, NULL, producer, NULL); pthread_create( &cid1, NULL, consumer, NULL); pthread_create( &cid2, NULL, consumer, NULL); pthread_join( pid1, NULL ); pthread_join( pid2, NULL ); pthread_join( cid1, NULL ); pthread_join( cid2, NULL ); return 0; }
count
- 这是不加锁的创建两个线程共享同一变量都实现加一操作的程序,在这个程序中虽然每个线程都给count加了5000,但由于结果的互相覆盖,最终输出值不是10000,而是5000。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter;
void *doit( void * );
int main(int argc, char **argv)
{
pthread_t tidA, tidB;
pthread_create( &tidA ,NULL, &doit, NULL );
pthread_create( &tidB ,NULL, &doit, NULL );
pthread_join( tidA, NULL );
pthread_join( tidB, NULL );
return 0;
}
void * doit( void * vptr)
{
int i, val;
for ( i=0; i<NLOOP; i++ ) {
val = counter++;
printf("%x: %d
", (unsigned int) pthread_self(), val + 1);
counter = val + 1;
}
}
countwithmutex.c
- 引入互斥锁(Mutex),获得锁的线程可以完成”读-修改-写”的操作,然后释放锁给其它线程。所以结果为10000。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
void *doit( void * );
int main(int argc, char **argv)
{
pthread_t tidA, tidB;
pthread_create( &tidA ,NULL, &doit, NULL );
pthread_create( &tidB ,NULL, &doit, NULL );
pthread_join( tidA, NULL );
pthread_join( tidB, NULL );
return 0;
}
void * doit( void * vptr)
{
int i, val;
for ( i=0; i<NLOOP; i++ ) {
pthread_mutex_lock( &counter_mutex );
val = counter++;
printf("%x: %d
", (unsigned int) pthread_self(), val + 1);
counter = val + 1;
pthread_mutex_unlock( &counter_mutex );
}
return NULL;
}
cp_t.c
- 用法:./cp_t [源文件名] [目的文件名] [创建线程数]
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <pthread.h>
typedef struct {
char *src_address;
char *dest_address;
int len;
} arg_t;
static void err_sys(const char *s);
static int get_file_length(int fd);
static int extend(int fd, int len);
arg_t map_src_dest(const char *src, const char *dest);
static void *cpy(void *arg);
void divide_thread(int pnum, arg_t arg_file);
int main(int argc, char *argv[])
{
arg_t arg_file;
if (argc != 4) {
fprintf(stderr, "Usage:%s file1 file2 thread_num", argv[0]);
exit(1);
}
arg_file = map_src_dest(argv[1], argv[2]);
divide_thread(atoi(argv[3]), arg_file);
munmap(arg_file.src_address, arg_file.len);
munmap(arg_file.dest_address, arg_file.len);
return 0;
}
static void err_sys(const char *s)
{
perror(s);
exit(1);
}
static int get_file_length(int fd)
{
int position = lseek(fd, 0, SEEK_CUR);
int length = lseek(fd, 0, SEEK_END);
lseek(fd, position, SEEK_SET);
return length;
}
static int extend(int fd, int len)
{
int file_len = get_file_length(fd);
if (file_len >= len) {
return -1;
}
lseek(fd, len - file_len - 1, SEEK_END);
write(fd, "", 1);
return 0;
}
arg_t map_src_dest(const char *src, const char *dest)
{
int fd_src, fd_dest, len;
char *src_address, *dest_address;
arg_t arg_file;
fd_src = open(src, O_RDONLY);
if (fd_src < 0) {
err_sys("open src");
}
len = get_file_length(fd_src);
src_address = mmap(NULL, len, PROT_READ, MAP_SHARED, fd_src, 0);
if (src_address == MAP_FAILED) {
err_sys("mmap src");
}
close(fd_src);
fd_dest = open(dest, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd_dest < 0) {
err_sys("open dest");
}
extend(fd_dest, len);
dest_address = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd_dest, 0);
if (dest_address == MAP_FAILED) {
err_sys("mmap dest");
}
close(fd_dest);
arg_file.len = len;
arg_file.src_address = src_address;
arg_file.dest_address = dest_address;
return arg_file;
}
static void *cpy(void *arg)
{
char *src_address, *dest_address;
int len;
src_address = ((arg_t *) arg)->src_address;
dest_address = ((arg_t *) arg)->dest_address;
len = ((arg_t *) arg)->len;
memcpy(dest_address, src_address, len);
return NULL;
}
void divide_thread(int pnum, arg_t arg_file)
{
int i, len;
char *src_address, *dest_address;
pthread_t *pid;
arg_t arg[pnum];
len = arg_file.len;
src_address = arg_file.src_address;
dest_address = arg_file.dest_address;
pid = malloc(pnum * sizeof(pid));
if (pnum > len) {
fprintf(stderr,
"too many threads, even larger than length, are you crazy?!
");
exit(1);
}
for (i = 0; i < pnum; i++) {
arg[i].src_address = src_address + len / pnum * i;
arg[i].dest_address = dest_address + len / pnum * i;
if (i != pnum - 1) {
arg[i].len = len / pnum;
} else {
arg[i].len = len - len / pnum * i;
}
pthread_create(&pid[i], NULL, cpy, &arg[i]);
}
for (i = 0; i < pnum; i++) {
pthread_join(pid[i], NULL);
}
free(pid);
}
createthread.c
- 打印进程和线程ID
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
pthread_t ntid;
void printids( const char *s )
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)
", s , ( unsigned int ) pid,
( unsigned int ) tid, (unsigned int ) tid);
}
void *thr_fn( void * arg )
{
printids( arg );
return NULL;
}
int main( void )
{
int err;
err = pthread_create( &ntid, NULL, thr_fn, "new thread: " );
if ( err != 0 ){
fprintf( stderr, "can't create thread: %s
", strerror( err ) );
exit( 1 );
}
printids( "main threads: " );
sleep(1);
return 0;
}
运行结果:
semphore.c
- semaphore表示信号量,semaphore变量的类型为sem_t,sem_init()初始化一个semaphore变量,value参数表示可用资源 的数量,pshared参数为0表示信号量用于同一进程的线程间同步。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
#define NUM 5
int queue[NUM];
sem_t blank_number, product_number;
void *producer ( void * arg )
{
static int p = 0;
for ( ;; ) {
sem_wait( &blank_number );
queue[p] = rand() % 1000;
printf("Product %d
", queue[p]);
p = (p+1) % NUM;
sleep ( rand() % 5);
sem_post( &product_number );
}
}
void *consumer ( void * arg )
{
static int c = 0;
for( ;; ) {
sem_wait( &product_number );
printf("Consume %d
", queue[c]);
c = (c+1) % NUM;
sleep( rand() % 5 );
sem_post( &blank_number );
}
}
int main(int argc, char *argv[] )
{
pthread_t pid, cid;
sem_init( &blank_number, 0, NUM );
sem_init( &product_number, 0, 0);
pthread_create( &pid, NULL, producer, NULL);
pthread_create( &cid, NULL, consumer, NULL);
pthread_join( pid, NULL );
pthread_join( cid, NULL );
sem_destroy( &blank_number );
sem_destroy( &product_number );
return 0;
}
share.c
- 获得线程的终止状态
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
char buf[BUFSIZ];
void *thr_fn1( void *arg )
{
printf("thread 1 returning %d
", getpid());
printf("pwd:%s
", getcwd(buf, BUFSIZ));
*(int *)arg = 11;
return (void *) 1;
}
void *thr_fn2( void *arg )
{
printf("thread 2 returning %d
", getpid());
printf("pwd:%s
", getcwd(buf, BUFSIZ));
pthread_exit( (void *) 2 );
}
void *thr_fn3( void *arg )
{
while( 1 ){
printf("thread 3 writing %d
", getpid());
printf("pwd:%s
", getcwd(buf, BUFSIZ));
sleep( 1 );
}
}
int n = 0;
int main( void )
{
pthread_t tid;
void *tret;
pthread_create( &tid, NULL, thr_fn1, &n);
pthread_join( tid, &tret );
printf("n= %d
", n );
printf("thread 1 exit code %d
", (int) tret );
pthread_create( &tid, NULL, thr_fn2, NULL);
pthread_join( tid, &tret );
printf("thread 2 exit code %d
", (int) tret );
pthread_create( &tid, NULL, thr_fn3, NULL);
sleep( 3 );
pthread_cancel(tid);
pthread_join( tid, &tret );
printf("thread 3 exit code %d
", (int) tret );
}
本周代码托管截图
其他(感悟、思考等,可选)
看到了娄老师的朋友圈,老师很器重大一的小朋友。想到自己下学期就没有娄老师的课了可能终于摆脱了博客,但是回想这两年多的学习自己到底沉淀了什么?大一的高数公式推理早已经忘记了,唯一沉淀下来的就是自己这近一年写的博客,和这种自主学习,写博客的学习习惯。设想自己如果从大一就开始接触老师这种学习方式,是不是和现在的自己会有很大的不一样,虽然很痛苦,但学习本来就不是一件容易的事。希望自己的脑子也能先变成容器,积极去吸取,然后再变成火把,熔化成陪伴自己一生的东西。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 2/4 | 18/38 | |
第三周 | 500/1000 | 3/7 | 22/60 | |
第四周 | 300/1300 | 2/9 | 30/90 | |
第五周 | 500/1000 | 3/12 | 22/120 | |
第六周 | 100/1300 | 2/15 | 30/150 | |
第七周 | 500/1000 | 3/18 | 22/180 | |
第八周 | 100/1500 | 2/20 | 30/210 | |
第九周 | 500/1600 | 2/22 | 32/242 | |
第十周 | 500/2100 | 4/26 | 40/274 | |
第十一周 | 0/2100 | 2/30 | 32/306 | |
第十二周 | 500/2600 | 1/31 | 12/318 |
参考资料
- 《深入理解计算机系统V2》学习指导