2017-2018-1 《信息安全系统设计基础》实验三报告
2017-2018-1 《信息安全系统设计基础》实验三报告
————————CONTENTS————————
任务一 C语言模拟wc命令
使用man wc
命令查看wc命令的基本用法:
可知wc命令的功能为:统计指定文件中的字节数、字数、行数等,并将统计结果显示输出。常用的参数为:
- -c:统计字节数
- -l:统计行数
- -m:统计字符数,且不能与-c参数一起使用
- -w:统计字数,一个字被定义为由空白、跳格或换行字符分割的字符串
- -L:打印最长行的长度
- ......
但是,如果我们想统计某文件中出现过某个特定单词的行数,只用wc命令是无法完成的。我们可以借助管道将wc命令与其他命令(如grep)串联起来:
grep and test.txt | wc -l
上面命令实现了查找test.txt中所有出现过“and”这个单词的行,并统计行数。
再进一步,如果想精确到个数(比如一行出现两次,算作2),可以加上参数-o选项(only),表示只选中那些匹配的地方,结果为:
基于以上分析,代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void wc_func(char *file,int ism,int isw,int isl);
int main(int argc ,char *argv[])
{
int ism,isw,isl,opt;
ism = isw = isl = 0;
int count = 0;
while((opt=getopt(argc,argv,"mwl"))!=-1)
{
count++;
switch(opt)
{
case 'm':
ism=1;
break;
case 'w':
isw=1;
break;
case 'l':
isl=1;
break;
case '?':
printf("请查看该指令说明文档 %c\n",optopt);
exit(0);
}
}
if(count==0)
{
ism=isw=isl=1;
}
if(optind==argc)
{
printf("wc error: have no file!\n");
}
for(;optind<argc;optind++)
{
wc_func(argv[optind],ism,isw,isl);
}
}
void wc_func(char *file,int ism,int isw,int isl)
{
int t,m,w,l;
int state = 0;
FILE *in;
if((in = fopen(file,"r"))==NULL)
{
printf("wc %s:no this file or dir\n",file);
return;
}
w=m=l=0;
while((t=fgetc(in))!=EOF)
{
/*if(t=='\t'||t==' ')
{
w++;
}
else if(t=='\n')
{
l++;
}*/
if(t == '\n') {
l++;
state = 0;
continue;
} else if(t == ' ') {
state = 0;
continue;
} else if(t == '\r') {
state = 0;
continue;
} else {
if(state == 0) {
state = 1;
w++;
}
continue;
}
m++;
}
if(isl)
printf("%-5d",l);
if(isw)
printf("%-5d",w);
if(ism)
printf("%-5d",m);
printf("%-10s\n",file);
}
运行结果如下:
任务二 实现传送文本文件的服务器和客户端
虽然在网络安全编程基础课程上学习过网络编程的相关知识,但基于的是Windows。将其移植到Linux下时需要注意以下几个方面:
- 头文件
Windows下winsock.h或winsock2.h;
Linux下netinet/in.h(包括大部分),unistd.h(包括close函数),sys/socket.h。
- 初始化
windows下需要用WSAStartup启动Ws2_32.lib;
linux下不需要。
- 关闭socket
windows下使用closesocket();
linux下使用close()。
- 类型
windows下SOCKET;
linux下int。
- 多线程(下一个任务会用到)
windows下包含process.h,使用_beginthread和_endthread;
linux下包含pthread.h,使用pthread_create和pthread_exit。
- ......
以上是我在移植过程中遇到的问题,需格外注意。更多情况可参考Socket程序从windows移植到linux的注意事项等相关文章。
基于客户端与服务器的通信流程,可分别写出创建服务器和客户端,以及客户端和服务器连接的代码:
/*创建服务器:*/
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);
}
/*接受客户端连接:*/
socklen_t addrlen = sizeof(struct sockaddr);
struct sockaddr_in client_addr; //客户端地址结构
client_sock = accept(ss, (struct sockaddr*)&client_addr, &addrlen);
if(client_sock < 0){
printf("accept error\n");
}
printf("accept success\n");
/*客户端:*/
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);
}
搭建好客户端和服务器,接下来考虑如何传输文件。
传输数据需要用到缓冲区。客户端首先输入文件名,并判断该文件是否存在。若不存在,则返回错误提示;存在,则发送至服务器。服务器接收到数据后,创建一个同样名称的文件。至此,完成了最基础的一步。
传输文件内容的思路上面类似,但需注意:文件名称一般很短,但文件内容大小并不确定。所以不要奢望一次性传输完所有的数据,而应设置循环,以固定大小传输数据,一直到传输结束。
传输数据部分的代码如下:
/*客户端:*/
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));
//向服务器发送buffer中的数据
send(client_socket,buffer,BUFFER_SIZE,0);
FILE * fp = fopen(file_name,"r");
if(NULL == fp )
{
printf("File:\t%s Not Found\n", file_name);
exit(1);
}
else
{
bzero(buffer, BUFFER_SIZE);
int file_block_length = 0;
while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE, fp))>0)
{
//printf("file_block_length = %d\n",file_block_length);
//发送buffer中的字符串到服务器
if(send(client_socket,buffer,file_block_length,0)<0)
{
printf("Send File:\t%s Failed\n", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}
}
printf("Send File:\t %s To Server[%s] Finished\n",file_name, argv[1]);
printf("The File has %d words.\n", wc_func(file_name));
fclose(fp);
/*服务器:*/
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
char buffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
recv(new_server_socket,file_name,BUFFER_SIZE,0);
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 len = 0;
while( len = recv(new_server_socket,buffer,BUFFER_SIZE,0))
{
if(len < 0)
{
printf("Recieve Data From Client %s Failed!\n", argv[1]);
break;
}
int write_length = fwrite(buffer,sizeof(char),len,fp);
if (write_length<len)
{
printf("File:\t%s Write Failed\n", file_name);
break;
}
bzero(buffer,BUFFER_SIZE);
}
printf("File:\t%s Transfer Finished!\n",file_name);
fclose(fp);
按照要求,在服务器端调用计算单词数的函数,并返回给客户端即可。
【注:完成代码已上传至码云】
任务三 多线程实现传送文本文件的服务器和客户端
通过man -k
命令查看与创建进程相关的函数:
我们可以找到“pthread_create()”函数,使用man 3 pthread_create
命令可以得知其用法:
说明:
- thread:线程标识符;
- attr:线程属性设置;
- start_routine:线程函数的起始地址;
- arg:传递给start_routine的参数;
- 返回值:成功,返回0;出错,返回-1。
- 注意:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在线程函数在编译时,需要使用“-lpthread”链接库函数。
因此,服务器需要循环检测是否有新的连接。如果有,则调用pthread_create()
函数创建新的进程,并执行相关代码。这部分的代码如下:
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");
}
}
创建了新的线程,接下来就可以传送文件了。过程与任务二类似。
【注:完成代码已上传至码云】
任务四 使用PC机和实验箱模拟客户端服务器并测试(未完成)
实验感想与体会
- 此次实验首先在Linux下实现了客户端与服务器传送文本文件,并模拟wc命令统计文本文件中的单词数。但一次只能为一个客户端提供服务的迭代网络是不现实的,因此,在任务二中创建了一个并发服务器,它为每一个客户端创建一个单独的逻辑流。这就允许服务器同时为多个客户端服务,提高了其使用价值。
- 在完成任务三的过程中遇到了重重困难。本打算在自己笔记本上完成,也方便后续学习,但参考指导书《实验开发环境使用说明-12.04》中“解决上网与本地网络调试冲突”的相关讲解完成配置后,实验箱与主机仍然无法ping通。想到之前实验一在实验室的PC机上完成得比较顺利,于是改用实验室的PC机,但找不到正确的端口,更换了PC机和实验箱后仍然解决不了,只能作罢。
- 近期学习效率低下,每周一章的任务常常不能按时完成,总需要后期抽时间补充。回想上学期学习Java时,虽然也是一周学习一章,但并没有觉得如此吃力,花十几个小时就能完成当周的任务。尤其这次实验结束,花费大量时间也没有解决问题,心情失落的同时,不禁怀疑自己的自学能力。且不说与老师的要求相差甚远,这种学习效率与状态连自己都不能接受。课本内容较上学期更难更深是一方面,但更多的是自身的问题。比如没有合理分配时间,做到门门课程兼顾;比如将精力浪费在一些无关紧要的事情上,等等。
- 课下了解到,不少同学也遇到了类似的问题。能及时赶上老师的进度还好,一旦某一阵略有松懈没跟上节奏,就容易产生恶性循环,落下越来越多的内容。老师一直以来采取的都是自学为主,教学为辅的方式,这一点无可厚非,我认为自学能力可以让人终生受益。不怕自学的过程中遇到困难,怕就怕在总是遇到无法解决的困难而逐渐放弃。
- 前几天不慎扭伤了脚,不能在寝室、主楼和图书馆之间随意跑的日子还真不适应,只能躺着休息,因此对好多任务都心有余而力不足...多亏朋友们悉心的照顾还有及时涂药,脚在一天天康复,落下的事情也要一点点补回来。那么,就先从努力按时完成本周学习任务开始吧:)