0%

计算机网络实验五_文件传输实验

文件传输实验

实验目的

学习利用套接字传送文件。

实验环境

WSL,gcc

实验内容

写一个Ftp客户端程序,用FTP协议下载多个文件到指定目录

把上次的mytelnet客户端稍作修改,让接受消息的线程在收到227 Entering Passive Mode(103,26,79,35,219,79). 这个消息后自动计算端口号,并绑定一个套接字,启动一个新的线程来接受文件内容。

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include<netdb.h>
#include<iostream>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<bits/stdc++.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <error.h>
#include <pthread.h>
#define BUFLEN 20000 // 缓冲区大小
#define File_Size 2000000 // 缓冲区大小
using namespace std;
char host[50];
ofstream out;
void input(char* buf){
fgets(buf,BUFLEN,stdin);
int len=strlen(buf);
buf[len-1]='\r',buf[len]='\n',buf[++len]='\0';
}

void* file_recv(void* in){
int my_sock=(long)in;
char* buf=(char*)malloc((File_Size)*sizeof(char));
int c1;
while(c1=recv(my_sock,buf,File_Size,0)){
if(c1==-1){
return NULL;
}
cout<<c1<<endl;
out.write(buf,c1);
}
out.close();
return NULL;
}

void* client_recv(void* in){
int my_sock=(long)in;
char* buf=(char*)malloc((BUFLEN+1)*sizeof(char));
while(1){
int c1=recv(my_sock,buf,BUFSIZ,0);
if(c1==0){
return NULL;
}
buf[c1]='\0';
if(strncmp(buf,"227",3)==0){
char* tem=buf;
while(*tem!=')')tem++;
while(*tem!=',')tem--;
int port1=strtol(tem+1,NULL,10);
tem--;
while(*tem!=',')tem--;
int port2=strtol(tem+1,NULL,10);
port2=(port2<<8)+port1;
cout<<port2<<endl;
struct sockaddr_in sin; /* an Internet endpoint address */
char buf[BUFLEN+1]; /* buffer for one line of text */
int sock; /* socket descriptor */
int cc; /* recv character count */
pthread_t thread_handle2;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET
memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host);
// printf("%d\n",sin.sin_addr.s_addr); // 设置服务器IP地址(32位)
sin.sin_port = htons((unsigned short)port2); // 设置服务器端口号
int ret=connect(sock, (struct sockaddr *)&sin, sizeof(sin)); // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,
pthread_create(&thread_handle2,NULL,file_recv,(void*)sock);
}
printf("%s\n",buf);
if(strncmp(buf,"221",3)==0){
return NULL;
}
}
}


int main(int argc,char* argv[]){
if(argc!=4){
abort();
}
out.open(argv[3],ios::out);
strcpy(host,argv[1]);

printf("%s",host);
char *service = "21"; /* server port to connect */
struct sockaddr_in sin; /* an Internet endpoint address */
char buf[BUFLEN+1]; /* buffer for one line of text */
int sock; /* socket descriptor */
int cc; /* recv character count */
pthread_t thread_handle;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET
memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host);
// printf("%d\n",sin.sin_addr.s_addr); // 设置服务器IP地址(32位)
sin.sin_port = htons((unsigned short)atoi(service)); // 设置服务器端口号
int ret=connect(sock, (struct sockaddr *)&sin, sizeof(sin)); // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,
pthread_create(&thread_handle,NULL,client_recv,(void*)sock);
char tem[10][100]={"user net\r\n\0","pass 123456\r\n\0","pasv\r\n\0","","quit\r\n\0"};
sprintf(tem[3],"retr .%s\r\n\0",argv[2]);
for(int i=0;i<5;i++){
printf("%s\n",tem[i]);
int c1=send(sock,tem[i],strlen(tem[i]),0);
if(c1==0){
printf("Error !\n");
abort();
}
} // 关闭监听套接字
pthread_join(thread_handle,NULL);
printf("按回车键继续...");
getchar(); // 等待任意按键
}

运行截屏及说明

这个有点坑,一个是斜杠和反斜杠不能搞错,另一个是传大文件的时候要多recv几次,不然一次recv收不下来。
由于我是在wsl里完成的实验,因此命令行的最后一个参数用 /mnt/e 把文件放置在windows系统的e盘。
image.png
image.png

通过建立TCP连接实现一对一的聊天和传输文件功能

功能说明

在客户和服务器之间建立TCP连接之后,任何一方可以输入并执行命令:

rdir d:\test 设置接收文件的目录d:\test
chat hello 向对方发送聊天字符串“hello”或者 >hello (非命令即可)
send c:\temp\ftp.pdf 向对方发送文件ftp.pdf,接收方对重名文件加编号。
quit 退出程序

数据包设计(仅作参考)

一个包由三部分构成:结构1,结构2(多种类型),数据。其中:结构1和结构2分别包含数据类型和数据长度。
在接收结构1之后,通过数据类型确定结构2,不同数据类型的结构2可以不同,再通过结构2中的数据长度接收数据。
接收结构和数据均要根据该结构或数据的长度接收。 要累计已接收的字节数,直到全部接收完毕,再接收下一部分。

源代码

客户端:(p2pClient.cpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#include <netdb.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <error.h>
#include <pthread.h>
#include <semaphore.h>
#include <bits/stdc++.h>
#define BUFLEN 20000 // 缓冲区大小
#define FILE_SIZE 1001000
#define FILE_NAME_LEN 300
using namespace std;
map<string, int> mp;
char dest_folder[100]="./new";
ofstream out;
sem_t my_semaphore;

struct FileStruct
{
char fileName[300];
long long fileSize;
};

long long getFileSize(char *fileName)
{
FILE *fp = fopen(fileName, "r");
if(fp==NULL)return -1;
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fclose(fp);
return size;
}

char *getFileName(char *pathName)
{
char* tem=pathName;
while (*tem != '\0')
tem++;
while (*tem != '/' && tem!=pathName)
tem--;
return tem==pathName?tem:tem+1;
}

void input(char *buf)
{
fgets(buf, BUFLEN, stdin);
int len = strlen(buf);
buf[len - 1] = '\0';
}

void Unpack(struct FileStruct *fileStruct, char *file_content, char *folder_name)
{
char Full_name[FILE_NAME_LEN];
sprintf(Full_name, "%s/%s", folder_name, fileStruct->fileName);
// printf("%s\n",Full_name);
FILE *file = fopen(Full_name, "wb");
fwrite(file_content, sizeof(char), fileStruct->fileSize, file);
return;
}

void add_suffix(char *new_filename, int occur_time, char *old_filename)
{
char *tem = old_filename;
while (*tem != '\0')
tem++;
while (*tem != '.' && tem != old_filename)
tem--;
if (tem != old_filename)
{
*tem = '\0';
sprintf(new_filename, "%s(%d).%s", old_filename, occur_time, tem + 1);
*tem = '.';
return;
}
else
{
sprintf(new_filename, "%s(%d)", old_filename, occur_time);
return;
}
}

void *client_recv(void *in)
{
int my_sock = (long)in;
char *buf = (char *)malloc((BUFLEN + 1) * sizeof(char));
while (1)
{
int c1 = recv(my_sock, buf, BUFSIZ, 0);
if (c1 == -1 || c1 == 0)
{
printf("接收消息错误!\n");
return NULL;
}
if (c1 <= 300)
{
buf[c1] = '\0';
printf("收到消息:\n%s\n", buf);
}
else
{
char file_name[FILE_NAME_LEN];
char full_file_name[FILE_NAME_LEN];
memcpy(file_name, buf, FILE_NAME_LEN);
// printf("file name: %s",file_name);
sem_wait(&my_semaphore);
if (mp.count(string(file_name)))
{
char new_file_name[FILE_NAME_LEN];
add_suffix(new_file_name, mp[string(file_name)], file_name);
strcpy(file_name, new_file_name);
}
sprintf(full_file_name, "%s/%s", dest_folder, file_name);
mp[string(file_name)]++;
sem_post(&my_semaphore);
out.open(full_file_name, ios::out);

out.write(buf + sizeof(FileStruct), strlen(buf + sizeof(FileStruct)));
printf("文件已经被写入到 %s!\n", full_file_name);
out.close();
}
}
}

int main(int argc, char *argv[])
{
if (argc != 3)
{
abort();
}
const char *host = argv[1];
// printf("%s", host);
char *service = argv[2]; /* server port to connect */
struct sockaddr_in sin; /* an Internet endpoint address */
char buf[BUFLEN + 1]; /* buffer for one line of text */
int sock; /* socket descriptor */
int cc; /* recv character count */
pthread_t thread_handle;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET
memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host);
// printf("%d\n",sin.sin_addr.s_addr); // 设置服务器IP地址(32位)
sin.sin_port = htons((unsigned short)atoi(service)); // 设置服务器端口号
int ret = connect(sock, (struct sockaddr *)&sin, sizeof(sin)); // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,

sem_init(&my_semaphore, 0, 1);
pthread_create(&thread_handle, NULL, client_recv, (void *)sock);

while (1)
{
input(buf);

if (strcmp(buf, ">quit") == 0)
{
printf("退出成功!\n");
break;
}

else if (strncmp(">chat", buf, 5) == 0)
{
send(sock, buf + 6, strlen(buf + 6), 0);
printf("消息发送成功!\n");
}

else if (strncmp(">send", buf, 5) == 0)
{
struct FileStruct f;
f.fileSize = getFileSize(buf + 6);
if(f.fileSize==-1){
cout<<"Error file name!\n";
continue;
}
strcpy(f.fileName, getFileName(buf + 6));
char *File_pack = (char *)malloc(sizeof(char) * FILE_SIZE);
memcpy(File_pack, &f, sizeof(FileStruct));
FILE *tem_file = fopen(buf + 6, "r");
char *content = (char *)malloc(f.fileSize);
fread(content, f.fileSize, 1, tem_file);
fclose(tem_file);
memcpy(File_pack + sizeof(FileStruct), content, f.fileSize);
send(sock, File_pack, sizeof(FileStruct) + f.fileSize, 0);
printf("文件发送成功!\n");
}

else if (strncmp(">rdir", buf, 5) == 0)
{
sem_wait(&my_semaphore);
strcpy(dest_folder, buf + 6);
mp.clear();
sem_post(&my_semaphore);
printf("文件夹路径已被修改!\n");
}
}

pthread_join(thread_handle, NULL);
close(sock); // 关闭监听套接字
printf("按回车键继续...");
getchar(); // 等待任意按键
}

服务器程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#include <netdb.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <error.h>
#include <pthread.h>
#include <semaphore.h>
#include <bits/stdc++.h>
#define BUFLEN 20000 // 缓冲区大小
#define FILE_SIZE 1001000
#define FILE_NAME_LEN 300
using namespace std;
map<string, int> mp;
char dest_folder[100]="./new";
ofstream out;
sem_t my_semaphore;

struct FileStruct
{
char fileName[300];
long long fileSize;
};

long long getFileSize(char *fileName)
{
FILE *fp = fopen(fileName, "r");
if(fp==NULL)return -1;
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fclose(fp);
return size;
}

char *getFileName(char *pathName)
{
char* tem=pathName;
while (*tem != '\0')
tem++;
while (*tem != '/' && tem!=pathName)
tem--;
return tem==pathName?tem:tem+1;
}

void input(char *buf)
{
fgets(buf, BUFLEN, stdin);
int len = strlen(buf);
buf[len - 1] = '\0';
}

void Unpack(struct FileStruct *fileStruct, char *file_content, char *folder_name)
{
char Full_name[FILE_NAME_LEN];
sprintf(Full_name, "%s/%s", folder_name, fileStruct->fileName);
// printf("%s\n",Full_name);
FILE *file = fopen(Full_name, "wb");
fwrite(file_content, sizeof(char), fileStruct->fileSize, file);
return;
}

void add_suffix(char *new_filename, int occur_time, char *old_filename)
{
char *tem = old_filename;
while (*tem != '\0')
tem++;
while (*tem != '.' && tem != old_filename)
tem--;
if (tem != old_filename)
{
*tem = '\0';
sprintf(new_filename, "%s(%d).%s", old_filename, occur_time, tem + 1);
*tem = '.';
return;
}
else
{
sprintf(new_filename, "%s(%d)", old_filename, occur_time);
return;
}
}

void *client_recv(void *in)
{
int my_sock = (long)in;
char *buf = (char *)malloc((BUFLEN + 1) * sizeof(char));
while (1)
{
int c1 = recv(my_sock, buf, BUFSIZ, 0);
if (c1 == -1 || c1 == 0)
{
printf("接收消息错误!\n");
return NULL;
}
if (c1 <= 300)
{
buf[c1] = '\0';
printf("收到消息:\n%s\n", buf);
}
else
{
char file_name[FILE_NAME_LEN];
char full_file_name[FILE_NAME_LEN];
memcpy(file_name, buf, FILE_NAME_LEN);
// printf("file name: %s",file_name);
sem_wait(&my_semaphore);
if (mp.count(string(file_name)))
{
char new_file_name[FILE_NAME_LEN];
add_suffix(new_file_name, mp[string(file_name)], file_name);
strcpy(file_name, new_file_name);
}
sprintf(full_file_name, "%s/%s", dest_folder, file_name);
mp[string(file_name)]++;
sem_post(&my_semaphore);
out.open(full_file_name, ios::out);

out.write(buf + sizeof(FileStruct), strlen(buf + sizeof(FileStruct)));
printf("文件已经被写入到 %s!\n", full_file_name);
out.close();
}
}
}

int main(int argc, char *argv[])
{
const char *host = "127.0.0.1";
// printf("%s", host);
char *service = "50500"; /* server port to connect */
struct sockaddr_in sin, fsin; /* an Internet endpoint address */
char buf[BUFLEN + 1]; /* buffer for one line of text */
int sock, ssock, alen; /* socket descriptor */
int cc; /* recv character count */
pthread_t thread_handle;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET
memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host);
// printf("%d\n",sin.sin_addr.s_addr); // 设置服务器IP地址(32位)
sin.sin_port = htons((unsigned short)atoi(service)); // 设置服务器端口号

bind(sock, (struct sockaddr *)&sin, sizeof(sin)); // 绑定监听的IP地址和端口号

listen(sock, 5); // 建立长度为5的连接请求队列,并把到来的连接请求加入队列等待处理。

ssock = accept(sock, (struct sockaddr *)&fsin, (socklen_t *)&alen); // 如果在连接请求队列中有连接请求,则接受连接请求并建立连接,返回该连接的套接字,否则,本语句被阻塞直到队列非空。fsin包含客户端IP地址和端口号

sem_init(&my_semaphore, 0, 1);
pthread_create(&thread_handle, NULL, client_recv, (void *)ssock);

while (1)
{
input(buf);

if (strcmp(buf, ">quit") == 0)
{
printf("退出成功!\n");
break;
}

else if (strncmp(">chat", buf, 5) == 0)
{
send(ssock, buf + 6, strlen(buf + 6), 0);
printf("消息发送成功!\n");
}

else if (strncmp(">send", buf, 5) == 0)
{
struct FileStruct f;
f.fileSize = getFileSize(buf + 6);
if(f.fileSize==-1){
cout<<"文件名错误!\n";
continue;
}
strcpy(f.fileName, getFileName(buf + 6));
char *File_pack = (char *)malloc(sizeof(char) * FILE_SIZE);
memcpy(File_pack, &f, sizeof(FileStruct));
FILE *tem_file = fopen(buf + 6, "r");
char *content = (char *)malloc(f.fileSize);
fread(content, f.fileSize, 1, tem_file);
fclose(tem_file);
memcpy(File_pack + sizeof(FileStruct), content, f.fileSize);
send(ssock, File_pack, sizeof(FileStruct) + f.fileSize, 0);
printf("文件发送成功!\n");
}

else if (strncmp(">rdir", buf, 5) == 0)
{
sem_wait(&my_semaphore);
strcpy(dest_folder, buf + 6);
mp.clear();
sem_post(&my_semaphore);
printf("文件夹路径已被修改!\n");
}
}

pthread_join(thread_handle, NULL);
close(sock); // 关闭监听套接字
printf("按回车键继续...");
getchar(); // 等待任意按键
}

服务器运行截屏

image.png
可以看到文件确实被写入到e盘了。
image.png

客户端运行截屏

image.png

实验体会

遇到的最大问题可能还是对套接字程序设计不够熟悉,有些函数只能照着之前的模板来用,导致程序里的很多代码都是复制粘贴的。还有就是ftp协议的send和recv是有点坑,服务器发送的消息长度比较短,对于大的文件一次传不完。
总的来说这次实验不是很难,掌握了原理之后完成起来比较简单。