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小时

    • 改进情况:

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

    参考资料

  • 相关阅读:
    python学习
    当时的月亮 王菲
    谈谈写程序与学英语(转载)
    excel 列索引(数字)转列名
    爆款PHP面试题
    关于PDO取得结果集的数据类型为string的问题
    分享几款常用的API/文档浏览器
    php写错命名空间 导致catch不到异常
    iOS图片上传后被旋转的问题
    vi写完文件保存时才发现是readonly😂
  • 原文地址:https://www.cnblogs.com/guyanlin/p/7806181.html
Copyright © 2011-2022 走看看