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>\n", 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)\n", 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>\n", 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>\n", 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("\n客户端IP:127.0.0.1\n");
    printf("服务器实现者学号:20155227\n");
    
    time (&t);
    lt = localtime (&t);
    printf ("当前时间为:%d/%d/%d %d:%d:%d\n",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", 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>\n", 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小时

  • 改进情况:

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

参考资料

posted @ 2017-11-11 21:54  20155227辜彦霖  阅读(223)  评论(0编辑  收藏  举报