2018-2019-1 信息安全系统设计实验三 并发编程 20165207 20165209 20165215
2018-2019-1 信息安全系统设计实验三 并发编程 20165207 20165209 20165215
任务一 学习Linux下的wc命令
1.准备
要求如下:
基于Linux Socket程序设计实现wc(1)服务器(端口号是你学号的后6位)和客户端
客户端传一个文本文件给服务器
服务器返加文本文件中的单词数
首先在Linux下学习以及使用wc命令:man wc查看wc的功能:
得知参数-w是用来统计单词的数量的。然后对老师给的两个测试文件使用完成-l命令查看输出结果
两个操作的截图如下:
所以文件test1的单词数量是:1576,文件test2的单词数量是:216375。
1.wc -w功能的实现
根据man手册中的解释,wc是根据被空格界定出来的字符串来判断一个单词的出现的。所以我们首先设想的是用库函数fscanf读取字符串来实现一个单词的计数。为了实现命令行传入文件名的效果,使用了main函数的参数。
但是要注意的是,传入的作为文件名的参数是下标为1的那个,下标为0的是参数和命令之间的空格
根据c语言的经验,fscanf这个函数经常会出错,不出所料:
于是更改了策略,使用了库函数fgetc,通过fgetc这个函数读进来两个字符一前一后地进行匹配,如果前一个字符不是“回车(\n)换行(\r)制表键tab(\t)空格”当中的任意一个;并且后面一个字符是这四个当中的任意一个。这样的两个子串足以说明一个广义的单词的存在。
代码如下:
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[]){
/*字符后面跟一个空格说明前面是一个单词*/
char ch,space;
int count;
char s[100];
FILE *fp = NULL;
fp = fopen(argv[1],"r");
if(fp==NULL){
printf("文件打开失败\n");
return 0;
}
else{
ch = fgetc(fp); space = fgetc(fp);
while(space !=EOF){
if(((ch!=' ')&&(ch!='\n')&&(ch!='\t')&&(ch!='\r'))&&((space==' ')||(space=='\n')||(space=='\t')||(space=='\r')))
count++;
ch = space;
space = fgetc(fp);
}
}
printf("%d\t%s\n",count,argv[1]);
return 0;
}
核心的部分是:
while(space !=EOF){
if(((ch!=' ')&&(ch!='\n')&&(ch!='\t')&&(ch!='\r'))&&((space==' ')||(space=='\n')||(space=='\t')||(space=='\r')))
count++;
ch = space;
space = fgetc(fp);
}
运行截图,即在命令行中使用实现的mywc-l统计的单词结果如下:
2.实现socket的服务器和客户端
这一部分的程序借鉴了之前在网络安全编程课程里已经编写好并且验收通过的socket程序,但是要注意的是在Linux里要使用和windows中不同的头文件。更改完头文件以及相应细节之后的代码如下:
服务器端
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#define PORT 7915//端口号
#define BACKLOG 5/*最大监听数*/
int main(){
int sockfd,new_fd;/*socket句柄和建立连接后的句柄*/
struct sockaddr_in my_addr;/*本方地址信息结构体,下面有具体的属性赋值*/
struct sockaddr_in their_addr;/*对方地址信息*/
int sin_size;
char a[100];
char filename[100];
FILE *fp;
char buf[20];
int length;
sockfd=socket(AF_INET,SOCK_STREAM,0);//建立socket
if(sockfd==-1){
printf("socket failed:%d",errno);
return -1;
}
my_addr.sin_family=AF_INET;/*该属性表示接收本机或其他机器传输*/
my_addr.sin_port=htons(PORT);/*端口号*/
my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*IP,括号内容表示本机IP*/
bzero(&(my_addr.sin_zero),8);/*将其他属性置0*/
if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0){//绑定地址结构体和socket
printf("bind error");
return -1;
}
listen(sockfd,BACKLOG);//开启监听 ,第二个参数是最大监听数
sin_size=sizeof(struct sockaddr_in);
new_fd=accept(sockfd,(struct sockaddr*)&their_addr,&sin_size);//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小
if(new_fd==-1){
printf("receive failed");
close(sockfd);
return 0;
} else{
printf("请求传输文件,待同意...\n");
send(new_fd,"服务器端要开始传输文件了,如果准备就绪请输入y后回车",100,0);//发送内容,参数分别是连接句柄,内容,大小,其他信息(设为0即可
recv(new_fd,a,24,0);
printf("%s\n",a);
printf("请输入要传输的文件名:\n");
scanf("%s",filename);
//send(new_fd,filename,20,0);
if((fp=fopen(filename,"r"))==NULL){
printf("文件打开失败\n");
}
else{
while((length=fread(buf,sizeof(char),20,fp))>0){
// printf("%s",buf);
send(new_fd,buf,length,0);
//bzero(buf,sizeof(buf));
}
// WPScleanup();
}
fclose(fp);
close(new_fd);
close(sockfd);
}
return 0;
}
客户端
#include <arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<unistd.h>
#define DEST_PORT 7915//目标地址端口号
#define DEST_IP "127.0.0.1"/*目标地址IP,这里设为本机*/
#define MAX_DATA 100//接收到的数据最大程度
int main(){
int sockfd,new_fd;/*cocket句柄和接受到连接后的句柄 */
struct sockaddr_in dest_addr;/*目标地址信息*/
char buf[MAX_DATA];//储存接收数据
FILE *fp;
char ch;
char a [20];
sockfd=socket(AF_INET,SOCK_STREAM,0);/*建立socket*/
if(sockfd==-1){
printf("socket failed:%d",errno);
}
int length;
char agree;
//参数意义见上面服务器端
dest_addr.sin_family=AF_INET;
dest_addr.sin_port=htons(DEST_PORT);
dest_addr.sin_addr.s_addr=inet_addr(DEST_IP);
bzero(&(dest_addr.sin_zero),8);
if(connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr))==-1){//连接方法,传入句柄,目标地址和大小
printf("connect failed:%d",errno);//失败时可以打印errno
close(sockfd);
return 0;
} else{
//printf("connect success");
recv(sockfd,buf,100,0);//将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。
printf("%s\n",buf);
scanf("%c",&agree);
if(agree=='y'){
printf("即将开始接收文件\n");
send(sockfd,"客户端已同意接收",24,0);
//printf("请输入要接收到哪个文件\n");
//scanf("%s",filename);
fp=fopen("b.txt","wt+");
bzero(buf,sizeof(buf));
while((length=recv(sockfd,a,20,0))>0){
fwrite(a,sizeof(char),length,fp);
}
}
}
//WSAcleanup();
fclose(fp);
close(sockfd);
int flag=0,num=0;
fp = fopen("b.txt","r");
while((ch=fgetc(fp))!=EOF)
{
if(ch==' ' || ch=='\n' || ch=='\t' || ch=='\r')
{
flag=0;
}
else
{
if(flag==0)
{
flag=1;
num++;
}
}
}
printf("%d\n",num);
fclose(fp);
return 0;
}
运行结果
任务二 使用多线程编程实现wc服务器并使用同步互斥机制保证计数正确
任务描述:
使用多线程实现wc服务器并使用同步互斥机制保证计数正确
对比单线程版本的性能,并分析原因
客户端端程序
#include<netinet/in.h> // for sockaddr_in
#include<sys/types.h> // for socket
#include<sys/socket.h> // for socket
#include<stdio.h> // for printf
#include<stdlib.h> // for exit
#include<string.h> // for bzero
#define HELLO_WORLD_SERVER_PORT 165209
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int mywc(char file_name[],int choose);
int main(int argc, char **argv)
{
FILE *fp;
if (argc != 2)
{
printf("Usage: ./%s ServerIPAddress\n", argv[0]);
exit(1);
}
// 设置一个socket地址结构client_addr, 代表客户机的internet地址和端口
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(client_addr));
client_addr.sin_family = AF_INET; // internet协议族
client_addr.sin_addr.s_addr = htons(INADDR_ANY); // INADDR_ANY表示自动获取本机地址
client_addr.sin_port = htons(0); // auto allocated, 让系统自动分配一个空闲端口
// 创建用于internet的流协议(TCP)类型socket,用client_socket代表客户端socket
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0)
{
printf("Create Socket Failed!\n");
exit(1);
}
// 把客户端的socket和客户端的socket地址结构绑定
if (bind(client_socket, (struct sockaddr*)&client_addr, sizeof(client_addr)))
{
printf("Client Bind Port Failed!\n");
exit(1);
}
// 设置一个socket地址结构server_addr,代表服务器的internet地址和端口
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
// 服务器的IP地址来自程序的参数
if (inet_aton(argv[1], &server_addr.sin_addr) == 0)
{
printf("Server IP Address Error!\n");
exit(1);
}
server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
socklen_t server_addr_length = sizeof(server_addr);
// 向服务器发起连接请求,连接成功后client_socket代表客户端和服务器端的一个socket连接
if (connect(client_socket, (struct sockaddr*)&server_addr, server_addr_length) < 0)
{
printf("Can Not Connect To %s!\n", argv[1]);
exit(1);
}
char file_name[FILE_NAME_MAX_SIZE + 1];
bzero(file_name, sizeof(file_name));
printf("Please Input File Name.\t");
scanf("%s", file_name);
if((fp = fopen(file_name,"r"))==NULL)
{
printf("Failure to open %s\n",file_name);
exit(0);
}
char buffer[BUFFER_SIZE];
bzero(buffer, sizeof(buffer));
strcpy(buffer,file_name);
if(send(client_socket,buffer,BUFFER_SIZE,0)==-1)
{
printf("发送文件名失败\n");
}
char ch;
int i=0;
while((ch=fgetc(fp))!=EOF)
{
buffer[i++]=ch;
if(i>=BUFFER_SIZE)
{
if((send(client_socket, buffer, BUFFER_SIZE, 0))==-1)
{
printf("发送文件失败\n");
}
bzero(buffer, sizeof(buffer));
i=0;
}
}
if(i<BUFFER_SIZE)
{
if((send(client_socket, buffer, i, 0))==-1)
{
printf("发送文件失败\n");
}
}
printf("发送%s完毕\n",file_name);
mywc(file_name,2);
// 向服务器发送buffer中的数据,此时buffer中存放的是客户端需要接收的文件
//以下接收服务器发来的单词个数
bzero(buffer, sizeof(buffer));
// int length = 0;
/* int length = recv(client_socket, buffer, sizeof(buffer), 0);
if (length < 0)
{
printf("Recieve Data From Server %s Failed!\n", argv[1]);
}
else
{
printf("Recieve words number %c From Server[%s] Finished!\n",buffer[0],argv[1]);
}
bzero(buffer, BUFFER_SIZE); */
// 传输完毕,关闭socket
fclose(fp);
close(client_socket);
return 0;
}
int mywc(char file_name[],int choose)
{
FILE *fp;
char ch;
int flag=0,num=0;
// int choose=2;
if((fp = fopen(file_name,"r"))==NULL)
{
printf("Failure to open %s\n",file_name);
exit(0);
}
if(choose==2)
{
while((ch=fgetc(fp))!=EOF)
{
if(ch==' ' || ch=='\n' || ch=='\t' || ch=='\r')
flag=0;
else
{
if(flag==0)
{
flag=1;
num++;
}
}
}
}
printf("单词个数为:%d\n",num);
fclose(fp);
return num;
}
服务器端程序
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#define HELLO_WORLD_SERVER_PORT 165209
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
void *process_client(void *new_server_socket);
int mywc(char file_name[])
{
char ch;
int flag=0,num=0;
int choose=2;
FILE *fp;
if((fp = fopen(file_name,"r"))==NULL)
{
printf("Failure to open %s\n",file_name);
exit(0);
}
if(choose==2)
{
while((ch=fgetc(fp))!=EOF)
{
if(ch==' ' || ch=='\n' || ch=='\t' || ch=='\r')
flag=0;
else
{
if(flag==0)
{
flag=1;
num++;
}
}
}
}
printf("单词个数为:%d\n",num);
fclose(fp);
return num;
}
int main(int argc, char **argv)
{
// set socket's address information
// 设置一个socket地址结构server_addr,代表服务器internet的地址和端口
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(HELLO_WORLD_SERVER_PORT);
// create a stream socket
// 创建用于internet的流协议(TCP)socket,用server_socket代表服务器向客户端提供服务的接口
int server_socket = socket(PF_INET, SOCK_STREAM, 0);
if (server_socket < 0)
{
printf("Create Socket Failed!\n");
exit(1);
}
// 把socket和socket地址结构绑定
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)))
{
printf("Server Bind Port: %d Failed!\n", HELLO_WORLD_SERVER_PORT);
exit(1);
}
// server_socket用于监听
if (listen(server_socket, LENGTH_OF_LISTEN_QUEUE))
{
printf("Server Listen Failed!\n");
exit(1);
}
// 服务器端一直运行用以持续为客户端提供服务
while(1)
{
// 定义客户端的socket地址结构client_addr,当收到来自客户端的请求后,调用accept
// 接受此请求,同时将client端的地址和端口等信息写入client_addr中
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
// 接受一个从client端到达server端的连接请求,将客户端的信息保存在client_addr中
// 如果没有连接请求,则一直等待直到有连接请求为止,这是accept函数的特性,可以
// 用select()来实现超时检测
// accpet返回一个新的socket,这个socket用来与此次连接到server的client进行通信
// 这里的new_server_socket代表了这个通信通道
int new_server_socket = accept(server_socket, (struct sockaddr*)&client_addr, &length);
printf("连接到客户端\n");
if (new_server_socket < 0)
{
printf("Server Accept Failed!\n");
}
//添加进程相关代码
pthread_t pid;
if(pthread_create(&pid, NULL, process_client,(void *) &new_server_socket) < 0){
printf("pthread_create error\n");
}
}
// close(server_socket);
}
void *process_client(void *new_server_socket)
{
int sockid=*(int *)new_server_socket;
FILE *fp;
//接受来自客户端的文件
char buffer[BUFFER_SIZE];
char file_name[FILE_NAME_MAX_SIZE];
bzero(buffer, sizeof(buffer));
int length=0;
if(recv(sockid,buffer,BUFFER_SIZE, 0)==-1)
{
printf("接受文件名%s失败\n",buffer);
}
strcpy(file_name,buffer);
strcat(file_name,"-server");
if((fp = fopen(file_name,"w"))==NULL)
{
printf("Failure to open %s\n",file_name);
exit(0);
}
while( length = recv(sockid, buffer, BUFFER_SIZE, 0))
{
if(length<0)
{
printf("接受文件出错\n");
exit(0);
}
if(fwrite(buffer,sizeof(char),length,fp)<length)
{
printf("写文件失败\n");
}
bzero(buffer, BUFFER_SIZE);
}
fclose(fp);
printf("接受文件完毕\n");
int number=0;
number=mywc(file_name);
bzero(buffer, BUFFER_SIZE);
buffer[0]=number+48;
// 发送buffer中的字符串到new_server_socket,实际上就是发送给客户端
/*if (send(new_server_socket, buffer, sizeof(buffer), 0) < 0)
{
printf("Send number Failed!\n");
}
printf("发送单词个数完毕\n");*/
bzero(buffer, sizeof(buffer));
// fclose(fp);
printf("File Transfer Finished!\n");
close(new_server_socket);
}
问题与解决
1. 换行符与回车符在读取时的差异
上面实现mywc时,从截图中可以看到,运行结果是1776而不是1576。而且test1.txt这个文件正好有200行,所以肯能是回车符的问题。但是实际上已经考虑了回车符‘\n’。在回忆文件打开的细节时,读取文件到下一行一定会读出来一个换行符‘\r’和一个紧接着的回车符‘\n’,尽管输出时只按着回车符输出,但是读取时两个字符应该是都有的。所以应该是没有考虑到换行符‘\r’,加上去之后,程序才正确输出了1576这个结果。
if(((ch!=' ')&&(ch!='\n')&&(ch!='\t')&&(ch!='\r'))&&((space==' ')||(space=='\n')||(space=='\t')||(space=='\r')))
2. 在服务器端的程序内部调用可执行文件mywc-l以获得输出的单词统计数量。
在程序内部调用可执行文件或者执行命令这个问题,经过搜索,调用stdlib.h头文件中的system函数就可以了。但是system只会在命令行里输出运行的结果。如果要获得这个运行结果存入一个变量并且发送给客户端,就远远没有一个system这么简单了。经过一段时间的了解,我发现把命令行执行的结果存入到程序的变量当中是需要Linux当中的“管道”的。
因为之前没有学习过管道,所以没有办法详细解释,在实现过程中也没有用这种“高大上”方法,只是把实现的mywc-l中的程序中的大部分复制到了模拟服务器端的程序当中。在上面代码当中有所体现。
如果我之后学习会了调用可执行文件,并且不只是输出结果,而且能够把执行的结果存入到程序中的变量里,再回来改写这篇实验报告。