2017-2018-1 20155227 實驗三 實時系統
阿新 • • 發佈:2017-11-19
客戶端 cep 一點 代碼 新的 ip add int mes 進行
2017-2018-1 20155227 實驗三 實時系統
實驗目的,實驗步驟
實驗過程如下。
實驗三-並發程序-1
學習使用Linux命令wc(1)
基於Linux Socket程序設計實現wc(1)服務器(端口號是你學號的後6位)和客戶端
客戶端傳一個文本文件給服務器
服務器返加文本文件中的單詞數
上方提交代碼
附件提交測試截圖,至少要測試附件中的兩個文件
client.c:
#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_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 :\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, "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(client_socket_fd, buffer, BUFFER_SIZE, 0)) > 0) { if(fwrite(buffer, sizeof(char), length, fp) < length) { printf("File:\t%s Write Failed\n", file_name); break; } bzero(buffer, BUFFER_SIZE); } // 接收成功後,關閉文件,關閉socket printf("Send File:\t%s Successful!\n", file_name); close(fp); close(client_socket_fd); return 0; }
server.c:
#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 int countChar(char input[]){ int p_input = 0; int count = 0; int word = 0; char ch; while(p_input < strlen(input)){ ch = input[p_input]; if(ch==‘ ‘){ if(word){ //讀取到空字符,而之前是非空字符,則說明讀完了一個單詞 count++; word = 0; } }else{ //讀取到第一個非空字符,說明是單詞的開始 word = 1; } p_input++; } return count; } 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); char ch; 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, "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(new_server_socket_fd, buffer, length, 0) < 0) { printf("Send File:%s Failed./n", file_name); break; } bzero(buffer, BUFFER_SIZE); } char input[256]; int p_input = 0; int count = 0; fgets(input,256,fp); // 關閉文件 fclose(fp); printf("File:%s Recieve Successful!\n", file_name); count = countChar(input); printf("Your words count:\n%d\n",count); } // 關閉與客戶端的連接 close(new_server_socket_fd); } // 關閉監聽用的socket close(server_socket_fd); return 0; }
運行截圖:
實驗三-並發程序-2
使用多線程實現wc服務器並使用同步互斥機制保證計數正確
上方提交代碼
下方提交測試
對比單線程版本的性能,並分析原因
wc命令的實現:
#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; }
運行截圖:
多線程編程時要調用pthread_create()
函數創建新的進程,在運行時要使用-lpthread
鏈接庫函數。
核心代碼為:
pthread_t pid;
if(pthread_create(&pid, NULL, process_client, &client_sock) < 0){
printf("pthread_create error\n");
實驗三-並發程序-3
交叉編譯多線程版本服務器並部署到實驗箱中
PC機作客戶端測試wc服務器
提交測試截圖
由於時間原因並沒有做。
實驗中的問題和解決過程
實驗過程的一些思考
什麽是多線程?
多線程編程的目的,就是"最大限度地利用CPU資源",當某一線程的處理不需要占用CPU而只和I/O,OEMBIOS等資源打交道時,讓需要占用CPU資源的其它線程有機會獲得CPU資源。每個程序執行時都會產生一個進程,而每一個進程至少要有一個主線程。多線程就是在一個進程內有多個線程。從而使一個應用程序有了多任務的功能。
wc命令
wc -c filename:顯示一個文件的字節數
wc -m filename:顯示一個文件的字符數
wc -l filename:顯示一個文件的行數
wc -L filename:顯示一個文件中的最長行的長度
wc -w filename:顯示一個文件的字數
新學到的知識點
- wc命令
- 多線程編程
實驗體會
本次實驗的實驗內容雖然不多,但實現起來很復雜,雖然課前已經花時間做了一點,但是課上的時間內還是沒有完成。
參考資料
- Linux下C編寫基本的多線程socket服務器
2017-2018-1 20155227 實驗三 實時系統