基于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差不多,客户端发送文件流给server
    char 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的实现并不是很难

  • 5
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值