1. 程式人生 > >2017-2018-1 20155201 《信息安全系統設計基礎》實驗三 實時系統

2017-2018-1 20155201 《信息安全系統設計基礎》實驗三 實時系統

byte each spec 實驗代碼 max ons 統計字數 ets mark

2017-2018-1 20155201 實驗三 實時系統

一、實驗內容

  1. 基於Linux Socket程序設計實現wc(1)服務器和客戶端
  2. 使用多線程實現wc服務器

二、實驗步驟

  1. 基於Linux Socket程序設計實現wc(1)服務器和客戶端
    • 學習使用Linux命令wc(1)
    • 在終端中輸入man wc命令查看wc命令的解釋:
    The wc utility displays the number of lines, words, and bytes contained
     in each input file, or standard input (if no file is specified) to the
     standard output. 
     命令統計指定文件中的行數、單詞數和字節數,並將統計結果輸出,如果沒有給出文件名,將從標準輸入讀取。
    
     命令參數:
         - c 統計字節數
         - l 統計行數
         - m 統計字符數。(不可以與-c一起使用)
         - w 統計字數。(一個字被定義為由空白、跳格或換行字符分隔的字符串。)
    
    >wc test1.txt    //命令顯示文件的行數、字數、字節數和文件名
    >wc -w test1.txt    //統計文件字數(單詞數)。
    • 客戶端傳一個文本文件給服務器

    客戶端代碼實現:

    #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
    #define SERVER_PORT 8000
    #define BUFFER_SIZE 1024
    #define FILE_NAME_MAX_SIZE 512
    int main()
    {
    // 聲明並初始化一個客戶端的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);
    
    // 創建socket,若成功,返回socket描述符
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(client_fd < 0)
    {
        perror("Create Socket Failed:");
        exit(1);
            }
    // 綁定客戶端的socket和客戶端的socket地址結構 非必需
    if((bind(clientfd, (struct sockaddr*)&client_addr, sizeof(client_addr)))==-1)
        {
        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;
    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(clientfd, (struct sockaddr*)&server_addr, server_addr_length) < 0)
        {
            perror("Can Not Connect To Server IP:");
            exit(0);}
    // 輸入文件名 並放到緩沖區buffer中等待發送
    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));
    send(clientfd, buffer, BUFFER_SIZE, 0);
    // 向服務器發送buffer中的數據
          // 打開文件,準備寫入
    FILE *fp = fopen(file_name, "r");   
    if(NULL == fp)
        {
            printf("File:\t%s Can Not Open To Write\n", file_name);
            exit(1);
            }
    else
    {
        bzero(buffer, BUFFER_SIZE);
        int file_length = 0;
        while( (file_length = fread(buffer,sizeof(char),BUFFER_SIZE, fp))>0)
        {
    
            if(send(clientfd,buffer,file_length,0)<0)
            {
                printf("Send File:\t%s Failed\n", file_name);
                break;
            }
            bzero(buffer, BUFFER_SIZE);
        }
    }
    close(fp);
    close(client_fd);
    return 0;
    }
    
    • 服務器返回文本文件中的單詞數
    #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
    #define SERVER_PORT 8000
    #define LENGTH_OF_LISTEN_QUEUE 20
    #define BUFFER_SIZE 1024
    #define FILE_NAME_MAX_SIZE 512
    #define MAX 10000000
    int main()
    {
    // 聲明並初始化一個服務器端的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((bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)))==-1)
        {
        perror("Server Bind Failed:");
        exit(1);
        }
        // socket監聽
    if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))
    {
    perror("Server Listen Failed:");
    exit(1);
    }
    while(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;
    }
    // recv函數接收數據到緩沖區buffer中
    char buffer[BUFFER_SIZE];
    bzero(buffer, BUFFER_SIZE);
    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("filename:%s\n", file_name);
    // 打開文件並讀取文件數據
    FILE *fp = fopen(file_name, "r");
    if(NULL == fp)
    {
    printf("File:%s Not Found\n", file_name);}
    else
    {
    //printf("buffer:%s\n",buffer);//buffer為filename
    char *argv[]={"wc","-w",file_name,0};
    execvp( "wc" ,argv);
    fclose(fp); 
    } 
    // 關閉與客戶端的連接 
    close(new_server_socket_fd); 
    }
    // 關閉監聽用的socket 
    close(server_socket_fd); 
    return 0; }
    程序截圖:
  2. 使用多線程實現wc服務器並使用同步互斥機制保證計數正確
    互斥鎖:互斥鎖是用加鎖的方式來控制對公共資源的操作(一旦開始進行就不會被打斷的操作)
    在同一時刻只有一個線程能夠對互斥鎖進行操作;只有上鎖的進程才可以對公共資源進行訪問,除該進程之外,其他進程只能等到上鎖進程解鎖才能對公共資源進行操作。
    之前老師給的課上示例代碼有互斥鎖的部分:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NLOOP 5000

int counter;

pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;  

void *doit( void * );

int main(int argc, char **argv)
{
    pthread_t tidA, tidB;

    pthread_create( &tidA ,NULL, &doit, NULL );      //創建進程
    pthread_create( &tidB ,NULL, &doit, NULL );

    pthread_join( tidA, NULL );     //一直阻塞調用線程
    pthread_join( tidB, NULL );

    return 0;
}

void * doit( void * vptr)
{
    int i, val;

    for ( i=0; i<NLOOP; i++ ) {
        pthread_mutex_lock( &counter_mutex );   //對進程上鎖
        val = counter++;
        printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
        counter = val + 1;
        pthread_mutex_unlock( &counter_mutex );  //解鎖
    }
    return NULL;
}

因此修改服務器代碼,使之成為多線程服務器。

    #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
    #define SERVER_PORT 8000
    #define LENGTH_OF_LISTEN_QUEUE 20
    #define BUFFER_SIZE 1024
    #define FILE_NAME_MAX_SIZE 512
    #define MAX 10000000
    
    
    
    
    int main()
    {
    // 聲明並初始化一個服務器端的socket地址結構
    ……
    // 綁定socket和socket地址結構
    if((bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)))==-1)
        {
        perror("Server Bind Failed:");
        exit(1);
        }
        // socket監聽
    if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))
    {
    perror("Server Listen Failed:");
    exit(1);
    }
    while(1)//調用pthread_create不斷創建新進程接受客戶端連接請求
    {
    // 定義客戶端的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;
    }
    
    int client_p=pthread_create(&pid, NULL, process_client,(void *) &new_server_socket);
    pthread_t pid;
        if(pthread_create(&pid, NULL, process_client,(void *) &new_server_socket) < 0){
              exit(1);
        }
    
    // recv函數接收數據到緩沖區buffer中
    char buffer[BUFFER_SIZE];
    bzero(buffer, BUFFER_SIZE);
    if(recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
    {
    perror("Server Recieve Data Failed:");
    break;
    }
    // 然後從buffer(緩沖區)拷貝到file_name中
    /*打開文件並計數代碼部分省略*/
    …………
    // 關閉與客戶端的連接 
    close(new_server_socket_fd); 
    }
    // 關閉監聽用的socket 
    close(server_socket_fd); 
    return 0; }

程序運行結果:
[2file]

三、實驗代碼調試中遇到的問題

  • 問題1:在完成服務器計算文件單詞總數時,每次執行程序後顯示字數總是零,後來發現只要一執行客戶端程序,文件內容就被清空。
  • 問題1解決方案:在客戶端打開文件的過程中,期初定義的操作方式是"w"(寫),因bzero()函數清空緩沖區內存,實際上將空的內容寫入了要打開的文件,所以文件每次都被清空。把打開方式寫成"r"(讀)後,不存在文件被清空問題,程序可以正常計數。
FILE *fp = fopen(file_name, "r");

2017-2018-1 20155201 《信息安全系統設計基礎》實驗三 實時系統