2018-2019 20165216 实验三 并发程序
2018-2019 20165216《信息安全系统设计基础》实验三 并发程序
实验三-并发程序-1
任务详情
- 学习使用Linux命令wc(1)
- 基于Linux Socket程序设计实现wc(1)服务器(端口号是学号的后6位)和客户端
客户端传一个文本文件给服务器
服务器返回文本文件中的单词数 - 上传代码并至少测试附件中的二个文件
实验步骤
使用man -k wc查找相关命令
继续使用man 1 wc查找相关指令
代码展示 serve.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 165216 #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; }
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 165216 #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; }
运行截图
实验三-并发程序-2
任务详情
- 使用多线程实现wc服务器并使用同步互斥机制保证计数正确
- 上方提交代码
- 下方提交测试
- 对比单线程版本的性能,并分析原因
代码展示
serve2.c
#include <stdio.h> #include <fcntl.h> #include <pthread.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #define PORT 165216 #define BUFF_SIZE 1024 #define LISTEN_SIZE 20 typedef struct{ char type; char data[BUFF_SIZE]; }m_package; void* process_client(); int main(){ int ss = create_tcp_server(PORT); if(-1 == ss) exit(-1); while(1){ //接受客户端连接 socklen_t addrlen = sizeof(struct sockaddr); struct sockaddr_in client_addr; //客户端地址结构 int client_sock = accept(ss, (struct sockaddr*)&client_addr, &addrlen); if(client_sock < 0){ printf("accept error\n"); } printf("accept success\n"); pthread_t pid; if(pthread_create(&pid, NULL, process_client, &client_sock) < 0){ printf("pthread_create error\n"); } } } //处理客户端程序 void *process_client(void *arg){ int size = 0, fd, count = 0, sockid = *(int*)arg; m_package pac; long total = 0, cur = 0; //循环接收文件 while(1) { memset(&pac, 0, sizeof(pac)); size = read(sockid, &pac, sizeof(pac)); if(size > 0){ if (pac.type == 1){ fd = open(pac.data, O_CREAT|O_WRONLY, 0777); if(-1 == fd){ printf("open file error!\n"); continue; } count = total = cur = 0; } else if (pac.type == 2){ cur += write(fd, pac.data, strlen(pac.data)); if(count++ % 5000 == 0){ printf("recv from client < %d > : %.01lf\%\n", sockid, cur * 100.0 / total); count = 0; } } else if (pac.type == 3){ printf("recv from client < %d > : 100.0\%\n", sockid); printf("recv success\n"); close(fd); } else if(pac.type == 4){//文件长度 total = strtol(pac.data, NULL, 10); printf("%ld\n", total); } }else{ printf("client disconnected\n"); close(sockid); break; } } return 0; } int start_server(int port, int type){ //建立服务器套接字 int ss = socket(AF_INET, type, 0); if(ss < 0){ printf("create socket error\n"); return -1; } //设置服务器地址 struct sockaddr_in server_addr; //服务器地址结构 bzero(&server_addr, sizeof(struct sockaddr_in)); //清零 server_addr.sin_family = AF_INET; //协议族 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //ip地址 server_addr.sin_port = htons(port); //端口 //绑定地址结构到套接字描述符 if(bind(ss, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0){ printf("bind error\n"); return -1; } //TCP if(SOCK_STREAM == type){ //设置侦听 if(listen(ss, LISTEN_SIZE) < 0){ printf("listen error\n"); return -1; } printf("tcp server start\n"); } else printf("udp server start\n"); return ss; } int create_tcp_server(int port){ start_server(port, SOCK_STREAM); } int create_udp_server(int port){ start_server(port, SOCK_DGRAM); }
client2.c
#include <stdio.h> #include <fcntl.h> #include <pthread.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #define PORT 165216 #define BUFF_SIZE 1024 #define LISTEN_SIZE 20 typedef struct{ char type; char data[BUFF_SIZE]; }m_package; int main(){ //创建连接 int sock_fd = connect_tcp("127.0.0.1", PORT); if(-1 == sock_fd) return -1; m_package pac; int fd, cur = 0, count = 0; long filesize = 0; while(1){ //打开文件 memset(&pac, 0, sizeof(pac)); pac.type = 1; // strcpy(pac.data, "/home/SKZH/a.txt"); scanf("%s", pac.data); //获取文件信息 struct stat sfile; stat(pac.data, &sfile ); filesize = sfile.st_size; time_t t; long begin = time(&t); cur = count = 0; fd = open(pac.data, O_RDONLY); if(-1 == fd){ printf("file open error\n"); continue; } //读取文件并发送 //发送文件名 strcpy(pac.data, strrchr(pac.data, '/') + 1); write(sock_fd, &pac, sizeof(pac)); memset(&pac, 0, sizeof(pac)); //发送文件长度 pac.type = 4; sprintf(pac.data,"%ld",filesize); write(sock_fd, &pac, sizeof(pac)); memset(&pac, 0, sizeof(pac)); int read_len = 0; while((read_len = read(fd, pac.data, BUFF_SIZE)) > 0){ pac.type = 2; write(sock_fd, &pac, sizeof(pac)); memset(&pac, 0, sizeof(pac)); cur += read_len; if(count++ % 5000 == 0){ count = 0; printf("send to server : %.1lf\%\n", cur * 100.0 / filesize); } } //发送结束标记 memset(&pac, 3, sizeof(pac)); write(sock_fd, &pac, BUFF_SIZE + 1); close(fd); printf("send to server : 100.0\%\n"); printf("file size : %d B\n", filesize); printf("time : %ld ms\n", time(&t) - begin); printf("send file success\n"); printf("------------------------\n"); } close(sock_fd); } int connectsock(char* server_ip, int server_port, int type){ int sock_fd = socket(AF_INET, type, 0); if(-1 == sock_fd){ printf("create socket error\n"); return -1; } struct sockaddr_in server_addr; //设置服务器地址 bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(server_port); inet_pton(AF_INET, server_ip, &server_addr.sin_addr); //连接服务器 if(-1 == connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in))){ printf("connect server error\n"); return -1; } printf("connect server success\n"); return sock_fd; } int connect_tcp(char* server_ip, int server_port){ return connectsock(server_ip, server_port, SOCK_STREAM); } int connect_udp(char* server_ip, int server_port){ return connectsock(server_ip, server_port, SOCK_DGRAM); }
运行截图
对比单线程的性能,并分析原因
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的。而多线程,则是多个执行流的意思。而这样做的好处是显而易见的,它可以将任务的多个部分分给不同线程,各自的完成自己的任务,增加时间效率,而单线程必须在上一任务完成的情况下才能进行下个任务。
而且,多线程还可以提升CPU的利用率,假如顺序完成任务中的一环需要占用的CPU是10%,那么剩下的90%就可以说是浪费了,而多线程能够提升这个利用率。
当然,线程越多并不是越好,如果希望通过多线程执行任务让程序运行得更快,会面临非常多的挑战,比如上下文切换的问题、死锁的问题,以及受限于硬件和软件的资源限制问题.
就上下文切换来说,cpu以很高的频率进行线程的切换而分配给线程,让我们感觉是同时进行的,假如任务简单而我们又分配多线程,其中的协调所用的资源反而会更多。
这个就如同电脑核心一般,电脑的核心并不是越多越好,就拿游戏说,现在的游戏优化大多以双核以及四核为核心,多的核心反而浪费,假如你有10个核心,玩个1核优化的游戏,那就真的是“一核有难,九核围观”了。