zoukankan      html  css  js  c++  java
  • 【2018-2019-1】20165223 实验三 实时系统


    实验成员:20165223

    实验名称:实时系统--并发程序


    目录


    一、实验内容

    二、实验总结


    一、实验内容


    任务一:基于Linux Socket程序设计实现wc服务器和客户端

    (一)实验要求

    0.学习使用Linux命令wc
    1.基于Linux Socket程序设计实现wc服务器(端口号是你学号的后6位)和客户端
    2.客户端传一个文本文件给服务器
    3.服务器返加文本文件中的单词数
    4.上方提交代码,附件提交测试截图,至少要测试附件中的两个文件

    (二)实验步骤

    • 1.先使用man wc了解wc命令的功能与用法

    • 2.了解wc的各命令参数的用法
    参数 用法
    -c 统计字节数
    -l 统计行数
    -m 统计字符数,不能与 -c 连用
    -w 统计字数,一个字被定义为由空白、跳格或换行字符分隔的字符串
    -L 打印最长行的长度
    -help 显示帮助信息
    --version 显示版本信息
    • 3.对 test1.txt 和 test2.txt 试验 wc -w 功能(wc命令使用实例)

    • 4.根据题目含义,编写实现 wc -w 功能代码(方便之后添加进代码中)
    • 5.编写服务器端代码,客户端代码,实现传送文本文件的服务器和客户端
    • 6.分析代码,编译运行,测试结果

    (三)实验结果

    • 用socket编程实现

    • 用wc命令检查

    (四)代码分析

    (1)实验代码

    (2)代码分析

    1.首先了解Linux下是如何进行socket编程的

    参考Linux的SOCKET编程详解

    2.再学习简单文件传输服务器和客户端代码是如何编写的

    参考Linux网络编程:socket文件传输范例

    3.最后进行代码改造

     ① 服务器端代码:server.c 实现返回文本文件总字数的功能

    • 除了基础的socket()、bind()、listen()、accept()、send()、recv()、close()函数以外,还要导入文件传输、缓冲区的思想。
    /*服务器端需要用到的各个函数的头文件*/
    #include<netinet/in.h>    //sockaddr_in  
    #include<sys/types.h>    //socket  
    #include<sys/socket.h>    //socket  
    #include<stdio.h>    //printf  
    #include<stdlib.h>    //exit  
    #include<string.h>    //bzero  
    #include <unistd.h>    //close
    #define SERVER_PORT 165223    //学号
    #define LENGTH_OF_LISTEN_QUEUE 20  
    #define BUFFER_SIZE 1024
    #define FILE_NAME_MAX_SIZE 512  
    #define BEGIN 1; 
    
    /*声明并初始化一个服务器端的socket地址结构*/        
    struct sockaddr_in server_addr;  
    bzero(&server_addr, sizeof(server_addr));  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);  
    server_addr.sin_port = htons(SERVER_PORT);  
    
    /*创建第一个socket,用于连接,若成功,返回socket描述符*/
    int server_socket_fd = socket(PF_INET, SOCK_STREAM, 0);  
    if(server_socket_fd < 0)  
    {  
    	perror("Create Socket Failed!");  
            exit(1);  
    }  
    int opt = 1;  
    setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  
    
    /*绑定socket和socket地址结构*/
    if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))))  
    {  
    	perror("Server Bind Failed!");  
    	exit(1);  
    }  
    
    /*实现socket监听*/
    if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))  
    {  
    	perror("Server Listen Failed!");  
    	exit(1);  
    }  
    
    /*定义客户端的socket地址结构*/
    struct sockaddr_in client_addr;  
    socklen_t client_addr_length = sizeof(client_addr);  
    
    /*接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信,accept函数会把连接到的客户端信息写到client_addr中*/
    int new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);  
    if(new_server_socket_fd < 0) 
    {
    	perror("Server Accept Failed!");  
    	break;  
    }  
    
    
    • 具体操作为:客户端首先输入文件名,并判断该文件是否存在。若不存在,则返回错误提示;存在,则发送至服务器。服务器接收到数据后,创建一个同样名称的文件。
    • 在此实现的是接受从客户端传来的文件,并返回计算的文本文件总字数,间接实现wc -w命令的功能。
    /*接收从客户端发来的文本文件,建立缓冲区*/
    char buffer[BUFFER_SIZE];  
    bzero(buffer, BUFFER_SIZE);  
    
    /*recv函数接收数据到缓冲区buffer中*/
    if(recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
    {
            perror("Server Recieve Data Failed:");
            break;
    }
    
    /*然后从buffer(缓冲区)拷贝到file_name中*/
    char file_name[FILE_NAME_MAX_SIZE+1];  
    bzero(file_name, FILE_NAME_MAX_SIZE+1);  
    strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));  
    printf("%s
    ", file_name);  
    
    /*打开文件只写模式,准备写入*/
    FILE *fp = fopen(file_name, "w");
    if(NULL == fp)
    {
            printf("File:	%s Can Not Open To Write
    ", file_name);
            exit(1);
    }
     
    /*从服务器接收数据到buffer中,每接收一段数据,便将其写入文件中,循环直到文件接收完并写完为止*/
    bzero(buffer, BUFFER_SIZE);
    int length = 0;
    while((length = recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0)) > 0)
    {
            if(fwrite(buffer, sizeof(char), length, fp) < length)
            {
                printf("File:	%s Write Failed
    ", file_name);
                break;
            }
            bzero(buffer, BUFFER_SIZE);
    }  
    
    /*否则就接收成功,关闭文件*/
    printf("Receive File: %s From Client Successful!
    ", file_name);  
    fclose(fp);
    
    /*在服务器端显示屏上打印统计的文本文件总字数*/
    int words=0;
    char s[100];
    FILE *fp2;
    if((fp2=fopen(file_name,"r"))==NULL)
    {
    	printf("ERROR!
    ");
    	exit(0);
    }
    while(fscanf(fp2,"%s",s)!=EOF)
    words++;
    fclose(fp2);
    printf("%d words.
    ",words);
    char sendbuf[50];
    sprintf(sendbuf,"%d",words);
    send(new_server_socket_fd,sendbuf,50,0);
    close(new_server_socket_fd); //先close用于传输数据(文件)的socket
    close(server_socket_fd); //再close用于连接(客户端与服务器)的socket
    
    

     ② 客户端代码:client.c 实现向服务器传送文件的功能

    • 客户端的任务是与服务器连接,实现向服务器传送文件,接收服务器返回的文本文件总字数并打印出来

    • 首先声明一个head.h头文件,包含之后要用到的wcfunc函数:head.h

    • 其次开始编写主函数包含的基础的socket()、bind()(非必需)、connet()、close()函数

    /*客户端需要用到的各个函数的头文件*/
    #include<netinet/in.h>   // sockaddr_in  
    #include<sys/types.h>    // socket  
    #include<sys/socket.h>   // socket  
    #include<stdio.h>        // printf  
    #include<stdlib.h>       // exit  
    #include<string.h>       // bzero  
    #include <arpa/inet.h>
    #include <unistd.h>
    #include "head.h"
    #define SERVER_PORT 165223    //学号
    #define BUFFER_SIZE 1024  
    #define FILE_NAME_MAX_SIZE 512  
    #define BEGIN 1;
    
    /*声明并初始化一个客户端的socket地址结构*/
    struct sockaddr_in client_addr;  
    bzero(&client_addr, sizeof(client_addr));  
    client_addr.sin_family = AF_INET;  
    client_addr.sin_addr.s_addr = htons(INADDR_ANY);  
    client_addr.sin_port = htons(0);  
    int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);  
    
    /*创建用于连接服务器端的socket,若成功,返回socket描述符*/
    if(client_socket_fd < 0)  
    {  
        	perror("Create Socket Failed!");  
        	exit(1);  
    }  
    
    /*绑定客户端的socket和客户端的socket地址结构(非必需)*/
    if(-1 == (bind(client_socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr))))  
    {  
        	perror("Client Bind Failed!");  
        	exit(1);  
    }
    
    /*声明一个服务器端的socket地址结构,并用服务器那边的IP地址(这里用的是本机地址)及端口对其进行初始化,用于后面的连接*/
    struct sockaddr_in server_addr;  
    bzero(&server_addr, sizeof(server_addr));  
    server_addr.sin_family = AF_INET;  
    
    /*用inet_pton将点分十进制的地址转换为长整型的地址*/
    if(inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) == 0)
    {  
        	perror("Server IP Address Error!");  
        	exit(1);  
    }  
    server_addr.sin_port = htons(SERVER_PORT);  
    socklen_t server_addr_length = sizeof(server_addr);  
    
    /*向服务器发起连接,连接成功后client_socket_fd代表了客户端和服务器的一个socket连接*/
    if(connect(client_socket_fd, (struct sockaddr*)&server_addr, server_addr_length) < 0)  
    {  
        	perror("Can Not Connect To Server IP!");  
        	exit(0);  
    }
    
    • 其中还要加入文件传输的部分(注意buffer缓冲区的建立)
    /*输入文件名 并放到缓冲区buffer中等待发送*/
    char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name, FILE_NAME_MAX_SIZE+1);
    printf("Please Input File Name On Client:	");
    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中的数据*/
    if(send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
    {
            perror("Send File Name Failed:");
            exit(1);
    }
     
    /*打开文本文件并执行只写操作*/
    FILE *fp = fopen(file_name, "r");
    if(NULL == fp)
    {
            printf("File:%s Not Found
    ", file_name);
    }
    else
    {
            bzero(buffer, BUFFER_SIZE);
            int length = 0;
            /*每读取一段数据,便将其发送给客户端,循环直到文件读完为止*/
            while((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
            {
                    if(send(client_socket_fd, buffer, length, 0) < 0)
                    {
                            printf("Send File:%s Failed./n", file_name);
                            break;
                    }
                    bzero(buffer, BUFFER_SIZE);
             }
    }
    printf("Send File: %s Successful!
    ", file_name);    //成功发送文件
    printf("The File has %d words.
    ",wcfunc(file_name));
    fclose(fp);
    close(client_socket_fd);
    
    
    • 最后调用wcfunc函数计算文本文件的总字数
    int wcfunc(char *file_name)
    {
    	int t;
    	int w = 0;
    	int state = 0;
    	FILE *in;
    	if((in = fopen(file_name,"r"))==NULL)
    	{
    		printf("wc %s:no this file or dir
    ",file_name);
    		return 0;
    	}
    	while((t=fgetc(in))!=EOF)
    	{
    		
    		if(t=='
    '||t==' '||t=='
    ') {
                		state = 0;
                		continue;
            	} else {
                		if(state == 0) {
                    	state = 1;
                    	w++;
               		}
                		continue;
            	}
    	}
    	return w;
    }
    
    

    返回目录


    任务二:使用多线程实现wc服务器

    (一)实验要求

    0.使用多线程实现wc服务器并使用同步互斥机制保证计数正确
    1.对比单线程版本的性能,并分析原因
    2.上方提交代码,下方提交测试

    (二)实验步骤

    • 1.先用man命令了解一下线程的创建

    • 2.了解到在多线程编译的过程中需要加上 -lpthread 才能成功编译运行。原因是:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在线程函数在编译时,需要使用“-lpthread”链接库函数。

    • 3.了解同步互斥机制,参考【Linux多线程】同步与互斥的区别

      • 互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
      • 同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
      • 同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。互斥是一种特殊的同步。
    • 4.编写用于多线程的服务器端代码、客户端代码

    • 5.分析代码,编译运行,测试结果

    • 6.总结对比单线程版本的性能,并分析原因

      • 与单线程版相比较:单线程程序只有一个线程,代码顺序执行,容易出现代码阻塞;而多线程程序有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能。因此还是多线程的性能好。

    (三)实验结果

    • 用socket编程实现多线程

    • 用wc命令检查

    (四)代码分析

    (1)实验代码

    (2)代码分析

     ① 服务器端代码:server.c

    • 基础部分与任务一中服务器端代码基本一致,需要添加的是多线程部分的代码
    //增加多线程相关的头文件
    #include <pthread.h>
    
    //主函数代码
    int main(int argc, char* argv[])
    {
        if(argc != 3)
        {
            Usage(argv[0]);
            return 2;
        }
        int listen_sock = startup(argv[1], atoi(argv[2]));
        //printf("sock:%d
    ", listen_sock);
        //需要让子进程的子进程去提供服务
        //父进程继续监听
        char buf[1024];
        while(1)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int newsock = accept(listen_sock, (struct sockaddr*)&client, &len);
            if(newsock < 0)
            {
                perror("accept");
                continue;
            }
            //用inet_ntoa和ntohs将网络中的数据转换为主机用户可以看懂的数据
            printf("get a new client %s:%d
    ", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
            //创建一个新的线程去服务
            //主线程只负责监听工作
            pthread_t tid;
            //pthread_create(&tid, NULL, handle, (void*)newsock);
            pthread_detach(tid);
        }
        close(listen_sock);
        return 0;
    }
    
    
    • 这样就成功创建了线程啦,能够实现一服务器、多客户端的多线程模式。

     ② 客户端代码:client.c

    返回目录


    二、实验总结


    (一)遇到的问题

    (1)gcc编译后警告:warning: implicit declaration of function ‘xxx’ [-Wimplicit-function-declaration]

    • 在代码编译的过程中常常遇到诸如下图的错误提示

    • 查询原因后得知:是相关的头文件没有声明这个函数,在相关头文件中声明即可

    (2)gcc编译后警告:在'xxx'函数中,对'pthread_create'未定义的引用

    • 在多线程服务器的编译过程中出现如下错误提示

    • 查询原因是:多线程代码需要使用使用静态库libpthread.a,因此在编译时要加上 -lpthread 来连接库函数

    (二)分析与总结

    • 本次实验重点在于socket编程的学习与掌握。恰好刘念老师的网络安全编程课程也需要掌握简单socket网络编程,因此在基础部分的代码编写上没有出什么差错。需要注意的就是将windows下的C语言转换到Linux下C语言实现。
    • 其次是在简单基础上加入文本文件传输和用 wc 计算文本文件总字数的实现,在参考了部分资料后也顺利理解了代码并组合成功。
    • 再者就是进阶部分,多线程服务器代码的编写。先是了解了什么是多线程,再了解了同步与互斥机制,最终实现了一服务器、多客户端的多线程同步实现的可能。
    • 本来还有个任务三是在实验箱上实现多线程服务器的代码,但老师最后取消了这一项,就只做了前两项任务。
    • 参考了许多网络上的资料以及课本第十二章《并发编程》的内容,获益匪浅。

    (三)参考资料

    1. linux socket TCP 服务器向客户端传文件
    2. Linux C TCPSocket 传输文件简单实例-多线程实现
    3. Linux多线程 同步与互斥机制
    4. gcc警告选项
    5. ubuntu C 语言 段错误 (核心已转储)
    6. C语言再学习 -- 段错误(核心已转储)
    7. Linux 线程Pthread
    8. Socket程序从Windows移植到Linux下的一些注意事项
    9. wc:统计一个文件里出现某个单词出现的次数

    返回目录

  • 相关阅读:
    第十六节 URL映射的时候指定默认参数
    第十五节 自定义path转换器
    第十四节 reverse函数补充
    第十一节 实例命名空间
    第十节 url命名和应用命名空间
    第八节 url解释器
    MySQL条件查询
    MySQL判断数据是否为空
    MySQL拼接字符串
    MySQL加号+ 的作用
  • 原文地址:https://www.cnblogs.com/moddy13162201/p/9973551.html
Copyright © 2011-2022 走看看