1. 程式人生 > >2018-2019-1 20165230 實驗三 實時系統

2018-2019-1 20165230 實驗三 實時系統

方式 更新 是不是 read info sprintf %d 輸入 strlen

2018-2019-1 20165230 實驗三 實時系統

實驗內容

  • 學習使用Linux命令wc(1)

  • 基於Linux Socket程序設計實現wc(1)服務器(端口號是你學號的後6位)和客戶端
  • 客戶端傳一個文本文件給服務器
  • 服務器返加文本文件中的單詞數

實驗步驟

一、並發程序-1

知識點
  • wc命令
  • socket編程
  • tcp文件傳輸
實驗過程
  • 使用man查看wc
    技術分享圖片

從幫助文檔可知

命令參數 作用
-c 統計字節
-m 統計字符
-l 統計行數
-L 打印最長行的長度
-w 統計字數。一個字被定義為由空白、跳格或換行字符分隔的字符串。
  • Mywc代碼實現:
#include<stdio.h>  
#include<unistd.h>  
#include<sys/stat.h>  
#include<stdlib.h>  
struct message{  
        int lines;  
        int words;  
        int max_line_length;  
        int size;  
        int chars;  
}info;  
void error_print(char str[]){  
        printf("Error:%s",str);  
}  
void init(char filename[]){  
        struct stat get_message = {};  
        FILE *fp;  
        int ret_stat = stat(filename,&get_message);/*用stat函數讀取filenmae文件的信息,並將結果寫到get_message結構體中*/  
        if(ret_stat == -1){//stat函數不出錯則進行信息輸出  
                error_print(filename);  
                return ;  
        }  
        mode_t mode = get_message.st_mode;      //接收文件信息,用於下面判斷是不是目錄  
        int length = 0;  
        if(S_ISDIR(mode))   //如果是目錄,輸出錯誤  
                printf("Error %s is dir\n0\t0\t0\t%s",filename,filename);  
        else{  
                info.size = get_message.st_size;    //文件字節大小 wc -c  
                fp = fopen(filename,"r");   //以只讀方式打開指定文件  
                char ch;  
                int flag = 0;  
                while((ch = fgetc(fp))!=EOF){   //一直讀到文件尾  
                        info.chars++;       //字符數加1 wc -m  
  
                        if(ch != ‘\n‘){  
  
                                length++;   //記錄當前行的長度 wc -L  
                        }  
                        if(ch == ‘\n‘){  
                                info.lines ++;  //行數加1 wc -l  
                                if(length>info.max_line_length)  
                                        info.max_line_length = length;  //更新最大長度  
                                length = 0;  
                        }  
                        if(ch == ‘\t‘ || ch == ‘ ‘ || ch == ‘\n‘){  
                                flag = 0;       //計算單詞數 wc -w  
                                continue;  
                        }  
                        else{  
  
                                if(flag == 0){  
                                        info.words++;   //計算單詞數 wc -w  
                                        flag = 1;  
                                }  
                        }  
                }  
                fclose(fp);  
        }  
  
}  
//計算鍵盤輸入內容的相關信息,即參數中沒有指定要打開的文件  
void EmptyFile(){  
        char ch;  
        int flag = 0;  
        int length = 0;  
  
        while((ch = getchar())!=EOF){  
                info.chars++;  
                info.size += sizeof(ch);    //字節累加  
                if(ch != ‘\n‘){  
                        length++;  
                }  
                if(ch == ‘\n‘){  
                        info.lines ++;  
                        if(length>info.max_line_length)  
                                info.max_line_length = length;  
                        length = 0;  
                }  
                if(ch == ‘\t‘ || ch == ‘ ‘ || ch == ‘\n‘){  
                        flag = 0;  
                        continue;  
                }  
                else{  
  
                        if(flag == 0){  
                                info.words++;  
                                flag = 1;  
                        }  
                }  
  
        }  
}  
int main(int argc,char *argv[]){  
  
        if(argc == 2){  
                if(argv[1][0] != ‘-‘){  
                        init(argv[1]);  
                        printf("%d %d %d %s\n",info.lines,info.words,info.size,argv[1]);  
                        return 0;  
                }  
                else{   //未指定打開文件,類似 wc -lmwcL  
                        EmptyFile();  
  
                }  
        }  
        else if(argc == 1){     //未指定打開文件和要輸出的參數,(默認輸出 -lwc)  
                EmptyFile();  
                printf("%d\t%d\t%d\n",info.lines,info.words,info.size);  
                return 0;  
        }  
        else if(argc == 3){  
                init(argv[2]);  
        }  
        int num;  
        while((num = getopt(argc,argv,"lwmcL"))!=-1){  
                switch(num){  
                        case ‘l‘:  
                                printf("%d\t",info.lines);  
                                break;  
                        case ‘w‘:  
                                printf("%d\t",info.words);  
                                break;  
                        case ‘m‘:  
                                printf("%d\t",info.chars);  
                                break;  
                        case ‘c‘:  
                                printf("%d\t",info.size);  
                                break;  
                        case ‘L‘:  
                                printf("%d\t",info.max_line_length);  
                                break;  
                }  
        }  
        if(argc != 2 && argv[1][0] != ‘-‘)  //一定要判斷,否則會越界  
                printf("%s\n",argv[2]);  
  
  
        return 0;  
}  

mywc實現截圖

技術分享圖片

  • 客戶端代碼
#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 155314  
#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_socket_fd = socket(AF_INET, SOCK_STREAM, 0);  
    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;  
    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中等待發送  
    char file_name[FILE_NAME_MAX_SIZE+1];  
    bzero(file_name, FILE_NAME_MAX_SIZE+1);  

    printf("Please Input File Name On Client:\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中的數據  
    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\n", 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);  
            }  
  
            // 關閉文件  
        
            fclose(fp);  
            printf("File:%s Transfer Successful!\n", file_name);  
        char s[50]; 
        scanf("%s",s);
        send(client_socket_fd,"OK",BUFFER_SIZE,0);
        recv(client_socket_fd,buffer,BUFFER_SIZE,0);    
        printf("%d words.\n",atoi(buffer));
    }  

    close(client_socket_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 155314  
#define LENGTH_OF_LISTEN_QUEUE 20  
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512  
 
int main(void)  
{  
    // 聲明並初始化一個服務器端的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);  
    }  
  
    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("%s\n", file_name);  
  
    // 打開文件,準備寫入  
        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 length = 0;  
        while((length = recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0)) > 0)  
        {  
        if(strcmp(buffer,"OK")==0) break;
            if(fwrite(buffer, sizeof(char), length, fp) < length)  
            {  
                printf("File:\t%s Write Failed\n", file_name);  
                break;  
            }  
            bzero(buffer, BUFFER_SIZE);  
        }  
    
    
    // 接收成功後,關閉文件,關閉socket  
        printf("Receive File:\t%s From Client IP Successful!\n", file_name);  
        fclose(fp);


    // 統計文件單詞個數,並發送給客戶端
    int words=0;
    char s[100];
    FILE *fp2;
    if((fp2=fopen(file_name,"r"))==NULL){
        printf("ERROR!\n");
        exit(0);
    }
    while(fscanf(fp2,"%s",s)!=EOF)
        words++;
    fclose(fp2);
    //printf("%d words.\n",words);


    sprintf(buffer,"%d",words);

    send(new_server_socket_fd,buffer,BUFFER_SIZE,0);

    //send(new_server_socket_fd,&words,sizeof(words),0);
    close(new_server_socket_fd);  
    
    
    
        // 關閉與客戶端的連接  
        
    }  
    // 關閉監聽用的socket  
    close(server_socket_fd);  
    return 0;  
} 
實驗截圖

技術分享圖片


二、並發程序-2

實驗過程
  1. 使用多線程實現wc服務器並使用同步互斥機制保證計數正確

  2. 對比單線程版本的性能,並分析原因
  • 客戶端代碼不變
  • 互斥鎖:互斥鎖是用加鎖的方式來控制對公共資源的操作(一旦開始進行就不會被打斷的操作)
    在同一時刻只有一個線程能夠對互斥鎖進行操作;只有上鎖的進程才可以對公共資源進行訪問,除該進程之外,其他進程只能等到上鎖進程解鎖才能對公共資源進行操作。
  • 修改服務器代碼,使之成為多線程服務器。
 #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 20165230
    #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; }

編譯時需要使用-lpthread,否則報錯:“對‘pthread_create’未定義的引用”

gcc -pthread  server2.c -o server2

運行截圖

技術分享圖片

分析原因

  • 單線程容易實現,但是一次只允許一個客戶端連接。
  • 多線程更復雜,但是一次允許多個客戶端,工作效率更高。
  • 單線程保證單用戶的安全性,但多線程保證任務的高效性。

實驗中的問題及解決過程

  1. 使用socket編程的時候,發現用書上的知識無法直接傳輸文件,結合老師講過的web編程,客戶端使用send和recv實現文件傳輸,但是運行的時候報錯,出現段錯誤(核心已轉儲)
  • 問題解決:檢查發現存在地址越界的錯誤,將代碼改過之後,代碼運行成功。
  1. 統計出的結果與預想結果不一樣,總會存在誤差,
  • 問題解決:查找資料顯示,是因為將測試文件編輯的時候,從Windows到Linux粘貼的過程中,會產生一些看不見的非法字符,使用od -tc xxx就能看出來,但實際上,這些字符在統計的時候是不會被統計到的,因此,結果總有偏差。

    新學到的知識點

  • 在主線程中初始化鎖為解鎖狀態

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
  • 在編譯時初始化鎖為解鎖狀態
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//鎖初始化 
  • 訪問對象時的加鎖操作與解鎖操作
pthread_mutex_lock(&mutex)//加鎖

pthread_mutex_unlock(&mutex)//釋放鎖

信號量

  • 鎖有一個很明顯的缺點,那就是它只有兩種狀態:鎖定與不鎖定。
    信號量本質上是一個非負數的整數計數器,它也被用來控制對公共資源的訪問。當公共資源增加的時候,調用信號量增加函數sem_post()對其進行增加,當公共資源減少的時候,調用函數sem_wait()來減少信號量。其實是可以把鎖當作一個0-1信號量的。
    它們是在/usr/include/semaphore.h中進行定義的,信號量的數據結構為sem_t, 本質上,它是一個long型整數
    在使用semaphore之前,我們需要先引入頭文件#include <semaphore.h>
    初始化信號量:int sem_init(sem_t *sem, int pshared, unsigned int value);
    成功返回0,失敗返回-1
  • sem:指向信號量結構的一個指針
  • pshared: 不是0的時候,該信號量在進程間共享,否則只能為當前進程的所有線程們共享
  • value:信號量的初始值
  • 信號量減1操作,當sem=0的時候該函數會堵塞 int sem_wait(sem_t *sem);
    成功返回0,失敗返回-1

2018-2019-1 20165230 實驗三 實時系統