2017-2018-1 《信息安全系统设计基础》实验三报告

2017-2018-1 《信息安全系统设计基础》实验三报告

本小组成员:2015530320155213

————————CONTENTS————————


任务一 C语言模拟wc命令

使用man wc命令查看wc命令的基本用法:

可知wc命令的功能为:统计指定文件中的字节数、字数、行数等,并将统计结果显示输出。常用的参数为:

  • -c:统计字节数
  • -l:统计行数
  • -m:统计字符数,且不能与-c参数一起使用
  • -w:统计字数,一个字被定义为由空白、跳格或换行字符分割的字符串
  • -L:打印最长行的长度
  • ......

但是,如果我们想统计某文件中出现过某个特定单词的行数,只用wc命令是无法完成的。我们可以借助管道将wc命令与其他命令(如grep)串联起来:

grep and test.txt | wc -l

上面命令实现了查找test.txt中所有出现过“and”这个单词的行,并统计行数。

再进一步,如果想精确到个数(比如一行出现两次,算作2),可以加上参数-o选项(only),表示只选中那些匹配的地方,结果为:

基于以上分析,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void wc_func(char *file,int ism,int isw,int isl);
int main(int argc ,char *argv[])

{
	int ism,isw,isl,opt;
	ism = isw = isl = 0;
	int count = 0;
	while((opt=getopt(argc,argv,"mwl"))!=-1)
	{
		count++;
		switch(opt)
		{
			case 'm':
				ism=1;
				break;
			case 'w':
				isw=1;
				break;
			case 'l':
				isl=1;
				break;
			case '?':
				printf("请查看该指令说明文档 %c\n",optopt);
				exit(0);
		}
	}	
	if(count==0)
	{
		ism=isw=isl=1;
	}
	if(optind==argc)
	{
		printf("wc error: have no file!\n");
	}
	for(;optind<argc;optind++)
	{
		wc_func(argv[optind],ism,isw,isl);
	}
}
void wc_func(char *file,int ism,int isw,int isl)
{
	int t,m,w,l;
	int state = 0;
	FILE *in;
	if((in = fopen(file,"r"))==NULL)
	{
		printf("wc %s:no this file or dir\n",file);
		return;
	}
	w=m=l=0;
	while((t=fgetc(in))!=EOF)
	{
		/*if(t=='\t'||t==' ')
		{	
			w++;
		}
		else if(t=='\n')
		{
			l++;
		}*/
		if(t == '\n') {
            		l++;
            		state = 0;
            		continue;
        	} else if(t == ' ') {
            		state = 0;
            		continue;
        	} else if(t == '\r') {
            		state = 0;
            		continue;
        	} else {
            		if(state == 0) {
                	state = 1;
                	w++;
           		}
            		continue;
        	}
		m++;	
	}
	if(isl)
		printf("%-5d",l);
	if(isw)
		printf("%-5d",w);
	if(ism)
		printf("%-5d",m);
	printf("%-10s\n",file);
}

运行结果如下:

返回目录


任务二 实现传送文本文件的服务器和客户端

虽然在网络安全编程基础课程上学习过网络编程的相关知识,但基于的是Windows。将其移植到Linux下时需要注意以下几个方面:

  • 头文件

Windows下winsock.h或winsock2.h;

Linux下netinet/in.h(包括大部分),unistd.h(包括close函数),sys/socket.h。

  • 初始化

windows下需要用WSAStartup启动Ws2_32.lib;

linux下不需要。

  • 关闭socket

windows下使用closesocket();

linux下使用close()。

  • 类型

windows下SOCKET;

linux下int。

  • 多线程(下一个任务会用到)

windows下包含process.h,使用_beginthread和_endthread;

linux下包含pthread.h,使用pthread_create和pthread_exit。

  • ......

以上是我在移植过程中遇到的问题,需格外注意。更多情况可参考Socket程序从windows移植到linux的注意事项等相关文章。

基于客户端与服务器的通信流程,可分别写出创建服务器和客户端,以及客户端和服务器连接的代码:

/*创建服务器:*/
int start_server(int port, int type){
    //建立服务器套接字
    int ss = socket(AF_INET, type, 0);
    if(ss < 0){
        printf("create socket error\n");
        return -1;
    }

    //设置服务器地址
    struct sockaddr_in server_addr; //服务器地址结构
    bzero(&server_addr, sizeof(struct sockaddr_in)); //清零
    server_addr.sin_family = AF_INET; //协议族
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //ip地址
    server_addr.sin_port = htons(port); //端口
    //绑定地址结构到套接字描述符
    if(bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){
        printf("bind error\n");
        return -1;
    }
    //TCP
    if(SOCK_STREAM == type){
        //设置侦听
        if(listen(ss, LISTEN_SIZE) < 0){
            printf("listen error\n");
            return -1;
        }
        printf("tcp server start\n");
    }
    else
        printf("udp server start\n");
    return ss;
}

int create_tcp_server(int port){
    start_server(port, SOCK_STREAM);
}

int create_udp_server(int port){
    start_server(port, SOCK_DGRAM);
}
/*接受客户端连接:*/
socklen_t addrlen = sizeof(struct sockaddr);
struct sockaddr_in client_addr; //客户端地址结构
client_sock = accept(ss, (struct sockaddr*)&client_addr, &addrlen);
if(client_sock < 0){
       printf("accept error\n");
}
       printf("accept success\n");
/*客户端:*/
int connectsock(char* server_ip, int server_port, int type){
	int sock_fd = socket(AF_INET, type, 0);
	if(-1 == sock_fd){
	    printf("create socket error\n");
	    return -1;
	}
	struct sockaddr_in server_addr;
	//设置服务器地址
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	server_addr.sin_port = htons(server_port);
	inet_pton(AF_INET, server_ip, &server_addr.sin_addr);
	
	//连接服务器
	if(-1 == connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in))){
	    printf("connect server error\n");
	    return -1;
	}
	
	printf("connect server success\n");
	return sock_fd;
}
int connect_tcp(char* server_ip, int server_port){
    return connectsock(server_ip, server_port, SOCK_STREAM);
}
int connect_udp(char* server_ip, int server_port){
    return connectsock(server_ip, server_port, SOCK_DGRAM);
}

搭建好客户端和服务器,接下来考虑如何传输文件。

传输数据需要用到缓冲区。客户端首先输入文件名,并判断该文件是否存在。若不存在,则返回错误提示;存在,则发送至服务器。服务器接收到数据后,创建一个同样名称的文件。至此,完成了最基础的一步。

传输文件内容的思路上面类似,但需注意:文件名称一般很短,但文件内容大小并不确定。所以不要奢望一次性传输完所有的数据,而应设置循环,以固定大小传输数据,一直到传输结束。

传输数据部分的代码如下:

/*客户端:*/
char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name, FILE_NAME_MAX_SIZE+1);
    printf("Please Input File Name On Server:\t");
    scanf("%s", file_name);
    
    char buffer[BUFFER_SIZE];
    bzero(buffer,BUFFER_SIZE);
    strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
    //向服务器发送buffer中的数据
    send(client_socket,buffer,BUFFER_SIZE,0);

    FILE * fp = fopen(file_name,"r");
    if(NULL == fp )
    {
        printf("File:\t%s Not Found\n", file_name);
        exit(1);
    }
    else
    {
            bzero(buffer, BUFFER_SIZE);
            int file_block_length = 0;
            while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE, fp))>0)
            {
                //printf("file_block_length = %d\n",file_block_length);
                //发送buffer中的字符串到服务器
                if(send(client_socket,buffer,file_block_length,0)<0)
                {
                    printf("Send File:\t%s Failed\n", file_name);
                    break;
                }
                bzero(buffer, BUFFER_SIZE);
            }
    }
    
    printf("Send File:\t %s To Server[%s] Finished\n",file_name, argv[1]);
	
    printf("The File has %d words.\n", wc_func(file_name));
    fclose(fp);
/*服务器:*/
    char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name, FILE_NAME_MAX_SIZE+1);
    char buffer[BUFFER_SIZE];
    bzero(buffer,BUFFER_SIZE);
    recv(new_server_socket,file_name,BUFFER_SIZE,0);
    
    
    FILE * fp = fopen(file_name,"w");
    if(NULL == fp )
    {
        printf("File:\t%s Can Not Open To Write\n", file_name);
        exit(1);
    }
    
    //从客户端接收数据到buffer中
    bzero(buffer,BUFFER_SIZE);
    int len = 0;
    while( len = recv(new_server_socket,buffer,BUFFER_SIZE,0))
    {
        if(len < 0)
        {
            printf("Recieve Data From Client %s Failed!\n", argv[1]);
            break;
        }

        int write_length = fwrite(buffer,sizeof(char),len,fp);
        if (write_length<len)
        {
            printf("File:\t%s Write Failed\n", file_name);
            break;
        }
        bzero(buffer,BUFFER_SIZE);    
    }
    printf("File:\t%s Transfer Finished!\n",file_name);
    
    fclose(fp);

按照要求,在服务器端调用计算单词数的函数,并返回给客户端即可。

【注:完成代码已上传至码云

返回目录


任务三 多线程实现传送文本文件的服务器和客户端

通过man -k命令查看与创建进程相关的函数:

我们可以找到“pthread_create()”函数,使用man 3 pthread_create命令可以得知其用法:

说明:

  • thread:线程标识符;
  • attr:线程属性设置;
  • start_routine:线程函数的起始地址;
  • arg:传递给start_routine的参数;
  • 返回值:成功,返回0;出错,返回-1。
  • 注意:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在线程函数在编译时,需要使用“-lpthread”链接库函数。

因此,服务器需要循环检测是否有新的连接。如果有,则调用pthread_create()函数创建新的进程,并执行相关代码。这部分的代码如下:

while(1){
        //接受客户端连接
        socklen_t addrlen = sizeof(struct sockaddr);
        struct sockaddr_in client_addr; //客户端地址结构
        int client_sock = accept(ss, (struct sockaddr*)&client_addr, &addrlen);
        if(client_sock < 0){
            printf("accept error\n");
        }
        printf("accept success\n");

        pthread_t pid;
        if(pthread_create(&pid, NULL, process_client, &client_sock) < 0){
            printf("pthread_create error\n");
        }
    }

创建了新的线程,接下来就可以传送文件了。过程与任务二类似。

【注:完成代码已上传至码云

返回目录


任务四 使用PC机和实验箱模拟客户端服务器并测试(未完成)

返回目录


实验感想与体会

  • 此次实验首先在Linux下实现了客户端与服务器传送文本文件,并模拟wc命令统计文本文件中的单词数。但一次只能为一个客户端提供服务的迭代网络是不现实的,因此,在任务二中创建了一个并发服务器,它为每一个客户端创建一个单独的逻辑流。这就允许服务器同时为多个客户端服务,提高了其使用价值。
  • 在完成任务三的过程中遇到了重重困难。本打算在自己笔记本上完成,也方便后续学习,但参考指导书《实验开发环境使用说明-12.04》中“解决上网与本地网络调试冲突”的相关讲解完成配置后,实验箱与主机仍然无法ping通。想到之前实验一在实验室的PC机上完成得比较顺利,于是改用实验室的PC机,但找不到正确的端口,更换了PC机和实验箱后仍然解决不了,只能作罢。
  • 近期学习效率低下,每周一章的任务常常不能按时完成,总需要后期抽时间补充。回想上学期学习Java时,虽然也是一周学习一章,但并没有觉得如此吃力,花十几个小时就能完成当周的任务。尤其这次实验结束,花费大量时间也没有解决问题,心情失落的同时,不禁怀疑自己的自学能力。且不说与老师的要求相差甚远,这种学习效率与状态连自己都不能接受。课本内容较上学期更难更深是一方面,但更多的是自身的问题。比如没有合理分配时间,做到门门课程兼顾;比如将精力浪费在一些无关紧要的事情上,等等。
  • 课下了解到,不少同学也遇到了类似的问题。能及时赶上老师的进度还好,一旦某一阵略有松懈没跟上节奏,就容易产生恶性循环,落下越来越多的内容。老师一直以来采取的都是自学为主,教学为辅的方式,这一点无可厚非,我认为自学能力可以让人终生受益。不怕自学的过程中遇到困难,怕就怕在总是遇到无法解决的困难而逐渐放弃。
  • 前几天不慎扭伤了脚,不能在寝室、主楼和图书馆之间随意跑的日子还真不适应,只能躺着休息,因此对好多任务都心有余而力不足...多亏朋友们悉心的照顾还有及时涂药,脚在一天天康复,落下的事情也要一点点补回来。那么,就先从努力按时完成本周学习任务开始吧:)

返回目录


参考资料

返回目录

posted @ 2017-11-14 14:41  0x14b7狄  阅读(477)  评论(2编辑  收藏  举报