基于socket实现FTP服务器
基于socket实现FTP服务器
前言
基于socket完成了一个FTP服务器,实现了其基本功能
环境
操作系统:Windows 10企业版 LTSC
开发语言:C++
开发工具:Visual Studio
功能列表
1.用户登录功能
2.显示服务器/本地目录的内容
3.更改服务器和本地的工作目录
4.文件上传、下载
5.关闭连接
使用说明
help:打印出支持的命令及其使用方法
pwd:服务器当前工作目录
cd:更改服务器工作目录
ls:列出服务器工作目录下的内容及其相关属性信息
put:将指定文件上传至服务器
get:从服务器上下载指定文件
!pwd:更改客户端当前工作目录
!cd:更改客户端当前工作目录 !ls:列出客户端工作目录下的内容及其相关属性信息
exit&quit:关闭与服务器的连接
连接服务器成功后会要求输入账户密码,直接回车即可登入匿名用户并进入命令界面
准备知识
最好先阅读
《windows网络编程(第2版)》
功能具体实现过程
socket的创建
按照参考书即可完成客户端与服务器socket的创建并连接
核心代码
cout << "Welcome to the FTP server made by Helix" << endl;
version = MAKEWORD(2, 2);
int error = WSAStartup(version, &wsadata);
if (error != 0)
{cout << "Socket load failed" << endl;return 0;
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2)
{cout << "Version error" << endl;WSACleanup();return 0;
}server_add.sin_family = AF_INET;
server_add.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_add.sin_port = htons(data_port);socket_server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //FTP使用TCP协议簇
if (bind(socket_server, (SOCKADDR*)&server_add, sizeof(SOCKADDR)) == SOCKET_ERROR)
{cout << "Bind failed" << endl;
}
if (listen(socket_server, 5) < 0)
{cout << "Listen failed" << endl;
}
length = sizeof(SOCKADDR);
cout << "Waiting to connect" << endl;socket_receive = accept(socket_server, (SOCKADDR*)&receive_add, &length);
if (socket_receive == SOCKET_ERROR)
{cout << "Connect failed" << endl;closesocket(socket_receive);closesocket(socket_server);WSACleanup();return 0;
}
cout << "Connect successfully!" << endl;
client
char send_message[100]; //两个缓冲区
char receive_message[100];
char* p = send_message;
int sendlen;
int receive_len;
int islogin = 0; //登录标志位
int data_port = 8000; //指定连接端口
cout << "Welcome to the FTP client made by Helix" << endl;
WSADATA mywsadata;
int error = WSAStartup(MAKEWORD(2, 2), &mywsadata);
if (error != 0)
{cout << "Socket load failed" << endl;return 0;
}
if (LOBYTE(mywsadata.wVersion) != 2 || HIBYTE(mywsadata.wVersion) != 2)
{cout << "Version error" << endl;WSACleanup();return 0;
}
SOCKADDR_IN server_data;
server_data.sin_family = AF_INET;
server_data.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_data.sin_port = htons(data_port);
SOCKET client_socket;
client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //FTP使用TCP协议簇
if (connect(client_socket, (SOCKADDR*)&server_data, sizeof(SOCKADDR)) == SOCKET_ERROR)
{cout << "Connection failed.The possible reason is that the server is not turned on" << endl;closesocket(client_socket);WSACleanup();return 0;}
cout << "Connect successful" << endl;
client和server连接成功
用户登录功能
只是做了个流程,并没有去严格核实用户身份,回车即可使用匿名用户登入server
server
while (!islogin) {strcpy_s(sendbuf, "username:");sendlen = send(socket_receive, sendbuf, 100, 0);recv(socket_receive, receivebuf, 100, 0);if (!receivebuf) {continue;}strcpy_s(sendbuf, "password:");sendlen = send(socket_receive, sendbuf, 100, 0);recv(socket_receive, receivebuf, 100, 0);if (receivebuf) {islogin = 1;}else {continue;}strcpy_s(sendbuf, "Login successful!");sendlen = send(socket_receive, sendbuf, 100, 0);
}
client
while (!islogin) {receive_len = recv(client_socket, receive_message, 100, 0);if (receive_len < 0){break;}else if (strcmp(receive_message, "Login successful!") == 0){islogin = 1;break;}cout << receive_message << endl;cin.getline(send_message, 100);sendlen = send(client_socket, send_message, 100, 0);}
登录成功
命令输入
client和server都将用户输入/收到的命令进行分割,然后进行分析
关键代码:
char seps[] = " ";
char* token = NULL;
char* ptr = NULL;
token = strtok_s(receivebuf, seps, &ptr);
此时token为命令的首部
使用
token = strtok_s(NULL, seps, &ptr);
即可获得后面的参数部分
显示服务器/本地目录的内容
windows自带的dir命令以及c++自带的shell即可完成
服务器接到ls命令后发送check标志位以及系统返回信息
客户端接受到服务端信息后对check进行判断,check为0表示信息传输完毕
server
if ((strcmp("ls", token) == 0) || (strcmp("dir", receivebuf) == 0)){ FILE* in;char temp[100];if (!(in = _popen("dir", "r"))) {cout << "error" << endl;};if (in){while (fgets(temp, sizeof(temp), in) != NULL) {sendlen = send(socket_receive, "1", 2, 0);sendlen = send(socket_receive, temp, 100, 0);}sendlen = send(socket_receive, "0", 2, 0);memset(sendbuf, 0, 100);_pclose(in);};continue;}
client
if ((strcmp("!ls", token) == 0) && (strcmp("!dir", token) == 0)){FILE* in;char temp[100];// 直接丢进内置的os,然后输出if (!(in = _popen("dir", "r"))) {cout << "error" << endl;};if (in){while (fgets(temp, sizeof(temp), in) != NULL) {cout << temp;}_pclose(in);};continue;}
ls
!ls
更改服务器和本地的工作目录
使用getcwd()函数即可实现
server
if (strcmp("pwd", receivebuf) == 0){char* path;path = _getcwd(NULL, 0);if (path != 0){strcpy_s(sendbuf, path);sendlen = send(socket_receive, sendbuf, 100, 0);};continue;}
client
if (strcmp("!pwd", token) == 0){char* path;path = _getcwd(NULL, 0);cout << path << endl;}
pwd
文件上传、下载
使用put,get命令来实现
下载:服务器接到客户端的下载命令后,将全局设置中的端口号+1后创建新的socket
客户端同理,在新端口成功建立连接后,服务器先发送1表示文件流开始传输。
客户端接到1后打开文件,按行接收文件流并写入文件。
文件发送完毕后服务器发送0表示发送完毕,客户端收到0后停止写入并关闭文件。
上传的实现与下载类似。
server
if (strcmp("get", token) == 0) {char port[MAXLINE], buffer[MAXLINE], char_num_blks[MAXLINE], char_num_last_blk[MAXLINE];int datasock, lSize, num_blks, num_last_blk, i;FILE* fp;token = strtok_s(NULL, seps, &ptr);cout << "Filename given is: " << token << endl;data_port = data_port + 1;sprintf_s(port, "%d", data_port);sendlen = send(socket_receive, port, MAXLINE, 0); // 接收到get命令后在port+1创建一个socket用于文件传输datasock = create_socket(data_port); datasock = accept_conn(datasock); errno_t err = fopen_s(&fp, token, "r");if (fp != NULL){sendlen = send(socket_receive, "1", 2, 0);// 文件发送之前先发个1给客户端,让客户端做好准备fseek(fp, 0, SEEK_END);lSize = ftell(fp);rewind(fp);num_blks = lSize / MAXLINE;num_last_blk = lSize % MAXLINE;sprintf_s(char_num_blks, "%d", num_blks);sendlen = send(socket_receive, char_num_blks, MAXLINE, 0);for (i = 0; i < num_blks; i++) {fread(buffer, sizeof(char), MAXLINE, fp);sendlen = send(datasock, buffer, MAXLINE, 0);}sprintf_s(char_num_last_blk, "%d", num_last_blk);sendlen = send(socket_receive, char_num_last_blk, MAXLINE, 0);if (num_last_blk > 0) {fread(buffer, sizeof(char), num_last_blk, fp);sendlen = send(datasock, buffer, MAXLINE, 0);}fclose(fp);cout << "File upload done.\n";}else {sendlen = send(socket_receive, "0", 2, 0);// 文件发送完毕后给客户端发个0告诉客户端文件发送完成}
}
else if (strcmp("put", token) == 0) {// 实现原理和get类似,只不过是客户端发送文件流char port[MAXLINE], buffer[MAXLINE], char_num_blks[MAXLINE], char_num_last_blk[MAXLINE], check[MAXLINE];int datasock, num_blks, num_last_blk, i;FILE* fp;token = strtok_s(NULL, seps, &ptr);cout << "Filename given is: " << token << endl;data_port = data_port + 1;sprintf_s(port, "%d", data_port);datasock = create_socket(data_port); send(socket_receive, port, MAXLINE, 0); datasock = accept_conn(datasock); recv(socket_receive, check, MAXLINE, 0);if (strcmp("1", check) == 0) {errno_t err = fopen_s(&fp, token, "w");if (fp == NULL) {cout << "Error in creating file\n";}else{recv(socket_receive, char_num_blks, MAXLINE, 0);num_blks = atoi(char_num_blks);for (i = 0; i < num_blks; i++) {recv(datasock, buffer, MAXLINE, 0);fwrite(buffer, sizeof(char), MAXLINE, fp);}recv(socket_receive, char_num_last_blk, MAXLINE, 0);num_last_blk = atoi(char_num_last_blk);if (num_last_blk > 0) {recv(datasock, buffer, MAXLINE, 0);fwrite(buffer, sizeof(char), num_last_blk, fp);}fclose(fp);cout << "File download done." << endl;}}
}
client
if (strcmp("get", token) == 0) {char port[MAXLINE], buffer[MAXLINE], char_num_blks[MAXLINE], char_num_last_blk[MAXLINE], message[MAXLINE];int data_port, num_blks, num_last_blk, i;SOCKET datasock;// 新创建一个socket用来接收文件FILE* fp;recv(client_socket, port, MAXLINE, 0);data_port = atoi(port);datasock = create_socket(data_port);token = strtok_s(NULL, seps, &ptr);recv(client_socket, message, MAXLINE, 0);//开始发送文件之前服务器会发个1过来if (strcmp("1", message) == 0) {errno_t err = fopen_s(&fp, token, "w");if (fp == NULL)cout << "Error in creating file" << endl;else{recv(client_socket, char_num_blks, MAXLINE, 0);num_blks = atoi(char_num_blks);for (i = 0; i < num_blks; i++) {recv(datasock, buffer, MAXLINE, 0);fwrite(buffer, sizeof(char), MAXLINE, fp);//写入文件}recv(client_socket, char_num_last_blk, MAXLINE, 0);num_last_blk = atoi(char_num_last_blk);if (num_last_blk > 0) {recv(datasock, buffer, MAXLINE, 0);fwrite(buffer, sizeof(char), num_last_blk, fp);}//文件发送完毕后服务器会发个0过来fclose(fp);cout << "File download done." << endl;}}else {cout << "Error in opening file. Check filename\nUsage: put filename" << endl;}
}
else if (strcmp("put", token) == 0) {// 实现原理和get差不多,客户端发送文件流给serverchar port[MAXLINE], buffer[MAXLINE], char_num_blks[MAXLINE], char_num_last_blk[MAXLINE];int data_port, datasock, lSize, num_blks, num_last_blk, i;FILE* fp;recv(client_socket, port, MAXLINE, 0); data_port = atoi(port);datasock = create_socket(data_port);token = strtok_s(NULL, seps, &ptr);errno_t err = fopen_s(&fp, token, "r");if (fp != NULL){send(client_socket, "1", 2, 0);fseek(fp, 0, SEEK_END);lSize = ftell(fp);rewind(fp);num_blks = lSize / MAXLINE;num_last_blk = lSize % MAXLINE;sprintf_s(char_num_blks, "%d", num_blks);send(client_socket, char_num_blks, MAXLINE, 0);for (i = 0; i < num_blks; i++) {fread(buffer, sizeof(char), MAXLINE, fp);send(datasock, buffer, MAXLINE, 0);}sprintf_s(char_num_last_blk, "%d", num_last_blk);send(client_socket, char_num_last_blk, MAXLINE, 0);if (num_last_blk > 0) {fread(buffer, sizeof(char), num_last_blk, fp);send(datasock, buffer, MAXLINE, 0);}fclose(fp);cout << "File upload done.\n";}else {send(client_socket, "0", 2, 0);cerr << "Error in opening file. Check filename\nUsage: put filename" << endl;}
}
下载
上传
关闭连接
客户端break循环,并关闭socket即可
if (strcmp("quit", token) == 0 || strcmp("exit", token) == 0) {cout << "Bye";break;}closesocket(socket_receive);closesocket(socket_server);
WSACleanup();
return 0;
多用户访问
Linux下可以使用fork()函数
windows下可以使用线程包thread,但可能因为封装函数太大导致出现
搞不定这个问题就放弃多用户访问了
源码
需要配合winsock等依赖,建议使用Visual Studio
可执行文件和工程文件传
gitee传送门
后记
FTP还是基于TCP协议簇的,FTP server的实现并不是很难
基于socket实现FTP服务器相关推荐
- 基于Socket的游戏服务器通信框架的设计与实现
博客地址:blog.liujunliang.com.cn 开发工具:VS2017.Unity2017 本文介绍使用Socket/TCP来开发客户端与服务器端通信框架 博主使用过PhotonServer ...
- 搭建基于springboot的FTP服务器
引言 最近有一个在集成系统上提供1G以上文件下载的功能,还要提供文件的展示功能和删除的操作,因为常规的文件流速度慢并且容易断掉因此我们采用FTP的方式,系统架构如下图所示,这里我们采用的ftp框架是a ...
- 基于CentOS的FTP服务器搭建
目录 一,简介 1,Centos中的FTP 2,vsftpd 二,vsftpd的安装 1,安装vsftpd服务 2,开启服务 3,端口 4,关闭防火墙 三,vsftp匿名模式搭建 1,修改配置文件 2 ...
- c#基于socket的UDP服务器和客户端实例
基于Udp协议是无连接模式通讯,占用资源少,响应速度快,延时低.至于可靠性,可通过应用层的控制来满足.(不可靠连接) 使用Udp协议通讯需要具备以下几个条件: (1).建立一个套接字(Socket) ...
- 使用python基于socket的tcp服务器聊天室
# coding=utf-8 import socket,threading,time '''代码说明:1.创建一个字典用于接受客户端的用户名和信息2.创建一个类对象client用于编写客户端套接字对 ...
- c语言转换字符编码为zhs16gbk,GitHub - veis-lzf/freecplus: freecplus开源框架,包含了数据库操作、socket、ftp服务器等。...
一.freecplus框架介绍 freecplus框架是UNIX平台下C/C++程序开发的业务层基础框架,由C语言技术网组织开发.维护.其目的是为C/C++程序员供免费的.开源的程序库.freecpl ...
- 【python练习】基于socket的FTP程序 v1.1.0(支持多用户)
增加功能 1.在FTP(1.0.0)的基础上,支持了多并发的功能 2.允许配置最大并发数,比如允许只有10个并发用户 程序功能: 本程序模拟实现了一个FTP程序: 1.程序分为客户端和服务端 2.用户 ...
- Java连接FTP服务器并且实现对其文件的上传和下载
概述 FTP是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为"文传协议".FTP作为网络共享文件的传输协议,在网络应用软件中具有广泛的应用.F ...
- 篇2:基于windows10专业版搭建ftp服务器
特别说明:此是基于Windows10专业版的ftp服务器的搭建, 家庭版大体一致,但是略有不同,此处不在赘述!!! >>> 步骤一 执行以下操作,并确定: >>> ...
最新文章
- 上海技术英雄会续:几个典型问题的看法
- python检测网格
- 高德地图API 简单使用
- 论文,质量管理+进度管理(主质量)
- 做python项目需要知道什么_一文带你了解python是什么?能做什么?为什么要学?(文末附学习资源)...
- 转:自定义谷歌地图配色方案
- 【POI2011】LIZ-Lollipop 【构造】
- 以python程序调用的系统_python 系统调用的实例详解
- 无法处理文件 MainForm.resx,因为它位于 Internet 或受限区域中,或者文件上具有 Web 标记。要想处理这些文件,请删除 Web 标记...
- LC-871 最小加油次数
- 从0开始,使用豆瓣数据集做一个基于FM和逻辑回归的电影推荐系统
- 测试计划和测试方案区别
- VBA WORD 光标处理
- 【HDFS】HDFS文件块大小(重点)
- ie浏览器中图片被拉长
- //菱形,内藏十字架
- FANUC机器人动作指令的定位类型FINE和CNT详解
- video控制条在部分浏览器禁止显示“下载”-解决方法
- centos镜像(阿里云centos镜像)
- 网页中怎么插入flash的代码
热门文章
- Python源码剖析2-字符串对象PyStringObject
- 05-什么是作用域链
- RSA2048签名和加密+OAEP填充方式(前端)
- iis6导出Excel报错检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件时失败,8000401a错误解决办法
- 在nuc972上实现I2C接口数字电位器isl95311的驱动
- Java项目:SSM员工考勤管理系统
- [转载]面向 Java 开发人员的 db4o 指南: 超越简单对象
- OAuth2的理解与客户端开发
- 远程桌面鼠标键盘映射问题
- 用 Python 做数学建模