C++实现FTP流服务器文件的上传和下载

 此报告是作者自学总结的,有错误还请提出

一,代码实现功能简述

此代码实现了简单的文件上传和下载,现仅支持txt文档,后续会不断优化,选择的是tcp 而不是udp,因为需要可靠传输。

二,实现用到的技术

1,c++库中的socket技术、thread线程库

2,WS2_32动态链接库

三,实现过程

1,先创建套接字进行客户端与服务器的基础连接通信

客户端如下:

WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa)!=0) {
	cout << "wsastartup faild:" << WSAGetLastError() << endl;
	return false;
}
//1,创建通信套接字
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd==INVALID_SOCKET) {
	perror("socket");
	return -1;
}
//2,连接服务器ip和port
struct sockaddr_in saddr;//定义套接字地址结构
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET, "192.168.101.35", &saddr.sin_addr.S_un.S_addr);//转换为网络大端,存放进s_addr  ip
int ret = connect(fd, (sockaddr*)&saddr, sizeof(saddr));
if (ret == -1) {
	perror("connect");
	return -1;
}

服务器如下 :

WSAData wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
	cout << "wsastartup faild:" << WSAGetLastError() << endl;
	return false;
}
//1,创建监听的套接字
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);//ipv4,流式协议,tcp
if (fd == INVALID_SOCKET) {
	perror("socket");//打印错误信息
	return -1;
}
//2,套接字与本地ip和端口绑定
//端口与ip为结构体
struct sockaddr_in saddr;//库里的结构体,定义结构体变量
saddr.sin_family = AF_INET;//指定地址家族为tcp/ip
saddr.sin_port = htons(9999);//端口转化为大端(低字节存储高位)端口5000以上,本机未使用的
saddr.sin_addr.S_un.S_addr = INADDR_ANY;//自动读取本地ip,服务器能接受任何计算机发来的请求(定义IP地址)
int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));//sizeof()计算占用内存大小
if (ret == -1) {
	perror("bind");
	return -1;
}
//3,设置监听
ret = listen(fd, 128);
if (ret == -1) {
	perror("listen");
	return -1;
}

//4,阻塞并等待客户端连接
sockaddr_in caddr;
int addrlen = sizeof(struct sockaddr_in);
while (1) {//初始化
	SOCKET cfd = accept(fd, (struct sockaddr*)&caddr , &addrlen);//cfd用于通信的套接字
	if (cfd == INVALID_SOCKET) {
		continue;
	}
文件的上传功能

客户端选择文件,发送文件名和文件大小给服务器,服务器会在自己的目录下生成一个txt文件来存储信息。

服务器:

ofstream file("文本.txt", ios::binary | ios::out);//二进制方式打开
if (!file.is_open()) {
				perror("Error opening file for writing");
				break;
}
// Receive file content from client and write to file
filesize = recv(cfd, rbuf, sizeof(rbuf), 0);//上传接受文件字节大小
if (filesize <= 0) break;
cout << "正在接受文件:" << rbuf <<"文件长度为:" << filesize << endl;
for (int i = 0; i < strlen(rbuf); i++) {
				file << rbuf[i];
}
cout << "上传成功" << endl;
file.close();

必须以二进制来读取文件,否则文件内会出现中文乱码

文件的下载功能

用户需发给服务器要下载的文件名,服务器根据文件名找到该文件,先发送文件的大小,让客户端做好存储文件空间的准备,当客户端准备好后,服务器发送文件给客户端。

服务器:

cout << "下载文件名:"<<filename << endl;
ifstream ifs(filename,ios::binary| ios::in);
if (!ifs.is_open()) {
				cout << "文件打不开" << endl;
				continue;
}char c; int i = 0;
while ((c=ifs.get())!=EOF) {
				sbuf[i++] = c;
}
cout <<"文件内容:" << sbuf <<"长度:" << endl;

				// 开始发送文件
					send(cfd, sbuf, strlen(sbuf), 0);
				cout << "发送成功" << endl;
				ifs.close();

由此实现下载功能

问题1:多个客户端请求时会出现“停滞”问题

原因:服务器只能线性处理客户端请求,多个客户端需排队等待

解决方法:在服务器端创建线程来解决等待问题,

while (1) {//初始化
	SOCKET cfd = accept(fd, (struct sockaddr*)&caddr , &addrlen);//cfd用于通信的套接字
	if (cfd == INVALID_SOCKET) {
		continue;
	}
	//创建子线程
	thread work(working,cfd,caddr);
	work.detach();
}

每一个线程对应一个客户端的连接,把子线程与主线程分离,防止线程资源被主线程收回或杀死

客户端完整代码:

#include<WS2tcpip.h>
#include<WinSock2.h>
#include<iostream>
#include<thread>
#include<chrono>
#include<fstream>
#include<string>
#define FILE "D:\\2.PC端\\文本.txt"
#pragma comment(lib,"ws2_32.lib")
using namespace std;
char rbuf[1024];//接受文件
char buff[1024];//对话
char sbuf[1024];//上传文件
char filename[1024];//文件名
int filesize = 0;//文件字节大小
int main() {
	
	//服务器有监听和通信两个套接字句柄,客户端只有通信套接字句柄
	WSADATA wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa)!=0) {
		cout << "wsastartup faild:" << WSAGetLastError() << endl;
		return false;
	}
	//1,创建通信套接字
	SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd==INVALID_SOCKET) {
		perror("socket");
		return -1;
	}
	//2,连接服务器ip和port
	struct sockaddr_in saddr;//定义套接字地址结构
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(9999);
	inet_pton(AF_INET, "192.168.101.35", &saddr.sin_addr.S_un.S_addr);//转换为网络大端,存放进s_addr  ip
	int ret = connect(fd, (sockaddr*)&saddr, sizeof(saddr));
	if (ret == -1) {
		perror("connect");
		return -1;
	}
	//3,通信
	while (1) {
		memset(filename, 0, sizeof(filename));
		cout << "1,上传文件,2,下载文件" << endl;
		//长传大文件要依据长度分块
		int select;
		cin >> select;
		if (select == 1) {
			strcpy_s(buff, "1");
			send(fd, buff, strlen(buff), 0);
			cout << "输入文件名:" << endl;
			cin >> filename;
			//strcpy_s(filename, FILE);

			send(fd, filename, strlen(filename), 0);
			ifstream file(filename,ios::binary|ios::in);//二进制方式打开
			if (!file.is_open()) {
				perror("Error opening file for reading");
				break;
			}
			char c; int i = 0;
			while ((c=file.get())!=EOF) {
				sbuf[i++] = c;
			}
			cout << sbuf << endl;
			send(fd, sbuf, strlen(sbuf), 0);
			file.close();
			cout << "已发送,长度为: "<<strlen(sbuf) << endl;
		}
		else {
			strcpy_s(buff, "2");
			send(fd, buff, strlen(buff), 0);
			cout << "输入下载文件名" << endl;
			cin >> filename;
			send(fd, filename, strlen(filename), 0);
			string s;
			cout << "1,准备好了,0,未准备好接收" << endl;
			cin >> s;
			if (stoi(s) == 0) {
				cout << "退出" << endl;
				break;
			}
			else {
				//接受文件
				recv(fd, rbuf, sizeof(rbuf), 0);
				cout <<"文件内容:"<< rbuf <<"文件长度:"<<strlen(rbuf) << endl;
				ofstream ofs("五五.txt",ios::binary|ios::out);
				for (int i = 0; i < strlen(rbuf); i++) {
					ofs << rbuf[i];
				}
				cout << "接收成功" << endl;
				ofs.close();
			}

		}
	}
	//4,关闭文件描述符
	closesocket(fd);
	
	WSACleanup();
	return 0;
}

服务器完整代码:

#include<winsock2.h>
//#include<arpa/inet.h>就是上面的winsock2.h
#include<WS2tcpip.h>
#include <iostream>
#include<thread>
#include<string>
#include<fstream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
char rbuf[1024];//接受文件
char buff[1024] ;//对话
char sbuf[1024];//上传文件
char filename[1024];//文件名
int filesize = 0;//文件字节大小
void working(SOCKET cfd, sockaddr_in caddr);
int main()
{
	WSAData wsa;
	if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
		cout << "wsastartup faild:" << WSAGetLastError() << endl;
		return false;
	}
	//1,创建监听的套接字
	SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);//ipv4,流式协议,tcp
	if (fd == INVALID_SOCKET) {
		perror("socket");//打印错误信息
		return -1;
	}
	//2,套接字与本地ip和端口绑定
	//端口与ip为结构体
	struct sockaddr_in saddr;//库里的结构体,定义结构体变量
	saddr.sin_family = AF_INET;//指定地址家族为tcp/ip
	saddr.sin_port = htons(9999);//端口转化为大端(低字节存储高位)端口5000以上,本机未使用的
	saddr.sin_addr.S_un.S_addr = INADDR_ANY;//自动读取本地ip,服务器能接受任何计算机发来的请求(定义IP地址)
	int ret = bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));//sizeof()计算占用内存大小
	if (ret == -1) {
		perror("bind");
		return -1;
	}
	//3,设置监听
	ret = listen(fd, 128);
	if (ret == -1) {
		perror("listen");
		return -1;
	}
	
	//4,阻塞并等待客户端连接
	sockaddr_in caddr;
	int addrlen = sizeof(struct sockaddr_in);
	while (1) {//初始化
		SOCKET cfd = accept(fd, (struct sockaddr*)&caddr , &addrlen);//cfd用于通信的套接字
		if (cfd == INVALID_SOCKET) {
			continue;
		}
		//创建子线程
		thread work(working,cfd,caddr);
		//work.detach();
	}
	closesocket(fd);
	WSACleanup();
	return 0;
}
void working(SOCKET cfd,sockaddr_in caddr){
	//连接成功,打印客户端ip和端口信息
	//将客户端网络大端转化为主机小端(低字节存储低位)
	char ip[32];//存放ip
	cout << "客户端ip:" << inet_ntop(AF_INET,&caddr.sin_addr.S_un.S_addr,ip,sizeof(ip)) <<//tcp/ip协议,需要转换的大端IP地址,
		"\t端口:" << ntohs(caddr.sin_port) << endl;
	//5,通信
	while (1) {
		memset(filename, 0, 1024);
		recv(cfd, buff, sizeof(buff), 0);

		if (buff[0] == '1') {
			int len = recv(cfd, buff, sizeof(buff), 0);//用于通信的文件描述符,存储地址,空间,标志为0
			if (len > 0) {
				cout <<"上传文件名:" << buff << endl;
				strcpy_s(filename, buff);
				ofstream file("文本.txt", ios::binary | ios::out);//二进制方式打开
				if (!file.is_open()) {
					perror("Error opening file for writing");
					break;
				}
				// Receive file content from client and write to file
				filesize = recv(cfd, rbuf, sizeof(rbuf), 0);//上传接受文件字节大小
				if (filesize <= 0) break;
				cout << "正在接受文件:" << rbuf <<"文件长度为:" << filesize << endl;
				for (int i = 0; i < strlen(rbuf); i++) {
					file << rbuf[i];
				}
				cout << "上传成功" << endl;
				file.close();
			}
		}
		else {
			int len = recv(cfd, filename, sizeof(filename), 0);
			if (len > 0) {
				cout << "下载文件名:"<<filename << endl;
				ifstream ifs(filename,ios::binary| ios::in);
				if (!ifs.is_open()) {
					cout << "文件打不开" << endl;
					continue;
				}char c; int i = 0;
				while ((c=ifs.get())!=EOF) {
					sbuf[i++] = c;
				}
				cout <<"文件内容:" << sbuf <<"长度:" << endl;

					// 开始发送文件
						send(cfd, sbuf, strlen(sbuf), 0);
					cout << "发送成功" << endl;
					ifs.close();	
			}
		}
	}
	//关闭文件描述符
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值