基于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服务器相关推荐

  1. 基于Socket的游戏服务器通信框架的设计与实现

    博客地址:blog.liujunliang.com.cn 开发工具:VS2017.Unity2017 本文介绍使用Socket/TCP来开发客户端与服务器端通信框架 博主使用过PhotonServer ...

  2. 搭建基于springboot的FTP服务器

    引言 最近有一个在集成系统上提供1G以上文件下载的功能,还要提供文件的展示功能和删除的操作,因为常规的文件流速度慢并且容易断掉因此我们采用FTP的方式,系统架构如下图所示,这里我们采用的ftp框架是a ...

  3. 基于CentOS的FTP服务器搭建

    目录 一,简介 1,Centos中的FTP 2,vsftpd 二,vsftpd的安装 1,安装vsftpd服务 2,开启服务 3,端口 4,关闭防火墙 三,vsftp匿名模式搭建 1,修改配置文件 2 ...

  4. c#基于socket的UDP服务器和客户端实例

    基于Udp协议是无连接模式通讯,占用资源少,响应速度快,延时低.至于可靠性,可通过应用层的控制来满足.(不可靠连接) 使用Udp协议通讯需要具备以下几个条件: (1).建立一个套接字(Socket) ...

  5. 使用python基于socket的tcp服务器聊天室

    # coding=utf-8 import socket,threading,time '''代码说明:1.创建一个字典用于接受客户端的用户名和信息2.创建一个类对象client用于编写客户端套接字对 ...

  6. c语言转换字符编码为zhs16gbk,GitHub - veis-lzf/freecplus: freecplus开源框架,包含了数据库操作、socket、ftp服务器等。...

    一.freecplus框架介绍 freecplus框架是UNIX平台下C/C++程序开发的业务层基础框架,由C语言技术网组织开发.维护.其目的是为C/C++程序员供免费的.开源的程序库.freecpl ...

  7. 【python练习】基于socket的FTP程序 v1.1.0(支持多用户)

    增加功能 1.在FTP(1.0.0)的基础上,支持了多并发的功能 2.允许配置最大并发数,比如允许只有10个并发用户 程序功能: 本程序模拟实现了一个FTP程序: 1.程序分为客户端和服务端 2.用户 ...

  8. Java连接FTP服务器并且实现对其文件的上传和下载

    概述 FTP是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为"文传协议".FTP作为网络共享文件的传输协议,在网络应用软件中具有广泛的应用.F ...

  9. 篇2:基于windows10专业版搭建ftp服务器

    特别说明:此是基于Windows10专业版的ftp服务器的搭建, 家庭版大体一致,但是略有不同,此处不在赘述!!! >>> 步骤一 执行以下操作,并确定: >>> ...

最新文章

  1. 上海技术英雄会续:几个典型问题的看法
  2. python检测网格
  3. 高德地图API 简单使用
  4. 论文,质量管理+进度管理(主质量)
  5. 做python项目需要知道什么_一文带你了解python是什么?能做什么?为什么要学?(文末附学习资源)...
  6. 转:自定义谷歌地图配色方案
  7. 【POI2011】LIZ-Lollipop 【构造】
  8. 以python程序调用的系统_python 系统调用的实例详解
  9. 无法处理文件 MainForm.resx,因为它位于 Internet 或受限区域中,或者文件上具有 Web 标记。要想处理这些文件,请删除 Web 标记...
  10. LC-871 最小加油次数
  11. 从0开始,使用豆瓣数据集做一个基于FM和逻辑回归的电影推荐系统
  12. 测试计划和测试方案区别
  13. VBA WORD 光标处理
  14. 【HDFS】HDFS文件块大小(重点)
  15. ie浏览器中图片被拉长
  16. //菱形,内藏十字架
  17. FANUC机器人动作指令的定位类型FINE和CNT详解
  18. video控制条在部分浏览器禁止显示“下载”-解决方法
  19. centos镜像(阿里云centos镜像)
  20. 网页中怎么插入flash的代码

热门文章

  1. Python源码剖析2-字符串对象PyStringObject
  2. 05-什么是作用域链
  3. RSA2048签名和加密+OAEP填充方式(前端)
  4. iis6导出Excel报错检索 COM 类工厂中 CLSID 为 {00024500-0000-0000-C000-000000000046} 的组件时失败,8000401a错误解决办法
  5. 在nuc972上实现I2C接口数字电位器isl95311的驱动
  6. Java项目:SSM员工考勤管理系统
  7. [转载]面向 Java 开发人员的 db4o 指南: 超越简单对象
  8. OAuth2的理解与客户端开发
  9. 远程桌面鼠标键盘映射问题
  10. 用 Python 做数学建模