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

教材学习内容总结

客户端-服务器编程模型

1、组成

一个服务器进程 -> 管理某种资源 -> 通过操作这种资源来为它的客户端提供某种服务
一个或多个客户端进程
客户端和服务器都是进程

2、基本操作:事务
image

网络

1、对主机而言:网络是一种I/O设备

通过DMA(直接存储器存取方式)传送:从网络上接收到的数据从适配器经过I/O和存储器总线拷贝到存储器

2、物理上:网络是一个按照地理远近组成的层次系统

最底层是LAN(局域网),最流行的是以太网

以太网段

包括一些电缆和集线器。每根电缆都有相同的最大位带宽,集线器不加分辩地将一个端口上收到的每个位复制到其他所有的端口上,因此每台主机都能看到每个位。

每个以太网适配器都有一个全球唯一的48位地址,存储在适配器的非易失性存储器上。

一台主机可以发送一段位:帧,到这个网段内其它任何主机。每个帧包括一些固定数量的头部位(标识此帧的源和目的地址及帧长)和数据位(有效载荷)。每个主机都能看到这个帧,但是只有目的主机能读取。

使用电缆和网桥,多个以太网段可以连接成较大的局域网,称为桥接以太网。这些电缆的带宽可以是不同的。
多个不兼容的局域网可以通过叫做路由器的特殊计算机连接起来,组成一个internet互联网络。

3、协议

互联网重要特性:由采用不同技术,互不兼容的局域网和广域网组成,并能使其相互通信。其中不同网络相互通信的解决办法是一层运行在每台主机和路由器上的协议软件,消除不同网络的差异。

协议提供的两种基本能力

命名机制:唯一的标示一台主机
传送机制:定义一种把数据位捆扎成不连续的片的同一方式
4、全球IP因特网

TCP/IP协议族

混合使用套接字接口函数和UnixI/O函数进行通信

世界范围的主机集合特性:

  • 主机集合被映射为一组32位的IP地址
  • 这组IP地址被映射为一组称为因特网域名的标识符
  • 因特网主机上的进程能够通过连接和任何其他主机上的进程
    5、检索并打印一个DNS主机条目
#include "csapp.h"
int main(int argc, char **argv) 
{
    char **pp;
    struct in_addr addr;
    struct hostent *hostp;
    if (argc != 2) {
    fprintf(stderr, "usage: %s <domain name or dotted-decimal>\n", 
        argv[0]);
    exit(0);
    }
    if (inet_aton(argv[1], &addr) != 0) 
    hostp = Gethostbyaddr((const char *)&addr, sizeof(addr), AF_INET); 
    else                                
    hostp = Gethostbyname(argv[1]);
    printf("official hostname: %s\n", hostp->h_name);
    for (pp = hostp->h_aliases; *pp != NULL; pp++)
    printf("alias: %s\n", *pp);
    for (pp = hostp->h_addr_list; *pp != NULL; pp++) {
    addr.s_addr = ((struct in_addr *)*pp)->s_addr;
    printf("address: %s\n", inet_ntoa(addr));
    }
    exit(0);
}

套接字

image

  • 函数包括:

socket函数
connect函数
open_clientfd函数
bind函数
listen函数
open_listenfd函数
accept函数

Web服务器

1、HTTP (Hypertext Transfer Protocol,超文本传输协议)

Web 客户端和服务器之间交互时用的一个基于文本的应用级协议。

HTTP 是一个简单的协议。

一个 Web 客户端(即浏览器) 打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容,然后关闭连接。浏览器读取这些内容,并把它显示在屏幕上。

2、内容

Web内容可以用一种叫做 HTML(Hypertext Markup Language,超文本标记语言)的语言来编写。一个 HTML 程序(页)包含指令(标记),它们告诉浏览器如何显示这页中的各种文本和图形对象。

Web 服务器以两种不同的方式向客户端提供内容:

取一个磁盘文件,并将它的内容返回给客户端。磁盘文件称为静态内容 (static content), 而返回文件给客户端的过程称为服务静态内容 (serving static content)。 运行一个可执行文件,并将它的输出返回给客户端。运行时可执行文件产生的输出称为态内容 (dynamic content),而运行程序并返回它的输出到客户端的过程称为服务动态内容 (serving dynamic content)。

并发概述

概述

1、并发:逻辑控制流在时间上重叠。

2、并发程序:使用应用级并发的应用程序。

3、三种基本的构造并发程序的方法:

进程,用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制。
I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。

基于进程的并发编程

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

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

  • 2、基于进程的并发服务器

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

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

  • 3、进程的优劣

父、子进程间共享状态信息,进程有一个非常清晰的模型:共享文件表,但是不共享用户地址空间。

进程有独立的地址空间既是优点也是缺点:

优点:一个进程不可能不小心覆盖另一个进程的虚拟存储器,这就消除了许多令人迷惑的错误。
缺点:独立的地址空间使得进程共享状态信息变得更加困难。为了共享信息,它们必须使用显式的IPC(进程间通信)机制。基于进程的设计的另一个缺点是,它们往往比较慢,因为进程控制和 IPC 的开销很高。

.基于进程的并发编程

构造并发程序最简单的方法就是用进程。
一个构造并发服务器的自然方法就是,在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
基于进程的并发服务器
通常服务器会运行很长的时间,所以我们必须要包括一个 SIGCHLD 处理程序,来回收僵死 (zombie) 子进程的资源。因为当 SIGCHLD 处理程序执行时, SIGCHLD 信号是阻塞的,而 Unix 信号是不排队的,所以 SIGCHLD 处理程序必须准备好回收多个僵死子进程的资源。

父子进程必须关闭它们各自的 connfd 拷贝。这对父进程而言尤为重要,它必须关闭它的已连接描述 符,以避免存储器泄漏。

因为套接字的文件表表项中的引用计数,直到父子进程的 connfd 都关闭了,到客户端的连接才会终止。

第一步:服务器接受客户端的连接请求
第二步:服务器派生一个子进程为这个客户端服务
第三步:服务器接受另一个连接请求
第四步:服务器派生另一个子进程为新的客户端服务
进程的优劣
在父、子进程间共享状态信息,进程有一个非常清晰的模型:共享文件表,但是不共享用户地址空间。

进程有独立的地址空间既是优点也是缺点。
优点:一个进程不可能不小心覆盖另一个进程的虚拟存储器,这就消除了许多令人迷惑的错误。
缺点:独立的地址空间使得进程共享状态信息变得更加困难。为了共享信息,它们必须使用显式的IPC(进程间通信)机制。基于进程的设计的另一个缺点是,它们往往比较慢,因为进程控制和 IPC 的开销很高。

线程(thread) 就是运行在进程上下文中的逻辑流。

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

基于线程的逻辑流结合了基于进程和基于 I/O 多路复用的流的特性。同进程一样,线程由内核自动调度,并且内核通过一个整数 ID 来识别线程。同基于 I/O 多路复用的流一样,多个线程 运行在单一进程的上下文中,因此共享这个进程虚拟地址空间的整个内容,包括它的代码、数据、堆、共享库和打开的文件。

线程

(1)线程执行模型

每个进程开始生命周期时都是单一线程,这个线程称为主线程 (main thread)。在某一时刻,主线程创建一个对等线程 (peer thread),从这个时间点开始,两个线程就并发地运行。最后,因为主线程执行一个慢速系统调用。或者因为它被系统的间隔计时器中断, 控制就会通过上下文切换传递到对等线程。对等线程会执行一段时间,然后控制传递回主线程,依次类推。

  • 线程的上下文切换要比进程的上下文切换快得多。
  • 不是按照严格的父子层次来组织的。
  • 和一个进程相关的线程组成一个对等(线程)池 (pool),独立于其他线程创建的线程。
  • 主线程和其他线程的区别仅在于它总是进程中第一个运行的线程。
  • 对等 (线程)池概念的主要影响是,一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止。
  • 每个对等线程都能读写相同的共享数据。
    (2)Posix 线程

Posix 线程 (Pthreads) 是在 C 程序中处理线程的一个标准接口。Pthreads 定义了大约 60 个函数,允许程序创建、杀死和回收线程,与对等线程安全地共享数据,还可以通知对等线程系统状态的变化。

线程的代码和本地数据被封装在一个线程例程(thread routine) 中。如果想传递多个参数给钱程例程,那么你应该将参数放 到一个结构中,并传递一个指向该结构的指针。想要线程例程返回多个参数,你可以返回一个指向一个结构的指针。

(3)创建线程

pthread_create 函数创建一个新的线程,并带着一个输入变量arg,在新线程的上下文中运行线程例程f。能用attr参数来改变新创建线程的默认属性。

当 pthreadcreate 返回时,参数 tid包含新创建线程的ID。新线程可以通过调用 pthreadself 函数来获得它自己的线程 ID.

(4)终止线程

当顶层的线程例程返回时,线程会隐式地终止。 通过调用 pthreadexit 函数,线程会显式地终止。如果主线程调用 pthreadexit , 它会等待所有其他对等线程终止,然后再终止主线程和整个进程,返回值为 thread_return。
(5)回收已终止线程的资源

线程通过调用 pthread_join 函数等待其他线程终止。

pthreadjoin 函数会阻塞,直到线程 tid 终止,将线程例程返回的 (void*) 指针赋值为 threadreturn 指向的位置,然后回收己终止线程占用的所有存储器资源。
pthread join 函数只能等待一个指定的线程终止。
(6)分离线程

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

默认情况下,线程被创建成可结合的。为了避免存储器泄漏,每个可结合线程都应该要么被其他线程显式地收回,要么通过调用 pthread_detach 函数被分离。

pthreaddetach 函数分离可结合线程 tid. 线程能够通过以 pthreadself()为参数的 pthread_detach 调用来分离它们自己。
(7)初始化线程

pthread_once 函数允许你初始化与线程例程相关的状态。

(8)一个基于线程的并发服务器

调用 pthread_ create 时,如何将已连接描述符传递给对等线程。最明显的方法就是传递一个指向这个描述符的指针。 对等线程间接引用这个指针,并将它赋值给一个局部变量。

在C语言中,过程抽象的结果是模块。模块包含接口和实现。
接口要指明该模块要做什么,内容主要包含标识符、类型定义和函数。
在C语言中,接口一般通过头文件的形式呈现。
作为函数调用者角色的程序员,只要了解模块的接口就可以使用模块了。

其中:

  • 接口指明模块要做什么,
    其中所用的标识符、类型、函数等,
    *.h结尾,
    是函数调用者。
  • 实现则指明模块如何完成接口,
    可以完成一个接口多个实现的可能,
    *.c结尾,
    是函数实现者
什么是函数签名

函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息。函数签名用于识别不同的函数,就像签名用于识别不同的人一样,函数的名字只是函数签名的一部分。

注意不包含返回值

  • 写出多个签名的重点是改变参数的数量和类型。
  • 使用结构体的好处是不管原来有几个参数、参数类型是什么,都可以封装在一个结构体中,这样使用结构体参数的函数的参数个数总是可以只有一个。

于是我们设计出来一个万能函数,借助结构体可以把所有的函数化成万能函数的等价形式。
那么万能函数指针 uf 就可以指向所有函数了。

void *  func( void * parameter)

typedef void* (*uf)(void * para)

并发

我们接触到两类并发:
程序级并发是通过进程实现的,更细粒度的并发是函数级并发,函数级并发通过线程来实现。

  • fflush(stdout)的作用

在printf()后使用fflush(stdout)的作用是立刻将要输出的内容输出。
当使用printf()函数后,系统将内容存入输出缓冲区,等到时间片轮转到系统的输出程序时,将其输出。
使用fflush(out)后,立刻清空输出缓冲区,并把缓冲区内容输出。

在hello_single.c文件中输出为隔一秒输出一个hello或world\n。是依次输出。如果没有fflush(stdout)则会隔几秒后一下子输出好几个hello。

pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。

  • thread 线程以 不同的方法终止,通过 pthread_join 得到的终止状态是不同的,总结如下: • 如果 thread 线程通过 return 返回,value_ptr 所指向的单元里存放的 是 thread 线程函数的返回值。 • 如果 thread 线程被别的线程调用 pthread_cancel 异常终止掉, value_ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED。 • 如果 thread 线程是自己调用 pthread_exit 终止的,value_ptr 所指向 的单元存放的是传给 pthread_exit 的参数。

多线程程序中的共享变量

1、线程存储器模型

一个变量是共享的,当且仅当多个线程引用这个变量的某个实例。

2、将变量映射到存储器

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

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

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

3、共享变量

变量是共享的<=>它的一个实例被一个以上的线程引用

用信号量同步线程

1、进度图

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

转换规则:

合法的转换是向右或者向上,即某一个线程中的一条指令完成
两条指令不能在同一时刻完成,即不允许出现对角线
程序不能反向运行,即不能出现向下或向左
线程循环代码的分解:

H:在循环头部的指令块
L:加载共享变量cnt到线程i中寄存器%eax的指令
U:更新(增加)%eax的指令
S:将%eax的更新值存回到共享变量cnt的指令
T:循环尾部的指令块
临界区:对于线程i,操作共享变量cnt内容的指令L,U,S构成了一个关于共享变量cnt的临界区。

2、信号量

信号量定义:

type semaphore=record
count: integer;
queue: list of process
end;
var s:semaphore;
使用信号量来实现互斥基本思想:将每个共享变量(或者一组相关的共享变量)与一个信号量s(初始为1)联系起来,然后用P和V操作将相应的临界区包围起来。

读者—写者问题:

(1)读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。
(2)写者优先,要求一旦一个写者准备好可以写,它就会尽可能地完成它的写操作。
(3)饥饿就是一个线程无限期地阻塞,无法进展。
使用线程提高并行性

写顺序程序只有一条逻辑流,写并发程序有多条并发流,并行程序的集合是并发程序集合的真子集。

其他并发问题

1、线程安全

一个线程是安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。
2、可重入性

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

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

3、在线程化的程序中使用已存在的库函数

线程不安全函数的可重入版本,名字以_r为后缀结尾。

4、竞争

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

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

5、死锁

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

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

  • 问题1:在对上面的问题进行求解过程中遇到如下问题,如图反汇编后得到的代码含有前面的堆栈号。
    tupian1

  • 问题1解决方案:把前面的堆栈号手动删除。

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

  • 问题1:运行程序时发生如下错误:发现“csapp.h”这个头文件C语言库中没有image

  • 问题1解决方案:老师在开学的时候上传的code中有“csapp.h”、“csapp.c”下载后,将它们放在我的代码文件夹中即可。image

  • 问题2:但是上述问题在生成.o文件后出现问题如下图:

  • 问题2解决方案:我认为出现这种情况的原因,应该是之前书上说的是C编译为.o文件的时候并不需要函数的具体实现,只要有函数的原型即可,但是在链接为可执行文件的时候就必须要具体的实现。
    查看.h和.c文件,发现.c文件中才是定义的那些函数的具体实现,结合第一次静态库的实践,这个.c文件就相当于那些add.c、sub.c...10-1的代码相当于那个main函数,所以可以将其做成一个静态库来使用,但是又出现了新问题。

最后得到解决方案。因为csapp.c中有关于线程的头文件,所以需要加上-lpthread,之后就可以运行教材上的代码了。

代码托管

信息安全系统设计基础》第八周课下作业

作业内容:

1、完成家庭作业4.47,4.48,4.49。

2、相应代码反汇编成X86-64汇编。

3、把上述X68-64汇编翻译成Y86-64汇编,并给出相应机器码。

作业4.47

A、书写一个C版本的冒泡排序法,用指针引用数组元素,而不是数组索引。
A:答 写出的函数如下所示:

void bubble_a(int *data, int count){  
    int i,next;  
    for(next = 1; next < count; next++){  
        for(i = next - 1; i >= 0; i--)  
            if(*(data + i + 1) < *(data + i)){  
                int t = *(data + i + 1);  
                *(data + i + 1) = *(data + i);  
                *(data + i) = t;  
            }  
    }  
}  

测试代码如下:

#include<stdio.h>
void mybubble(int *data,int count){
	int i,j;
	int *p,*q;
	for(i=count-1;i!=0;i--){
		p = data,q = data + 1;
		for(j=0;j!=i;++j)
		{
			if(*p>*q)
			{
				int t = *p;
				*p = *q;
				*q = t;
			}
			p++,q++;
		}
	} 
}

int main(void){
	int count,i,a[20];
	printf("How many numbers do you want to input:\n");
	scanf("%d",&count);
	printf("Please input %d numbers:\n",count);
	for(i=0;i<count;i++)
	{
		scanf("%d",&a[i]);
	} 
	mybubble(a,count);
	for(i=0;i<count;i++)
	{
		printf("%d ",a[i]);
	}
	printf("\n");
}

得出结果如下图:
图片2

B、书写并测试一个由这个函数和测试代码组成的Y86-64程序。

main:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $48, %rsp
    movq    %fs:40, %rax
    movq    %rax, -8(%rbp)
    xorl    %eax, %eax
    movl    $4, -32(%rbp)
    movl    $3, -28(%rbp)
    movl    $2, -24(%rbp)
    movl    $1, -20(%rbp)
    movl    $0, -16(%rbp)
    leaq    -32(%rbp), %rax
    movl    $5, %esi
    movq    %rax, %rdi
    movl    $0, %eax
    call    bubble_a
    movl    $0, -36(%rbp)
    jmp .L2
.L3:
    movl    -36(%rbp), %eax
    cltq
    movl    -32(%rbp,%rax,4), %eax
    movl    %eax, %esi
    movl    $.LC0, %edi
    movl    $0, %eax
    call    printf
    addl    $1, -36(%rbp)
.L2:
    cmpl    $4, -36(%rbp)
    jle .L3
    movl    $10, %edi
    call    putchar
    nop
    movq    -8(%rbp), %rax
    xorq    %fs:40, %rax
    je  .L4
    call    __stack_chk_fail
.L4:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   main, .-main
    .globl  bubble_a
    .type   bubble_a, @function
  • Y86-64程序如下:
bubble_b:  
.LFB22:  
    .cfi_startproc  
    pushl   %edi  
    .cfi_def_cfa_offset 8  
    .cfi_offset 7, -8  
    pushl   %esi  
    .cfi_def_cfa_offset 12  
    .cfi_offset 6, -12  
    pushl   %ebx  
    .cfi_def_cfa_offset 16  
    .cfi_offset 3, -16  
    mrmovl   16(%esp), %edx  
    mrmovl   20(%esp), %edi  
    irmovl   $1, %eax  
    subl     %eax, %edi  
    jle      .L1  
    subl     $1, %edi  
    irmovl   $0, %esi  
.L6:  
    rrmovl   %esi, %eax  
    irmovl   $0 , ebx   
    subl     %ebx, %esi  
    jl       .L3  
.L7:  
    rrmovl   %eax, %ecx  
    addl     %ecx, %ecx  
    addl     %ecx, %ecx  
    addl     %edx, %ecx  
    mrmovl   4(%ecx), %ecx  
    rrmovl   %eax, %ebx  
    addl     %ecx, %ebx  
    addl     %ecx, %ebx  
    addl     %edx, %ebx  
    mrmovl   (%ebx), %ebx  
    subl     %ebx, %ecx  
    jge     .L4  
    addl     %eax, %eax  
    addl     %eax, %eax  
    addl     %edx, %eax  
    rmmovl   %ebx, 4(%eax)  
    addl     %eax, %eax  
    addl     %eax, %eax  
    addl     %edx, %eax  
    rmmovl   %ecx, 4(%eax)  
.L4:  
    subl    $1, %eax  
    irmovl  $-1, %edx  
    subl    %edx, %eax  
    jne .L7  
.L3:  
    addl    $1, %esi  
    subl    %edi, %esi  
    jne .L6  
.L1:  
    popl    %ebx  
    .cfi_def_cfa_offset 12  
    .cfi_restore 3  
    popl    %esi  
    .cfi_def_cfa_offset 8  
    .cfi_restore 6  
    popl    %edi  
  
    .cfi_def_cfa_offset 4  
    .cfi_restore 7  
    ret  
    .cfi_endproc  
.LFE22:  
    .size   bubble_b, .-bubble_b  
    .section    .rodata.str1.1,"aMS",@progbits,1   

Y86模拟器使用过程

安装完成后
在sim文件夹下右键选择在终端中打开,输入
make clean;make
如图4:

若是想多练习的话,方法是在文件夹y86-code中新建文档输入练习题4.1(练习题4.1:
确定下面的Y86指令序列的字节编码。.pos 0x100表明这段代码的起始地址应该是0x100)的代码,命名文4-1.ys,在文件中右键选择在终端中打开,首先make clean一下,然后这时只剩.ys文件了,然后可以make 4-1.yo,然后可以cat P4-1.yo查看,如下图所示,make all可以汇编运行所有代码。
即可得到

irmovl $15,%ebx               ##30f30f000000
rrmovl %ebx,%ecx              ##2031
loop:               
rmmovl %ecx,-3(%ebx)          ##4013fdffffff  
addl %ebx,%ecx                ##6031
jmp loop   
条件传送指令

实现条件操作的传统方法是利用控制的条件转移。这种机制简单而通用,但是在现代处理器上,它可能回非常的低效率。
数据的条件转移是一种替代的策略。这种方法先计算一个条件操作的两种结果,然后再根据条件是否满足从而选取一个。只有在一些受限制的情况下,这种策略才可行,但是如果可行,就可以用一条简单的条件传送指令来实现它。条件传送指令更好地匹配了现代处理器的性能特征。
基于条件数据传送的代码比基于条件控制转移的代码性能好,原因和现代处理器如何运行有关。(流水线)

4.48实现冒泡排序,要求不使用跳转,且最多使用3次条件传送。

void bubble_c(int *data,int count)
{
    int i , next;
    int pre_ele,next_ele;
    for(next = 1;next < count;next++)
    {
        for(i = next -1;i >= 0;i--)
        {
            pre_ele = *(data + i);
            next_ele = *(data + i + 1);
            *(data + i) = next_ele < pre_ele ? next_ele : pre_ele;
            *(data + i + 1) = next_ele < pre_ele ? pre_ele : next_ele;//使用两次条件传送
        }
    }
}

X86-64机器码:

000000000000009e <bubble_c>:
  9e:   55                      push   %rbp
  9f:   48 89 e5                mov    %rsp,%rbp
  a2:   48 89 7d e8             mov    %rdi,-0x18(%rbp)
  a6:   89 75 e4                mov    %esi,-0x1c(%rbp)
  a9:   c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)
  b0:   e9 97 00 00 00          jmpq   14c <bubble_c+0xae>
  b5:   8b 45 f4                mov    -0xc(%rbp),%eax
  b8:   83 e8 01                sub    $0x1,%eax
  bb:   89 45 f0                mov    %eax,-0x10(%rbp)
  be:   eb 7e                   jmp    13e <bubble_c+0xa0>
  c0:   8b 45 f0                mov    -0x10(%rbp),%eax
  c3:   48 98                   cltq   
  c5:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
  cc:   00 
  cd:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  d1:   48 01 d0                add    %rdx,%rax
  d4:   8b 00                   mov    (%rax),%eax
  d6:   89 45 f8                mov    %eax,-0x8(%rbp)
  d9:   8b 45 f0                mov    -0x10(%rbp),%eax
  dc:   48 98                   cltq   
  de:   48 83 c0 01             add    $0x1,%rax
  e2:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
  e9:   00 
  ea:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  ee:   48 01 d0                add    %rdx,%rax
  f1:   8b 00                   mov    (%rax),%eax
  f3:   89 45 fc                mov    %eax,-0x4(%rbp)
  f6:   8b 45 f0                mov    -0x10(%rbp),%eax
  f9:   48 98                   cltq   
  fb:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
 102:   00 
 103:   48 8b 45 e8             mov    -0x18(%rbp),%rax
 107:   48 01 c2                add    %rax,%rdx
 10a:   8b 45 fc                mov    -0x4(%rbp),%eax
 10d:   39 45 f8                cmp    %eax,-0x8(%rbp)
 110:   0f 4e 45 f8             cmovle -0x8(%rbp),%eax
 114:   89 02                   mov    %eax,(%rdx)
 116:   8b 45 f0                mov    -0x10(%rbp),%eax
 119:   48 98                   cltq   
 11b:   48 83 c0 01             add    $0x1,%rax
 11f:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
 126:   00 
 127:   48 8b 45 e8             mov    -0x18(%rbp),%rax
 12b:   48 01 c2                add    %rax,%rdx
 12e:   8b 45 f8                mov    -0x8(%rbp),%eax
 131:   39 45 fc                cmp    %eax,-0x4(%rbp)
 134:   0f 4d 45 fc             cmovge -0x4(%rbp),%eax
 138:   89 02                   mov    %eax,(%rdx)
 13a:   83 6d f0 01             subl   $0x1,-0x10(%rbp)
 13e:   83 7d f0 00             cmpl   $0x0,-0x10(%rbp)
 142:   0f 89 78 ff ff ff       jns    c0 <bubble_c+0x22>
 148:   83 45 f4 01             addl   $0x1,-0xc(%rbp)
 14c:   8b 45 f4                mov    -0xc(%rbp),%eax
 14f:   3b 45 e4                cmp    -0x1c(%rbp),%eax
 152:   0f 8c 5d ff ff ff       jl     b5 <bubble_c+0x17>
 158:   90                      nop
 159:   5d                      pop    %rbp
 15a:   c3                      retq   

Y86-64汇编代码:

bubble_b:  
.LFB22:  
    .cfi_startproc  
    pushl   %edi  
    .cfi_def_cfa_offset 8  
    .cfi_offset 7, -8  
    pushl   %esi  
    .cfi_def_cfa_offset 12  
    .cfi_offset 6, -12  
    pushl   %ebx  
    .cfi_def_cfa_offset 16  
    .cfi_offset 3, -16  
    mrmovl   16(%esp), %edx  
    mrmovl   20(%esp), %edi  
    irmovl   $1, %eax  
    subl     %eax, %edi  
    jle      .L1  
    subl     $1, %edi  
    irmovl   $0, %esi  
.L6:  
    movl    (%ebx,%eax,4), %edx  
    movl    4(%ebx,%eax,4), %ecx  
    cmpl    %edx, %ecx  
    movl    %edx, %ebp  
    cmovle  %ecx, %ebp  
    movl    %ebp, (%ebx,%eax,4)  
    cmovge  %ecx, %edx  
    movl    %edx, 4(%ebx,%eax,4)  
    subl    $1, %eax  
    cmpl    $-1, %eax  
    jne .L6  
.L7:  
    rrmovl   %eax, %ecx  
    addl     %ecx, %ecx  
    addl     %ecx, %ecx  
    addl     %edx, %ecx  
    mrmovl   4(%ecx), %ecx  
    rrmovl   %eax, %ebx  
    addl     %ecx, %ebx  
    addl     %ecx, %ebx  
    addl     %edx, %ebx  
    mrmovl   (%ebx), %ebx  
    subl     %ebx, %ecx  
    jge     .L4  
    addl     %eax, %eax  
    addl     %eax, %eax  
    addl     %edx, %eax  
    rmmovl   %ebx, 4(%eax)  
    addl     %eax, %eax  
    addl     %eax, %eax  
    addl     %edx, %eax  
    rmmovl   %ecx, 4(%eax)  
.L4:  
    subl    $1, %eax  
    irmovl  $-1, %edx  
    subl    %edx, %eax  
    jne .L7  
.L3:  
    addl    $1, %esi  
    subl    %edi, %esi  
    jne .L6  
.L1:  
    popl    %ebx  
    .cfi_def_cfa_offset 12  
    .cfi_restore 3  
    popl    %esi  
    .cfi_def_cfa_offset 8  
    .cfi_restore 6  
    popl    %edi  
  
    .cfi_def_cfa_offset 4  
    .cfi_restore 7  
    ret  
    .cfi_endproc  
.LFE22:  
    .size   bubble_b, .-bubble_b  
    .section    .rodata.str1.1,"aMS",@progbits,1

4.49实现冒泡排序,要求不使用跳转,且最多使用1次条件传送。

void bubble_c(int *data,int count)  
{  
    int i , next;  
    int pre_ele,next_ele;  
    for(next = 1;next < count;next++)  
    {  
        for(i = next -1;i >= 0;i--)  
        {  
            pre_ele = *(data + i);  
            next_ele = *(data + i + 1);  
            *(data + i) = next_ele < pre_ele ? next_ele : pre_ele;  
            *(data + i + 1) = next_ele < pre_ele ? pre_ele : next_ele;   
        }  
    }  
}  

X86-64汇编代码如下:

bubble_c:
.LFB3:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -24(%rbp)
    movl    %esi, -28(%rbp)
    movl    $1, -12(%rbp)
    jmp .L6
.L9:
    movl    -12(%rbp), %eax
    subl    $1, %eax
    movl    %eax, -16(%rbp)
    jmp .L7
.L8:
    movl    -16(%rbp), %eax
    cltq
    leaq    0(,%rax,4), %rdx
    movq    -24(%rbp), %rax
    addq    %rdx, %rax
    movl    (%rax), %eax
    movl    %eax, -8(%rbp)
    movl    -16(%rbp), %eax
    cltq
    addq    $1, %rax
    leaq    0(,%rax,4), %rdx
    movq    -24(%rbp), %rax
    addq    %rdx, %rax
    movl    (%rax), %eax
    movl    %eax, -4(%rbp)
    movl    -16(%rbp), %eax
    cltq
    leaq    0(,%rax,4), %rdx
    movq    -24(%rbp), %rax
    addq    %rax, %rdx
    movl    -4(%rbp), %eax
    cmpl    %eax, -8(%rbp)
    cmovle  -8(%rbp), %eax
    movl    %eax, (%rdx)
    movl    -16(%rbp), %eax
    cltq
    addq    $1, %rax
    leaq    0(,%rax,4), %rdx
    movq    -24(%rbp), %rax
    addq    %rax, %rdx
    movl    -8(%rbp), %eax
    movl    %eax, (%rdx)
    subl    $1, -16(%rbp)
.L7:
    cmpl    $0, -16(%rbp)
    jns .L8
    addl    $1, -12(%rbp)
.L6:
    movl    -12(%rbp), %eax
    cmpl    -28(%rbp), %eax
    jl  .L9
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE3:
    .size   bubble_c, .-bubble_c
    .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
    .section    .note.GNU-stack,"",@progbits

X86-64机器码:

000000000000094 <bubble_c>:
  94:   55                      push   %rbp
  95:   48 89 e5                mov    %rsp,%rbp
  98:   48 89 7d e8             mov    %rdi,-0x18(%rbp)
  9c:   89 75 e4                mov    %esi,-0x1c(%rbp)
  9f:   c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%rbp)
  a6:   e9 8c 00 00 00          jmpq   137 <bubble_c+0xa3>
  ab:   8b 45 f4                mov    -0xc(%rbp),%eax
  ae:   83 e8 01                sub    $0x1,%eax
  b1:   89 45 f0                mov    %eax,-0x10(%rbp)
  b4:   eb 77                   jmp    12d <bubble_c+0x99>
  b6:   8b 45 f0                mov    -0x10(%rbp),%eax
  b9:   48 98                   cltq   
  bb:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
  c2:   00 
  c3:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  c7:   48 01 d0                add    %rdx,%rax
  ca:   8b 00                   mov    (%rax),%eax
  cc:   89 45 f8                mov    %eax,-0x8(%rbp)
  cf:   8b 45 f0                mov    -0x10(%rbp),%eax
  d2:   48 98                   cltq   
  d4:   48 83 c0 01             add    $0x1,%rax
  d8:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
  df:   00 
  e0:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  e4:   48 01 d0                add    %rdx,%rax
  e7:   8b 00                   mov    (%rax),%eax
  e9:   89 45 fc                mov    %eax,-0x4(%rbp)
  ec:   8b 45 f0                mov    -0x10(%rbp),%eax
  ef:   48 98                   cltq   
  f1:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
  f8:   00 
  f9:   48 8b 45 e8             mov    -0x18(%rbp),%rax
  fd:   48 01 c2                add    %rax,%rdx
 100:   8b 45 fc                mov    -0x4(%rbp),%eax
 103:   39 45 f8                cmp    %eax,-0x8(%rbp)
 106:   0f 4e 45 f8             cmovle -0x8(%rbp),%eax
 10a:   89 02                   mov    %eax,(%rdx)
 10c:   8b 45 f0                mov    -0x10(%rbp),%eax
 10f:   48 98                   cltq   
 111:   48 83 c0 01             add    $0x1,%rax
 115:   48 8d 14 85 00 00 00    lea    0x0(,%rax,4),%rdx
 11c:   00 
 11d:   48 8b 45 e8             mov    -0x18(%rbp),%rax
 121:   48 01 c2                add    %rax,%rdx
 124:   8b 45 f8                mov    -0x8(%rbp),%eax
 127:   89 02                   mov    %eax,(%rdx)
 129:   83 6d f0 01             subl   $0x1,-0x10(%rbp)
 12d:   83 7d f0 00             cmpl   $0x0,-0x10(%rbp)
 131:   79 83                   jns    b6 <bubble_c+0x22>
 133:   83 45 f4 01             addl   $0x1,-0xc(%rbp)
 137:   8b 45 f4                mov    -0xc(%rbp),%eax
 13a:   3b 45 e4                cmp    -0x1c(%rbp),%eax
 13d:   0f 8c 68 ff ff ff       jl     ab <bubble_c+0x17>
 143:   90                      nop
 144:   5d                      pop    %rbp
 145:   c3                      retq
  • Y86代码:
.L6:  
    movl    (%ebx,%eax,4), %edx  
    movl    4(%ebx,%eax,4), %ecx  
    cmpl    %edx, %ecx  
    movl    %edx, %ebp  
    cmovle  %ecx, %ebp  
    movl    %ebp, (%ebx,%eax,4)  
    cmovge  %ecx, %edx  
    movl    %edx, 4(%ebx,%eax,4)  
    subl    $1, %eax  
    cmpl    $-1, %eax  
    jne .L6 

作业内容:

把daytime服务器分别用多进程和多线程实现成并发服务器并测试。

多进程实现daytime服务器
实现代码:
server:

client:

多线程和多进程的区别

一.为何需要多进程(或者多线程),为何需要并发?

并发技术,就是可以让你在同一时间同时执行多条任务的技术。你的代码将不仅仅是从上到下,从左到右这样规规矩矩的一条线执行。你可以一条线在main函数里跟你的客户交流,另一条线,你早就把你外卖送到了其他客户的手里。进程,在一定的环境下,把静态的程序代码运行起来,通过使用不同的资源,来完成一定的任务。比如说,进程的环境包括环境变量,进程所掌控的资源,有中央处理器,有内存,打开的文件,映射的网络端口等等。一个系统中,有很多进程,它们都会使用内存。为了确保内存不被别人使用,每个进程所能访问的内存都是圈好的。进程需要管理好它的资源。

进程和线程不是同一个层面上的概念,线程是进程的一部分,线程主抓中央处理器执行代码的过程,其余的资源的保护和管理由整个进程去完成。

所以,为何需要并发?因为我们需要更强大的功能,提供更多的服务,所以并发,必不可少。

多进程

进程是程序执行时的一个实例,即它是程序已经执行到课中程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。

多线程

线程就是把一个进程分为很多片,每一片都可以是一个独立的流程。这已经明显不同于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成很多条小溪。它没有拷贝这些额外的开销,但是仅仅是现存的一条河流,就被多线程技术几乎无开销地转成很多条小流程,它的伟大就在于它少之又少的系统开销。

线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

优点:

1.提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。

2.使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。

3.改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

int pthread_create(pthread_t *restrict tidp,
                   const pthread_attr_t *restrict attr,
                   void *(*start_rtn)(void), 
                   void *restrict arg);

Returns: 0 if OK, error number on failure

第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。

多线程还是多进程的选择

进程是资源分配的最小单位,线程是CPU调度的最小单位。看下图:

1)需要频繁创建销毁的优先用线程
原因请看上面的对比。
这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

2)需要进行大量计算的优先使用线程
所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。
这种原则最常见的是图像处理、算法处理。

3)强相关的处理用线程,弱相关的处理用进程
什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。
一般的Server需要完成如下任务:消息收发、消息处理。“消息收发”和“消息处理”就是弱相关的任务,而“消息处理”里面可能又分为“消息解码”、“业务处理”,这两个任务相对来说相关性就要强多了。因此“消息收发”和“消息处理”可以分进程设计,“消息解码”、“业务处理”可以分线程设计。
当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

4)可能要扩展到多机分布的用进程,多核分布的用线程
原因请看上面对比。
5)都满足需求的情况下,用你最熟悉、最拿手的方式
至于“数据共享、同步”、“编程、调试”、“可靠性”这几个维度的所谓的“复杂、简单”应该怎么取舍,我只能说:没有明确的选择方法。但我可以告诉你一个选择原则:如果多进程和多线程都能够满足要求,那么选择你最熟悉、最拿手的那个。

需要提醒的是:虽然我给了这么多的选择原则,但实际应用中基本上都是“进程+线程”的结合方式,千万不要真的陷入一种非此即彼的误区。

多线程代码如下

客户端:

void process(FILE *fp,int sockfd);
char *getMessage(char *sendline,int len,FILE *fp);
int main(int argc,char *argv[])
{
    int fd;
    struct hostent *he;
    struct sockaddr_in server;
    if(argc!=2)
    {
        printf("Usage: %s <IP Address>\n",argv[0]);
        exit(1);
    }
    if((he=gethostbyname(argv[1]))==NULL)
    {
        printf("gethostbyname error.\n");
        exit(1);
    }
    if((fd=socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        perror("socket() error.\n");
        exit(1);
    }
    bzero(&server,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(PORT);
    server.sin_addr=*((struct in_addr *)he->h_addr);
    if(connect(fd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1)
    {
        perror("connect() error.\n");
        exit(1);
    }
    process(stdin,fd);
    close(fd);
    return 0;
}
void process(FILE *fp,int sockfd)
{
    char sendbuf[MAXDATASIZE];
    char recvbuf[MAXDATASIZE];
    int num;
    time_t t;
    //t=time(NULL);
    printf("用户名:\n");
    if(fgets(sendbuf,MAXDATASIZE,fp)==NULL)
    {
        printf("lease enter your name,now you have exit.\n");
        return;
    }
    
    send(sockfd,sendbuf,strlen(sendbuf),0);
    while(getMessage(sendbuf,MAXDATASIZE,fp)!=NULL)
    {
        send(sockfd,sendbuf,strlen(sendbuf),0);
        t=time(NULL);
        if((num=recv(sockfd,recvbuf,MAXDATASIZE,0))==0)
        {
            printf("Server no send you any data.\n");
            return;
        }
        recvbuf[num]='\0';
        printf("服务器消息:%s\n",ctime(&t));
    }
    printf("Exit.\n");
}
char *getMessage(char *sendline,int len,FILE *fp)
{
    printf("请输入文字:\n");
    return(fgets(sendline,len,fp));
}

服务器:

void process_cli(int connectfd,struct sockaddr_in client);
void sig_handler(int s);
int main()
{
    int opt,listenfd,connectfd;
    pid_t pid;
    struct sockaddr_in server;
    struct sockaddr_in client;
    int sin_size;
    struct sigaction act;
    struct sigaction oact;
    act.sa_handler=sig_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    printf("服务器实现者学号:20155219\n");
    if(sigaction(SIGCHLD,&act,&oact)<0)
    {
        perror("Sigaction failed!\n");
        exit(1);
    }
    if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        perror("Creating socket failed.\n");
        exit(1);
    }
    opt=SO_REUSEADDR;
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    bzero(&server,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(PORT);
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1)
    {
        perror("Bind error.\n");
        exit(1);
    }
    if(listen(listenfd,BACKLOG)==-1)
    {
        perror("listen() error.\n");
        exit(1);
    }
    sin_size=sizeof(struct sockaddr_in);
    while(1)
    {
        if((connectfd=accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1)
        {
            if(errno==EINTR) continue;
            perror("accept() error.\n");
            exit(1);
        }
        if((pid=fork())>0)
        {
            close(connectfd);
            continue;
        }
        else if(pid==0)
        {
            close(listenfd);
            process_cli(connectfd,client);
            exit(0);
        }
        else
        {
            printf("fork error.\n");
            exit(1);
        }
    }
    close(listenfd);
    return 0;
}
void process_cli(int connectfd,struct sockaddr_in client)
{
    int i,num;
    char recvbuf[MAXDATASIZE];
    char sendbuf[MAXDATASIZE];
    char cli_name[MAXDATASIZE];
    time_t t;
    //t=time(NULL);
    printf("你被用户 %s 联系。\n",inet_ntoa(client.sin_addr));
    
    num=recv(connectfd,cli_name,MAXDATASIZE,0);
    if(num==0)
    {
        close(connectfd);
        printf("Client disconnected.\n");
        return;
    }
    send(connectfd,(void *)&t,sizeof(time_t),0);
    while(num=recv(connectfd,recvbuf,MAXDATASIZE,0))
    {
        recvbuf[num]='\0';
        t=time(NULL);
        printf("当前时间:%s\n",ctime(&t));
        //send(connectfd,(void *)&t,sizeof(time_t),0);
    }
    
    close(connectfd);
}
void sig_handler(int s)
{
    pid_t pid;
    int stat;
    while((pid=waitpid(-1,&stat,WNOHANG))>0)
        printf("子进程 %d 关闭。\n",pid);
    return;
}

多进程代码:

客户端


void talk_to_server(char ** argv)
{
    //设置一个socket地址结构client_addr,代表客户机internet地址, 端口
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr)); //把一段内存区的内容全部设置为0
    client_addr.sin_family = AF_INET;    //internet协议族
    client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY表示自动获取本机地址
    client_addr.sin_port = htons(0);    //0表示让系统自动分配一个空闲端口
    //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket
    int client_socket = socket(AF_INET,SOCK_STREAM,0);
    time_t t;
    if( client_socket < 0)
    {
        printf("Create Socket Failed!\n");
        exit(1);
    }
    //把客户机的socket和客户机的socket地址结构联系起来
    if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr)))
    {
        printf("Client Bind Port Failed!\n"); 
        exit(1);
    }

    //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    if(inet_aton(argv[1],&server_addr.sin_addr) == 0) //服务器的IP地址来自程序的参数
    {
        printf("Server IP Address Error!\n");
        exit(1);
    }
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
    socklen_t server_addr_length = sizeof(server_addr);
    //向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接
    if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)
    {
        printf("Can Not Connect To %s!\n",argv[1]);
        exit(1);
    }

    char buffer[BUFFER_SIZE];
    bzero(buffer,BUFFER_SIZE);
    //从服务器接收数据到buffer中
    int length = recv(client_socket,buffer,BUFFER_SIZE,0);
    if(length < 0)
    {
        printf("Recieve Data From Server %s Failed!\n", argv[1]);
        exit(1);
    }
    printf("From Server %s :\t%s\n",argv[1],buffer);
    length = recv(client_socket,(void *)&t,sizeof(time_t),0);
    if(length < 0)
    {
        printf("Recieve Data From Server %s Failed!\n", argv[1]);
        exit(1);
    }
    printf("当前时间: %s \n",ctime(&t));
    bzero(buffer,BUFFER_SIZE);
    
    //向服务器发送buffer中的数据
    send(client_socket,buffer,BUFFER_SIZE,0);
    //关闭socket
    close(client_socket);
}
int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: .\%s ServerIPAddress\n",argv[0]);
        exit(1);
    }
    talk_to_server(argv);

    return 0;
}


服务器

void reaper(int sig)
{
    int status;
    //调用wait3读取子进程的返回值,使zombie状态的子进程彻底释放
    while(wait3(&status,WNOHANG,(struct rusage*)0) >=0)
        ;
}
int main(int argc, char **argv)
{
    //设置一个socket地址结构server_addr,代表服务器internet地址, 端口
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr)); //把一段内存区的内容全部设置为0
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);

    time_t t;
    t=time(NULL);

    printf("服务器开始工作!\n");

    //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket
    int server_socket = socket(AF_INET,SOCK_STREAM,0);
    if( server_socket < 0)
    {
        printf("Create Socket Failed!\n");
        exit(1);
    }
    
    //把socket和socket地址结构联系起来
    if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
    {
        printf("Server Bind Port : %d Failed!\n", HELLO_WORLD_SERVER_PORT); 
        exit(1);
    }
    
    //server_socket用于监听
    if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
    {
        printf("Server Listen Failed!"); 
        exit(1);
    }
    //通知操作系统,当收到子进程的退出信号(SIGCHLD)时,执行reaper函数,释放zombie状态的进程
    (void)signal(SIGCHLD,reaper);
    
    while (1) //服务器端要一直运行
    {
        //定义客户端的socket地址结构client_addr
        struct sockaddr_in client_addr;
        socklen_t length = sizeof(client_addr);

        //接受一个到server_socket代表的socket的一个连接
        //如果没有连接请求,就等待到有连接请求--这是accept函数的特性
        //accept函数返回一个新的socket,这个socket(new_server_socket)用于同连接到的客户的通信
        //new_server_socket代表了服务器和客户端之间的一个通信通道
        //accept函数把连接到的客户端信息填写到客户端的socket地址结构client_addr中

        int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
        if ( new_server_socket < 0)
        {
            printf("Server Accept Failed!\n");
            break;
        }
        int child_process_pid = fork(); //fork()后,子进程是主进程的拷贝
        //在主进程和子进程中的区别是fork()的返回值不同.
        if(child_process_pid == 0 )//如果当前进程是子进程,就执行与客户端的交互
        {
            close(server_socket); //子进程中不需要被复制过来的server_socket
            char buffer[BUFFER_SIZE];
            bzero(buffer, BUFFER_SIZE);
            strcpy(buffer,"20155219实现");
            strcat(buffer,"\n"); //C语言字符串连接              
            
            //发送buffer中的字符串到new_server_socket,实际是给客户端
            send(new_server_socket,buffer,BUFFER_SIZE,0);
            printf("服务器实现者学号:20155219\n");
            printf("当前时间: %s\n",ctime(&t));
            send(new_server_socket,(void *)&t,sizeof(time_t),0);

            bzero(buffer,BUFFER_SIZE);
            //接收客户端发送来的信息到buffer中
            length = recv(new_server_socket,buffer,BUFFER_SIZE,0);
            if (length < 0)
            {
                printf("Server Recieve Data Failed!\n");
                exit(1);
            }
            printf("\n%s\n",buffer);
            //关闭与客户端的连接
            close(new_server_socket); 
            exit(0);         
        }
        else if(child_process_pid > 0)     //如果当前进程是主进程 
            close(new_server_socket);    //主进程中不需要用于同客户端交互的new_server_socket
    }
    //关闭监听用的socket
    close(server_socket);
    return 0;
}


遇到的问题如下:

其他(感悟、思考等,可选)

本周我学习了课本上的第十二章,书面上了解并发的相关知识——进程、线程、I/O多路复用……使我对于并发是怎么进行的有了一个大概的理解,能够理解这种并发进行的思想。然后,结合老师上传的代码,我又掌握了对于多线程的程序的编译运行的方法。

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 200/200 2/2 20/20 了解计算机系统、静态链接与动态链接
第三周 300/500 2/4 18/38 深入学习计算机算术运算的特性
第四周 500/1000 3/7 22/60 掌握程序崩溃处理、Linux系统编程等知识,利用所学知识优化myod,并实现head和tail命令
第五周 300/1300 2/9 10/70 掌握“进程”的概念,并学习应用相关函数;了解程序如何在机器上表示
第六周 500/1869 4/11 14/84 学习了异常控制流
第七周 380/2290 2/13 14/98 学习了Y86模拟器及相关知识
第八周 400/2690 1/14 12/110 学习了网络编程、并发、进程、多线程
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。

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

  • 计划学习时间:10小时

  • 实际学习时间:14小时

  • 改进情况:

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

参考资料