zoukankan      html  css  js  c++  java
  • 2017-2018-1 20155227 《信息安全系统设计基础》第八周学习总结

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

    第八周课下作业2(课上没完成的必做)

    把课上练习3的daytime服务器分别用多进程和多线程实现成并发服务器并测试
    
    提交博客链接
    

    课上的实践3由于时间原因,我只运行了从网上找的代码,还没有来得及修改,运行结果如下:

    多线程

    找到书上的相关代码:

    echoserveri.c :

    /* 
     * echoserveri.c - An iterative echo server 
     */ 
    /* $begin echoserverimain */
    #include "csapp.h"
    
    void echo(int connfd);
    
    int main(int argc, char **argv) 
    {
        int listenfd, connfd, port, clientlen;
        struct sockaddr_in clientaddr;
        struct hostent *hp;
        char *haddrp;
        if (argc != 2) {
    	fprintf(stderr, "usage: %s <port>
    ", argv[0]);
    	exit(0);
        }
        port = atoi(argv[1]);
    
        listenfd = Open_listenfd(port);
        while (1) {
    	clientlen = sizeof(clientaddr);
    	connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
    
    	/* determine the domain name and IP address of the client */
    	hp = Gethostbyaddr((const char *)&clientaddr.sin_addr.s_addr, 
    			   sizeof(clientaddr.sin_addr.s_addr), AF_INET);
    	haddrp = inet_ntoa(clientaddr.sin_addr);
    	printf("server connected to %s (%s)
    ", hp->h_name, haddrp);
    
    	echo(connfd);
    	Close(connfd);
        }
        exit(0);
    }
    /* $end echoserverimain */
    
    

    echoclient.c:

    /*
     * echoclient.c - An echo client
     */
    /* $begin echoclientmain */
    #include "csapp.h"
    
    int main(int argc, char **argv) 
    {
        int clientfd, port;
        char *host, buf[MAXLINE];
        rio_t rio;
    
        if (argc != 3) {
    	fprintf(stderr, "usage: %s <host> <port>
    ", argv[0]);
    	exit(0);
        }
        host = argv[1];
        port = atoi(argv[2]);
    
        clientfd = Open_clientfd(host, port);
        Rio_readinitb(&rio, clientfd);
    
        while (Fgets(buf, MAXLINE, stdin) != NULL) {
    	Rio_writen(clientfd, buf, strlen(buf));
    	Rio_readlineb(&rio, buf, MAXLINE);
    	Fputs(buf, stdout);
        }
        Close(clientfd);
        exit(0);
    }
    /* $end echoclientmain */
    
    

    修改后的代码为:

    /*
     * echoclient.c - An echo client
     */
    /* $begin echoclientmain */
    #include "csapp.h"
    
    int main(int argc, char **argv) 
    {
        int clientfd, port;
        char *host, buf[MAXLINE];
        rio_t rio;
    
        if (argc != 3) {
    	fprintf(stderr, "usage: %s <host> <port>
    ", argv[0]);
    	exit(0);
        }
        host = argv[1];
        port = atoi(argv[2]);
    
        clientfd = Open_clientfd(host, port);
        Rio_readinitb(&rio, clientfd);
    
        while (Fgets(buf, MAXLINE, stdin) != NULL) {
    	
    	time_t t;
        struct tm * lt;
        size_t n; 
        printf("
    客户端IP:127.0.0.1
    ");
        printf("服务器实现者学号:20155227
    ");
        
        time (&t);
        lt = localtime (&t);
        printf ("当前时间为:%d/%d/%d %d:%d:%d
    ",lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec);
    	Rio_writen(clientfd, buf, strlen(buf));
    	Rio_readlineb(&rio, buf, MAXLINE);
    	Fputs(buf, stdout);
        }
        Close(clientfd); //line:netp:echoclient:close
        exit(0);
    }
    /* $end echoclientmain */
    

    遇到的问题: csapp.h无法使用。

    解决办法:

    csapp.h其实就是一堆头文件的打包,
    下载并解压后(以 root 身份登录)应该是一个code的文件夹,在其子文件夹include和src中分别可以找到csapp.h和csapp.c两个文件,把这两个文件拷贝到文件夹/usr/include里面,并在csapp.h文件中 #endif 之前加上一句 #include<csapp.h> ,然后编译时在最后加上 “-lpthread” 例如:gcc main.c -lpthread 就可以编译了。

    参考在Ubuntu下使用 csapp.h 和 csapp.c

    修改之后运行结果如下:

    服务器显示信息:

    服务器不显示信息:

    多进程

    echoserverp.c:

    /* 
    
     * echoserverp.c - A concurrent echo server based on processes
    
     */
    
    /* $begin echoserverpmain */
    
    #include "csapp.h"
    
    void echo(int connfd) 
    
    {
    
        size_t n; 
    
        char buf[MAXLINE]; 
    
        rio_t rio;
    
    
    
        Rio_readinitb(&rio, connfd);
    
        while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
    
    	printf("server received %d bytes
    ", n);
    
    	Rio_writen(connfd, buf, n);
    
        }
    
    }
    
    
    
    void sigchld_handler(int sig) 
    
    {
    
        while (waitpid(-1, 0, WNOHANG) > 0)
    
    	;
    
        return;
    
    }
    
    
    
    int main(int argc, char **argv) 
    
    {
    
        int listenfd, connfd, port, clientlen=sizeof(struct sockaddr_in);
    
        struct sockaddr_in clientaddr;
    
    
    
        if (argc != 2) {
    
    	fprintf(stderr, "usage: %s <port>
    ", argv[0]);
    
    	exit(0);
    
        }
    
        port = atoi(argv[1]);
    
    
    
        Signal(SIGCHLD, sigchld_handler);
    
        listenfd = Open_listenfd(port);
    
        while (1) {
    
    	connfd = 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!) */
    
        }
    
    }
    
    /* $end echoserverpmain */
    

    运行结果:

    教材第十二章学习

    并发编程

    • 并发:逻辑控制流在时间上重叠
    • 并发程序:使用应用级并发的应用程序称为并发程序。
    • 三种基本的构造并发程序的方法:

    进程,用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制。

    I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。

    线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。

    基于进程的并发编程

    • 构造并发程序最简单的方法就是用进程。

      一个构造并发服务器的自然方法就是,在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。

    • 基于进程的并发服务器

      通常服务器会运行很长的时间,所以我们必须要包括一个 SIGCHLD 处理程序,来回收僵死 (zombie) 子进程的资源。当 SIGCHLD 处理程序执行时, SIGCHLD 信号是阻塞的,而 Unix 信号是不排队的。

      父子进程必须关闭它们各自的 connfd 拷贝。父进程必须关闭它的已连接描述符,以避免存储器泄漏。直到父子进程的 connfd 都关闭了,到客户端的连接才会终止。

    • 进程的优劣

      优点:一个进程不可能不小心覆盖另一个进程的虚拟存储器,这就消除了许多令人迷惑的错误。

      缺点:独立的地址空间使得进程共享状态信息变得更加困难。为了共享信息,它们必须使用显式的IPC(进程间通信)机制。基于进程的设计的另一个缺点是,它们往往比较慢,因为进程控制和 IPC 的开销很高。

    基于 I/O 多路复用的并发编程

    • I/O多路复用技术的基本思路:使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序

    • 状态机就是一组状态、输入事件和转移,转移就是将状态和输入时间映射到状态,自循环是同一输入和输出状态之间的转移。

    • I/O 多路复用技术的优劣

      优点:

      • 它比基于进程的设计给了程序员更多的对程序行为的控制。
      • 一个基于 I/O 多路复用的事件驱动服务器是运行在单一进程上下文中的,因 此每个逻辑流都能访问该进程的全部地址空间。

      缺点:编码复杂且不能充分利用多核处理器。

    基于线程的并发编程

    • 线程:运行在进程上下文中的逻辑流。

    • 线程有自己的线程上下文,包括一个唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间

    • 主线程:每个进程开始生命周期时都是单一线程。

    • 对等线程:某一时刻,主线程创建的对等线程。

    • 创建线程

      pthread_create 函数创建一个新的线程,并带着一个输入变量arg,在新线程的上下文中运行线程例程f。用attr参数来改变新创建线程的默认属性。 返回时,参数 tid包含新创建线程的ID。新线程可以通过调用 pthreadself 函数来获得它自己的线程 ID。

    • 终止线程
      当顶层的线程例程返回时,线程会隐式地终止。

      通过调用 pthreadexit 函数,线程会显式地终止。如果主线程调用 pthreadexit , 它会等待所有其他对等线程终止,然后再终止主线程和整个进程,返回值为 thread_return。

    • 回收已终止线程的资源

      线程通过调用 pthread_join 函数等待其他线程终止。pthread _join函数会阻塞,直到线程tid终止,回收已终止线程占用的所有存储器资源。pthread _join函数只能等待一个指定的线程终止。

    • 分离线程

      在任何一个时间点上,线程是可结合的 (joinable) 或者是分离的 (detached)。一个可结合的线程能够被其他线程收回其资源和杀死。在被其他线程回收之前,它的存储器资源(例如栈)是没有被释放的。

    多线程程序中的变量共享

    • 每个线程和其他线程一起共享进程上下文的剩余部分。包括整个用户虚拟地址空间,是由只读文本、读/写数据、堆以及所有的共享库代码和数据区域组成的。线程也共享同样的打开文件的集合。

    • 全局变量:虚拟存储器的读/写区域只会包含每个全局变量的一个实例。

    • 本地自动变量:定义在函数内部但没有static属性的变量。

    • 本地静态变量:定义在函数内部并有static属性的变量。

    用信号量同步线程

    • 进度图

      进度图是将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线,原点对应于没有任何线程完成一条指令的初始状态。

    转换规则:
    合法的转换是向右或者向上,即某一个线程中的一条指令完成
    两条指令不能在同一时刻完成,即不允许出现对角线
    程序不能反向运行,即不能出现向下或向左
    
    
    • 信号量定义:
    type semaphore=record
    count: integer;
    queue: list of process
    end;
    var s:semaphore;
    
    读者—写者问题:
    (1)读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。 
    (2)写者优先,要求一旦一个写者准备好可以写,它就会尽可能地完成它的写操作。 
    (3)饥饿就是一个线程无限期地阻塞,无法进展。
    
    

    其他并发问题

    线程安全

    当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。

    可重入性

    显式可重入的:所有函数参数都是传值传递,没有指针,并且所有的数据引用都是本地的自动栈变量,没有引用静态或全剧变量。

    隐式可重入的:调用线程小心的传递指向非共享数据的指针。

    竞争

    发生的原因:一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点。也就是说,程序员假定线程会按照某种特殊的轨迹穿过执行状态空间,忘了一条准则规定:线程化的程序必须对任何可行的轨迹线都正确工作。

    消除方法:动态的为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针

    死锁

    死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。

    死锁是不可预测的。

    代码托管

    (statistics.sh脚本的运行结果截图)

    学习进度条

    代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
    目标 5000行 30篇 400小时
    第一周 133/133 1/1 8/8
    第三周 159/292 1/3 10/18
    第五周 121/413 1/5 10/28
    第七周 835/3005 2/7 10/38
    第八周 1702/4777 1/8 10/48

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

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

    • 计划学习时间:15小时

    • 实际学习时间:10小时

    • 改进情况:

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

    参考资料

  • 相关阅读:
    django页面分类和继承
    django前端从数据库获取请求参数
    pycharm配置django工程
    django 应用各个py文件代码
    CF. 1428G2. Lucky Numbers(背包DP 二进制优化 贪心)
    HDU. 6566. The Hanged Man(树形背包DP DFS序 重链剖分)
    小米邀请赛 决赛. B. Rikka with Maximum Segment Sum(分治 决策单调性)
    区间树 学习笔记
    CF GYM. 102861M. Machine Gun(主席树)
    2016-2017 ACM-ICPC East Central North America Regional Contest (ECNA 2016) (B, D, G, H)
  • 原文地址:https://www.cnblogs.com/guyanlin/p/7806181.html
Copyright © 2011-2022 走看看