2017-2018-1 20155201 《信息安全系統設計基礎》實驗三 實時系統
阿新 • • 發佈:2017-11-19
byte each spec 實驗代碼 max ons 統計字數 ets mark
2017-2018-1 20155201 實驗三 實時系統
一、實驗內容
- 基於Linux Socket程序設計實現wc(1)服務器和客戶端
- 使用多線程實現wc服務器
二、實驗步驟
- 基於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; }
使用多線程實現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 《信息安全系統設計基礎》實驗三 實時系統