实验成员:20165223
实验名称:实时系统--并发程序
目录
一、实验内容
二、实验总结
一、实验内容
任务一:基于Linux Socket程序设计实现wc服务器和客户端
(一)实验要求
0.学习使用Linux命令wc
1.基于Linux Socket程序设计实现wc服务器(端口号是你学号的后6位)和客户端
2.客户端传一个文本文件给服务器
3.服务器返加文本文件中的单词数
4.上方提交代码,附件提交测试截图,至少要测试附件中的两个文件
(二)实验步骤
- 1.先使用
man wc
了解wc命令的功能与用法
- 2.了解wc的各命令参数的用法
参数 | 用法 |
---|---|
-c | 统计字节数 |
-l | 统计行数 |
-m | 统计字符数,不能与 -c 连用 |
-w | 统计字数,一个字被定义为由空白、跳格或换行字符分隔的字符串 |
-L | 打印最长行的长度 |
-help | 显示帮助信息 |
--version | 显示版本信息 |
- 3.对 test1.txt 和 test2.txt 试验 wc -w 功能(wc命令使用实例)
- 4.根据题目含义,编写实现 wc -w 功能代码(方便之后添加进代码中)
- 5.编写服务器端代码,客户端代码,实现传送文本文件的服务器和客户端
- 6.分析代码,编译运行,测试结果
(三)实验结果
- 用socket编程实现
- 用wc命令检查
(四)代码分析
(1)实验代码
- 完整代码:码云代码 实验三 T1
(2)代码分析
1.首先了解Linux下是如何进行socket编程的
2.再学习简单文件传输服务器和客户端代码是如何编写的
3.最后进行代码改造
① 服务器端代码:server.c 实现返回文本文件总字数的功能
- 除了基础的socket()、bind()、listen()、accept()、send()、recv()、close()函数以外,还要导入文件传输、缓冲区的思想。
/*服务器端需要用到的各个函数的头文件*/
#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
#include <unistd.h> //close
#define SERVER_PORT 165223 //学号
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
#define BEGIN 1;
/*声明并初始化一个服务器端的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);
}
/*定义客户端的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;
}
- 具体操作为:客户端首先输入文件名,并判断该文件是否存在。若不存在,则返回错误提示;存在,则发送至服务器。服务器接收到数据后,创建一个同样名称的文件。
- 在此实现的是接受从客户端传来的文件,并返回计算的文本文件总字数,间接实现
wc -w
命令的功能。
/*接收从客户端发来的文本文件,建立缓冲区*/
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
/*recv函数接收数据到缓冲区buffer中*/
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
", file_name);
/*打开文件只写模式,准备写入*/
FILE *fp = fopen(file_name, "w");
if(NULL == fp)
{
printf("File: %s Can Not Open To Write
", 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(fwrite(buffer, sizeof(char), length, fp) < length)
{
printf("File: %s Write Failed
", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}
/*否则就接收成功,关闭文件*/
printf("Receive File: %s From Client Successful!
", file_name);
fclose(fp);
/*在服务器端显示屏上打印统计的文本文件总字数*/
int words=0;
char s[100];
FILE *fp2;
if((fp2=fopen(file_name,"r"))==NULL)
{
printf("ERROR!
");
exit(0);
}
while(fscanf(fp2,"%s",s)!=EOF)
words++;
fclose(fp2);
printf("%d words.
",words);
char sendbuf[50];
sprintf(sendbuf,"%d",words);
send(new_server_socket_fd,sendbuf,50,0);
close(new_server_socket_fd); //先close用于传输数据(文件)的socket
close(server_socket_fd); //再close用于连接(客户端与服务器)的socket
② 客户端代码:client.c 实现向服务器传送文件的功能
-
客户端的任务是与服务器连接,实现向服务器传送文件,接收服务器返回的文本文件总字数并打印出来
-
首先声明一个head.h头文件,包含之后要用到的wcfunc函数:head.h
-
其次开始编写主函数包含的基础的socket()、bind()(非必需)、connet()、close()函数
/*客户端需要用到的各个函数的头文件*/
#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
#include <arpa/inet.h>
#include <unistd.h>
#include "head.h"
#define SERVER_PORT 165223 //学号
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
#define BEGIN 1;
/*声明并初始化一个客户端的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);
int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
/*创建用于连接服务器端的socket,若成功,返回socket描述符*/
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;
/*用inet_pton将点分十进制的地址转换为长整型的地址*/
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缓冲区的建立)
/*输入文件名 并放到缓冲区buffer中等待发送*/
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
printf("Please Input File Name On Client: ");
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
", 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);
}
}
printf("Send File: %s Successful!
", file_name); //成功发送文件
printf("The File has %d words.
",wcfunc(file_name));
fclose(fp);
close(client_socket_fd);
- 最后调用wcfunc函数计算文本文件的总字数
int wcfunc(char *file_name)
{
int t;
int w = 0;
int state = 0;
FILE *in;
if((in = fopen(file_name,"r"))==NULL)
{
printf("wc %s:no this file or dir
",file_name);
return 0;
}
while((t=fgetc(in))!=EOF)
{
if(t=='
'||t==' '||t=='
') {
state = 0;
continue;
} else {
if(state == 0) {
state = 1;
w++;
}
continue;
}
}
return w;
}
任务二:使用多线程实现wc服务器
(一)实验要求
0.使用多线程实现wc服务器并使用同步互斥机制保证计数正确
1.对比单线程版本的性能,并分析原因
2.上方提交代码,下方提交测试
(二)实验步骤
- 1.先用man命令了解一下线程的创建
- 2.了解到在多线程编译的过程中需要加上 -lpthread 才能成功编译运行。原因是:pthread库不是Linux系统默认的库,连接时需要使用静态库libpthread.a,所以在线程函数在编译时,需要使用“-lpthread”链接库函数。
-
3.了解同步互斥机制,参考【Linux多线程】同步与互斥的区别
- 互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
- 同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
- 同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。互斥是一种特殊的同步。
-
4.编写用于多线程的服务器端代码、客户端代码
-
5.分析代码,编译运行,测试结果
-
6.总结对比单线程版本的性能,并分析原因
- 与单线程版相比较:单线程程序只有一个线程,代码顺序执行,容易出现代码阻塞;而多线程程序有多个线程,线程间独立运行,能有效地避免代码阻塞,并且提高程序的运行性能。因此还是多线程的性能好。
(三)实验结果
- 用socket编程实现多线程
- 用wc命令检查
(四)代码分析
(1)实验代码
- 完整代码:码云代码 实验三 T2
(2)代码分析
① 服务器端代码:server.c
- 基础部分与任务一中服务器端代码基本一致,需要添加的是多线程部分的代码
//增加多线程相关的头文件
#include <pthread.h>
//主函数代码
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
return 2;
}
int listen_sock = startup(argv[1], atoi(argv[2]));
//printf("sock:%d
", listen_sock);
//需要让子进程的子进程去提供服务
//父进程继续监听
char buf[1024];
while(1)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int newsock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(newsock < 0)
{
perror("accept");
continue;
}
//用inet_ntoa和ntohs将网络中的数据转换为主机用户可以看懂的数据
printf("get a new client %s:%d
", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
//创建一个新的线程去服务
//主线程只负责监听工作
pthread_t tid;
//pthread_create(&tid, NULL, handle, (void*)newsock);
pthread_detach(tid);
}
close(listen_sock);
return 0;
}
- 这样就成功创建了线程啦,能够实现一服务器、多客户端的多线程模式。
② 客户端代码:client.c
二、实验总结
(一)遇到的问题
(1)gcc编译后警告:warning: implicit declaration of function ‘xxx’ [-Wimplicit-function-declaration]
- 在代码编译的过程中常常遇到诸如下图的错误提示
- 查询原因后得知:是相关的头文件没有声明这个函数,在相关头文件中声明即可
(2)gcc编译后警告:在'xxx'函数中,对'pthread_create'未定义的引用
- 在多线程服务器的编译过程中出现如下错误提示
- 查询原因是:多线程代码需要使用使用静态库libpthread.a,因此在编译时要加上 -lpthread 来连接库函数
(二)分析与总结
- 本次实验重点在于socket编程的学习与掌握。恰好刘念老师的网络安全编程课程也需要掌握简单socket网络编程,因此在基础部分的代码编写上没有出什么差错。需要注意的就是将windows下的C语言转换到Linux下C语言实现。
- 其次是在简单基础上加入文本文件传输和用 wc 计算文本文件总字数的实现,在参考了部分资料后也顺利理解了代码并组合成功。
- 再者就是进阶部分,多线程服务器代码的编写。先是了解了什么是多线程,再了解了同步与互斥机制,最终实现了一服务器、多客户端的多线程同步实现的可能。
- 本来还有个任务三是在实验箱上实现多线程服务器的代码,但老师最后取消了这一项,就只做了前两项任务。
- 参考了许多网络上的资料以及课本第十二章《并发编程》的内容,获益匪浅。