【2018-2019-1】20165223 实验三 实时系统


实验成员: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)实验代码

(2)代码分析

1.首先了解Linux下是如何进行socket编程的

参考Linux的SOCKET编程详解

2.再学习简单文件传输服务器和客户端代码是如何编写的

参考Linux网络编程:socket文件传输范例

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)实验代码

(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 计算文本文件总字数的实现,在参考了部分资料后也顺利理解了代码并组合成功。
  • 再者就是进阶部分,多线程服务器代码的编写。先是了解了什么是多线程,再了解了同步与互斥机制,最终实现了一服务器、多客户端的多线程同步实现的可能。
  • 本来还有个任务三是在实验箱上实现多线程服务器的代码,但老师最后取消了这一项,就只做了前两项任务。
  • 参考了许多网络上的资料以及课本第十二章《并发编程》的内容,获益匪浅。

(三)参考资料

  1. linux socket TCP 服务器向客户端传文件
  2. Linux C TCPSocket 传输文件简单实例-多线程实现
  3. Linux多线程 同步与互斥机制
  4. gcc警告选项
  5. ubuntu C 语言 段错误 (核心已转储)
  6. C语言再学习 -- 段错误(核心已转储)
  7. Linux 线程Pthread
  8. Socket程序从Windows移植到Linux下的一些注意事项
  9. wc:统计一个文件里出现某个单词出现的次数

返回目录

原文地址:https://www.cnblogs.com/moddy13162201/p/9973551.html