2017-2018-1 20155228《信息安全系统设计基础》第八周学习总结

2017-2018-1 20155228 《信息安全系统设计基础》第八周学习总结

教材学习内容总结

网络编程

客户端-服务器编程模型

每个网络应用都是基于客户端一服务器模型的。采用这个模型,一个应用是由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源,并且通过操作这种资源来为它的客户端提供某种服务。
例如,一个Web服务器管理着一组磁盘文件,它会代表客户端进行检索和执行。一个FTP服务器管理着一组磁盘文件,它会为客户端进行存储和检索。相似地,一个电子邮件服务器管理着一些文件,它为客户端进行读和更新。
客户端一服务器模型中的基本操作是事务。一个客户端一服务器事务由以下四步组成。

  1. 当一个客户端需要服务时,它向服务器发送一个请求,发起一个事务。例如,当Web浏览器需要一个文件时,它就发送一个请求给Web服务器。
  2. 服务器收到请求后,解释它,并以适当的方式操作它的资源。例如,当Web服务器收到浏览器发出的请求后,它就读一个磁盘文件。
  3. 服务器给客户端发送一个响应,并等待下一个请求。例如,Web服务器将文件发送回客户端。
  4. 客户端收到响应并处理它。例如,当Web浏览器收到来自服务器的一页后,就在屏幕上显示此页。

网络

  • 物理上而言,网络是一个按照地理远近组成的层次系统。最低层是LAN(Local Area Network,局域网),在一个建筑或者校园范围内,迄今为止,最流行的局域网技术是以太网(Ethernet)。

  • 一个以太网段(Ethernet segment)包括一些电缆(通常是双绞线)和一个叫做集线器的小盒子,以太网段通常跨越一些小的区域,例如某建筑物的一个房间或者一个楼层。集线器一端接到主机的适配器,而另一端则连接到集线器的一个端口上。集线器不加分辨地将从一个端口上收到的每个位复制到其他所有的端口上。因此,每台主机都能看到每个位。

  • 使用一些电缆和叫做网桥(bridge)的小盒子,多个以太网段可以连接成较大的局域网,称为桥接以太网(bridged Ethernet)。桥接以太网能够跨越整个建筑物或者校区。在一个桥接以太网里,一些电缆连接网桥与网桥,而另外一些连接网桥和集线器。这些电缆的带宽可以是不同的。
    网桥比集线器更充分地利用了电缆带宽。利用一种聪明的分配算法,它们随着时间自动学习哪个主机可以通过哪个端口可达,然后只在有必要时,有选择地将帧从一个端口复制到另一个端口。例如,如果主机A发送一个帧到同网段上的主机B,当该帧到达网桥X的输人端口时,X就将丢弃此帧,因而节省了其他网段上的带宽。然而,如果主机A发送一个帧到一个不同网段上的主机C,那么网桥X只会把此帧复制到和网桥Y相连的端口上,网桥Y会只把此帧复制到与主机C的网段连接的端口。

  • 在层次的更高级别中,多个不兼容的局域网可以通过叫做路由器(router)的特殊计算机连接起来,组成一个Internet(互联网络)。每台路由器对于它所连接到的每个网络都有一个适配器(端口)。路由器也能连接高速点到点电话连接,这是称为WAN ( Wide-AreaNetwork,广域网)的网络示例,之所以这么叫是因为它们覆盖的地理范围比局域网的大。一般而言,路由器可以用来由各种局域网和广域网构建互联网络。

全球IP因特网

  • 每台因特网主机都运行实现TCP/IP协议(Transmission Control Protocol/Internet Protocol,传输控制协议/互联网络协议)的软件,几乎每个现代计算机系统都支持这个协议。因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数来进行通信。通常将套接字函数实现为系统调用,这些系统调用会陷人内核,并调用各种内核模式的TCP/IP函数。
  • TCP/IP实际是一个协议族,其中每一个都提供不同的功能。例如,IP协议提供基本的命名方法和递送机制,这种递送机制能够从一台因特网主机往其他主机发送包,也叫做数据报(datagram) o IP机制从某种意义上而言是不可靠的,因为,如果数据报在网络中丢失或者重复,它并不会试图恢复。UDP ( Unreliable Datagram Protocol,不可靠数据报协议)稍微扩展了IP协议,这样一来,包可以在进程间而不是在主机间传送。TCP是一个构建在IP之上的复杂协议,提供了进程间可靠的全双工(双向的)连接。

套接字接口

  • 套接字接口(socket interface)是一组函数,它们和Unix I/O函数结合起来,用以创建网络应用。大多数现代系统上都实现套接字接口,包括所有的Unix变种、Windows和Macintosh系统。
  • 从Linux内核的角度来看,一个套接字就是通信的一个端点。从Linux程序的角度来看,套接字就是一个有相应描述符的打开文件。 因特网的套接字地址存放在如图11-13所示的类型为sockaddr in的16字节结构中。对于因特网应用,sin family成员是AFJNET, sin port成员是一个16位的端口号,而:in addr成员就是一个32位的IP地址。IP地址和端口号总是以网络字节顺序(大端法)存放的。
struct sockaddr_ in
{
    uintl6_t      sin_family;
    uintl6_t      sin_port;
    struct in_addr   sin_addr;
    unsigned char    sin_zero[8];
};

struct sockaddr
{
    uintl6_t  sa_family;
    char    sa_data[14];
};

  • connect、bind和accept函数要求一个指向与协议相关的套接字地址结构的指针。套接字接口的设计者面临的问题是,如何定义这些函数,使之能接受各种类型的套接字地址结构。今天我们可以使用通用的void*指针,但是那时在C中并不存在这种类型的指针。解决办法是定义套接字函数要求一个指向通用。ockaddr结构的指针,然后要求应用程序将与协议特定的结构的指针强制转换成这个通用结构。

Web服务器

  • Web客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫做HTTP(Hypertext Transfer Protocol,超文本传输协议)。HTTP是一个简单的协议。一个Web客户端(即浏览器)打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容,然后关闭连接。浏览器读取这些内容,并把它显示在屏幕上。
  • Web服务和常规的文件检索服务(例如FTP)有什么区别呢?主要的区别是Web内容可以用一种叫做HTML(Hypertext Markup Language,超文本标记语言)的语言来编写。一个HTML程序(页)包含指令(标记),它们告诉浏览器如何显示这页中的各种文本和图形对象。

并发编程

三种并发的方式

基于进程的并发编程
  • 构造并发程序最简单的方法就是用进程,使用那些大家都很熟悉的函数,像fork,exec和waitpid。例如,一个构造并发服务器的自然方法就是,在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。

  • 假设我们有两个客户端和一个服务器,服务器正在监听一个监听描述符(比如指述符3)上的连接请求。现在假设服务器接受了客户端1的连接请求,并返回一个已连接描述符(比如指述符4),如图12-1所示。在接受连接请求之后,服务器派生一个子进程,这个子进程获得服务器描述符表的完整副本。子进程关闭它的副本中的监听描述符3,而父进程关闭它的已连接描述符4的副本,因为不再需要这些描述符了。这就得到了图12-2中的状态,其中子进程正忙于为客户端提供服务。

  • 因为父、子进程中的已连接描述符都指向同一个文件表表项,所以父进程关闭它的已连接描述符的副本是至关重要的。否则,将永不会释放已连接描述符4的文件表条目,而且由此引起的内存泄漏将最终消耗光可用的内存,使系统崩溃。

  • 现在,假设在父进程为客户端1创建了子进程之后,它接受一个新的客户端2的连接请求,并返回一个新的已连接描述符(比如描述符5),如图12-3所示。然后,父进程又派生另一个子进程,这个子进程用已连接描述符5为它的客户端提供服务,如图12-4所示。此时,父进程正在等待下一个连接请求,而两个子进程正在并发地为它们各自的客户端提供服务。

  • 以下是一个基于进程的并发echo服务器的代码,第29行调用的ech,关于这个服务器,有几点重要内容需要说明:

  1. 首先,通常服务器会运行很长的时间,所以我们必须要包括一个SIGCHLD处理程序,来回收僵死(zombie)子进程的资源(第4-v9行)。因为当SIGCHLD处理程序执行时,SIGCHLD信号是阻塞的,而Linux信号是不排队的,所以SIGCHLD处理程序必须准备好回收多个僵死子进程的资源。
  2. 其次,父子进程必须关闭它们各自的connfd<分别为第33行和第30行)副本。就像我们已经提到过的,这对父进程而言尤为重要,它必须关闭它的已连接描述符,以避免内存泄漏。
  3. 最后,因为套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。
#include "csapp.h"
void echo(int connfd)
void
{
    sigchld handler(int sig)
    while (waitpid(-1, 0, WNOHANG)>0)
        return;
}
int
main(int argc,char **argv)
int listenfd, connfd;
socklen_t clientlen;
struct sockaddr_storage clientaddr;
if  (argc!=2)
{
    fprintf(stderr, "usage:}s <port>
",argv[O7);
            exit (0);
}
Signal(SIGCALD, sigchld_handler);
listenfd=Open_listenfd(argv[1]);
while (1)
{
    clientlen=sizeof(struct sockaddr_storage);
    c onnf d”Accept(listenfd, (SA*)&clientaddr, &clientlen);
    if (Fork()==0)
    {
        Close(listenfd);/* Child closes its listening socket*/
        echo(connfd);/*Child services client*/
        Close(connfd);/*Child closes connection with client*/
        exit (0);/*Child exits*/
    }
    Close(connfd);/*Parent closes connected socket (important!)*/
}
}

基于I/O多路复用的并发编程
  • 要求编写一个echo服务器,它也能对用户从标准输入键人的交互命令做出响应。在这种情况下,服务器必须响应两个互相独立的I/O事件:1>网络客户端发起连接请求,2)用户在键盘上键人命令行。先等待哪个事件呢?没有哪个选择是理想的。如果在accept中等待一个连接请求,就不能响应输人的命令。类似地,如果在read中等待一个输人命令,我们就不能响应任何连接请求。
  • 针对这种困境的一个解决办法就是I/O多路复用(I/O multiplexing)技术。基本的思路就是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
基于线程的并发编程

线程(thread)就是运行在进程上下文中的逻辑流。程序都是由每个进程中一个线程组成的。但是现代系统也允许编写一个进程里同时运行多个线程的程序。线程由内核自动调度。每个线程都有它自己的线程上下文(thread context),包括一个唯一的整数线程ID(Thread ID, TID)、栈、栈指针、程序计数器、通用目的寄存器和条件码。所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间。 基于线程的逻辑流结合了基于进程和基于I/O多路复用的流的特性。同进程一样,线程由内核自动调度,并且内核通过一个整数ID来识别线程。同基于I/O多路复用的流一样,多个线程运行在单一进程的上下文中,因此共享这个进程虚拟地址空间的所有内容,包括它的代码、数据、堆、共享库和打开的文件。

线程控制及相关系统调用

多线程的执行模型在某些方面和多进程的执行模型是相似的。思考图12-12中的示例。每个进程开始生命周期时都是单一线程,这个线程称为主线程(main thread) o在某一时刻,主线程创建一个对等线程(peer thread),从这个时间点开始,两个线程就并发地运行。最后,因为主线程执行一个慢速系统调用,例如read或者sleep,或者因为被系统的间隔计时器中断,控制就会通过上下文切换传递到对等线程。对等线程会执行一段时间,然后控制传递回主线程,依次类推。 在一些重要的方面,线程执行是不同于进程的。因为一个线程的上下文要比一个进程的上下文小得多,线程的上下文切换要比进程的上下文切换快得多。另一个不同就是线程不像进程那样,不是按照严格的父子层次来组织的。和一个进程相关的线程组成一个对等(线程)池,独立于其他线程创建的线程。主线程和其他线程的区别仅在于它总是进程中第一个运行的线程。对等(线程)池概念的主要影响是,一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止。另外,每个对等线程都能读写相同的共享数据。

教材学习中的问题和解决过程

线程同步互斥及相关系统调用

  • 信号量提供了一种很方便的方法来确保对共享变量的互斥访问。基本思想是将每个共享变量(或者一组相关的共享变量)与一个信号量、(初始为1>联系起来,然后用尸(、)和V(、)操作将相应的临界区包围起来。 以这种方式来保护共享变量的信号量叫做二元信号量(binary semaphore),因为它的值总是0或者1。以提供互斥为目的的二元信号量常常也称为互斥锁(mutex)。在一个互斥锁上执行尸操作称为对互斥锁加锁。类似地,执行V操作称为对互斥锁解锁。对一个互斥锁加了锁但是还没有解锁的线程称为占用这个互斥锁。一个被用作一组可用资源的计数器的信号量被称为计数信号量。
    (插入图片)
  • 图12-22中的进度图展示了我们如何利用二元信号量来正确地同步计数器程序示例。每个状态都标出了该状态中信号量、的值。关键思想是这种尸和V操作的结合创建了一组状态,叫做禁止区(forbidden region),其中s<0因为信号量的不变性,没有实际可行的轨迹线能够包含禁止区中的状态。而且,因为禁止区完全包括了不安全区,所以没有实际可行的轨迹线能够接触不安全区的任何部分。因此,每条实际可行的轨迹线都是安全的,而且不管运行时指令顺序是怎样的,程序都会正确地增加计数器值。从可操作的意义上来说,由尸和V操作创建的禁止区使得在任何时间点上,在被包围的临界区中,不可能有多个线程在执行指令。换句话说,信号量操作确保了对临界区的互斥访问。

代码调试中的问题和解决过程

用inet_pton而不是getnameinf将每个套接字地址转换成点分十进制地址字符串, 编HOSTINFO的一个版本

#include "csapp.h"
int main(int argc,char **argv)
{
    struct addrinfo *p,*listp, hints
        char buf[MAXLINE];
    int rc,flags;
    if (argc != 2)
    {
        fprintf(stderr,"usage:}s <domain name>
",argv [0])
        exit (0);
    }
    /*Get a list of addrinfo records*/
    memset(&hints,0,sizeof(struct addrinfo));
    hints.ai_family=AF_ INET;/*IPv4 only*/
    hints.ai_socktype=SOCK_STREAM;/*Connections only*/
    if((rc=getaddrinfo(argv[1],NULL,&hints,8tlistp))l=0)
    {
        fprintf(stderr, "getaddrinfo error:}s
",gai_strerror(rc))
        exit (1);
    }
    /*Walk the list and display each IP address*/
    flags=NI NUMERICHOST;/*Display address string instead of domain name*/
    for  (p=listp; p; P=p->ai next)
    {
        Getnameinfo(p->ai_addr,p->ai-addrlen, buf,MAXLINE, NULL, 0,flags);
        printf("%s
",buf );
    }
    /*Clean up*/
    Freeaddrinfo(listp);
    exit (0)
}
#include "csapp.h
int
{
    main(int argc, char **argv)
    struct addrinfo *p,
               struct sockaddr_in
                   char buf[MAXLINE];
    int rc;
    *listp, hints;
    *sockp
    if  (argc!=2){
        fprintf(stderr, "usage:
            } s <domain name>
",argv [0]);
        exit (0);
    }
    /*Get a list of addrinfo records*/
    memset($hints, 0, sizeof(struct addrinfo));
    hints.ai_family=AF_INET;/*IPv4 only*/
    hints.ai_socktype=SOCK_STREAM;/*Connections only*/
    if((rc=getaddrinfo(argv[1],NULL,& hints, &listp))!=0){
        fprintf(stderr, "getaddrinfo error:
            } s
",gai_strerror(rc));
        exit(1);
    }
    /*Walk the list and display each associated IP address*/
    for  (p,listp; p; p‘p->ai next){
        sockp=(struct sockaddr-in *)p->ai-addr;
        Inet ntop(AF-INET, }(sockp->sin_addr),buf, MAXLINE);
    printf("
       } s
",buf );
}
/*Clean up*/
Freeaddrinfo(listp);
exit (0)
}

代码托管

本周结对学习情况

- [201552222](http://www.cnblogs.com/20155222lzj/)

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 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
第五周 400/900 2/6 6/30
第六周 200/1100 1/7 6/30
第七周 500/1600 2/9 6/36
第八周 300/1900 1/10 6/42

尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。

参考:软件工程软件的估计为什么这么难软件工程 估计方法

  • 计划学习时间:6小时

  • 实际学习时间:6小时

  • 改进情况:

(有空多看看现代软件工程 课件
软件工程师能力自我评价表
)

参考资料

原文地址:https://www.cnblogs.com/besti20155228/p/7821406.html