2018-2019-1 20165230 實驗三 實時系統
阿新 • • 發佈:2018-11-18
方式 更新 是不是 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
實驗過程
使用多線程實現wc服務器並使用同步互斥機制保證計數正確
- 對比單線程版本的性能,並分析原因
- 客戶端代碼不變
- 互斥鎖:互斥鎖是用加鎖的方式來控制對公共資源的操作(一旦開始進行就不會被打斷的操作)
在同一時刻只有一個線程能夠對互斥鎖進行操作;只有上鎖的進程才可以對公共資源進行訪問,除該進程之外,其他進程只能等到上鎖進程解鎖才能對公共資源進行操作。 - 修改服務器代碼,使之成為多線程服務器。
#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
運行截圖
分析原因
- 單線程容易實現,但是一次只允許一個客戶端連接。
- 多線程更復雜,但是一次允許多個客戶端,工作效率更高。
- 單線程保證單用戶的安全性,但多線程保證任務的高效性。
實驗中的問題及解決過程
- 使用socket編程的時候,發現用書上的知識無法直接傳輸文件,結合老師講過的web編程,客戶端使用send和recv實現文件傳輸,但是運行的時候報錯,出現段錯誤(核心已轉儲)
- 問題解決:檢查發現存在地址越界的錯誤,將代碼改過之後,代碼運行成功。
- 統計出的結果與預想結果不一樣,總會存在誤差,
問題解決:查找資料顯示,是因為將測試文件編輯的時候,從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 實驗三 實時系統