要在linux下做一个模仿ftp的小型服务器,后来在百度文库中找到一份算是比较完整的实现,就在原代码一些重要部分上备注自己的理解(可能有误,千万不要轻易相信)。

客户端:

客户端要从服务器端中读取数据,然后将read到的数据存在rcv_buf数组中,再使用atoi函数将rcv_buf中的数字字符提取出来(atoi提取数字时有着自己的规则),这样客户端解可以根据reply_code这个返回值来判断服务器要告诉自己什么。

 //从套接字描述sock_fd中获取服务器的回复//
int resp_from_server(int sock_fd)
{
static int reply_code=,count=;
char rcv_buf[];
count=read(sock_fd, rcv_buf, );
if(count>) //如果从服务器端读取数据成功,那么就向服务器反馈reply_code=atoi(rcv_buf);然后服务器端用recv_client_info(client_sock);来接收反馈信息
{
reply_code=atoi(rcv_buf); //atoi函数把字符串转化成整型数,该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。
//该函数要求被转换的字符串是按十进制数理解的。
}
else
return ;
while()
{
if(count<=)
break;
rcv_buf[count]='\0';
printf("%s",rcv_buf);
count=read(sock_fd, rcv_buf, );
}
return reply_code;
}
/*详解一下这个resp_from_server()函数,在此之前先了解一下read函数的大致用法及其定义;
参考:https://blog.csdn.net/hhhlizhao/article/details/71552588 函数定义:ssize_t read(int fd, void * buf, size_t count); 函数说明:read()会把参数fd所指的文件传送count 个字节到buf 指针所指的内存中。 返回值:返回值为实际读取到的字节数, 如果返回0, 表示已到达文件尾或是无可读取的数据。若参数count 为0, 则read()不会有作用并返回0。 注意:read时fd中的数据如果小于要读取的数据,就会引起阻塞。 好的,read函数就点到为止;意犹未尽者可以自行请教搜索引擎; 简单总结一下,read函数就是把用第一个参数描述的文件的第三个参数的数量的字节写入到第二个参数中
那么,怎么会突然就read起来了呢?我怎么知道第一个参数第对哪个文件的描述?
请君息怒,您冷静分析,是不是 大部分函数在调用resp_from_server()函数之前都会调用send_to_server()或者其他的可以从服务器端得到数据的函数
或者你可以看一看服务器端的send_to_client()函数,是不是有很多个send_to_client()函数的第二个参数都是从以下的字符数组中传入的
char serverInfo220[]="220 登录服务器";
char serverInfo230[]="230 root用户";
char serverInfo331[]="331 请输入用户密码";
char serverInfo221[]="221 ";
char serverInfo150[]="150 ";
char serverInfo226[]="226 关闭数据连接";
char serverInfo200[]="200 ";
char serverInfo215[]="215 ";
char serverInfo213[]="213 ";
char serverInfo211[]="211 ";
char serverInfo350[]="350 ";
char serverInfo530[]="530 登录失败";
char serverInfo531[]="531 匿名用户";
char serverInfo123[]="123 wu";
char serverInfo[]="202"; 这时我们要回到客户端了,看看这个函数count=read(sock_fd, rcv_buf, 510);如果read成功的话,就会返回第二个参数所读入的字节数
否则返回-1(至于返回0的情况就自行百度了) 这时我们再看int resp_from_server(int sock_fd)函数如果调用成功后的返回值是什么,没错就是reply_code;
那么reply_code是从何而来的?
请看 reply_code=atoi(rcv_buf);
这里有牵引出一个atoi函数了,至于它的作用就不详细介绍了
大意就是将一个字符串中的数字字符串转化成int型的,当然它并不是无条件转换的,atoi遇到空格会直接无视掉它;
“它们都从字符串开始寻找数字或者正负号或者小数点,然后遇到非法字符终止,不会报异常:”“”
看到了吧,所谓的非法字符就是从左到右遇到的第一个非数字或者正负号或者小数点的字符;
比如,atoi处理 "226 关闭数据连接";这个字符串,返回值是226;
再看,atoi处理 " 331 请输入用户密码"; 的返回值是331;【注意前面的空格】
这样atoi函数处理完后使得 resp_from_server(int sock_fd) 函数返回的reply_code是一个从字符串提取出来的int型;
好像可以理解为啥 char serverInfo[]="202";存储的字符串是几个数字了; 这就是为啥你会看到一堆的 如什么 226,227,331,220这样的数了; 当然,我的理解是,为啥服务器端要以这样的形式来告诉客户端呢?
你去看看服务器端的 do_client_work()函数中,人家服务器是不是每发送一次send_to_client(client_sock, serverInfo221, strlen(serverInfo221));
这样的信息前面都会调用相关的函数来处理客户端发起的请求了,之所以要这样反馈我个人觉得有以下两个原因
一是客户端和服务器端进行的不是一次性处理事件,需要进行交互
二是服务器端为了提醒客户端,喂,我出来完你的请求了,你那边还要干什么,自己看着办吧
不然没有通知的话,我们人类就不能明显地发现二者之间什么时候完成处理,我们下一步要客户端做什么这样的事情了
以上是我理解
对于atoi函数的理解:https://www.cnblogs.com/wzxwhd/p/6030083.html
对于read函数的理解:http://c.biancheng.net/cpp/html/3037.html
*/

客户端要给服务器端发送数据,下面定义的函数并不是简单地将某个参数直接发送过去,而是将两个参数strcat起来后再发过去。

 //客户端发送数据到服务器
/*send_to_server(const char *s1, const char *s2, int sock_fd)函数的具体作用是
将参数的两个字符拼接起来放在“ char send_buf[256];”中,然后一并发送给服务器端
*/
int send_to_server(const char *s1, const char *s2, int sock_fd)
{
char send_buf[];
int send_err, len;
if(s1)
{
strcpy(send_buf,s1);
if(s2)
{ //strcat函数用来将两个char类型链接,结果存放在第一个参数中
strcat(send_buf, s2);
strcat(send_buf,"\r\n");
len = strlen(send_buf);
send_err = send(sock_fd, send_buf, len, );
}
else
{
strcat(send_buf,"\r\n");
len = strlen(send_buf);
send_err = send(sock_fd, send_buf, len, );
}
}
if(send_err < )
printf("send() error!\n");
return send_err;
}

服务器:

 //服务器端给客户端发送信息
/*send函数的参数:
第一个参数:指定发送端套接字描述符;
第二个参数:指明一个存放应用程序要发送数据的缓冲区;
第三个参数:指明实际要发送的数据的字节数;
第四个参数:一般为0;
*/ void send_to_client(int client_sock,char* info,int length)
{
int len;
if((len = send(client_sock, info, length,))<)
{
perror("信息发送失败");
return;
}
}
 /*服务器接收客户端的信息
函数中使用了一个recv函数来接收客户端发来的数据,该函数的第一个参数client_sock指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据,第三个参数指明第二个餐数中缓冲区的长度;
第四个参数一般为0; 因此,执行这个函数后,client_Control_Info里的内容就是从客户端接收到的数据;
参考:https://blog.csdn.net/ly0303521/article/details/52290217
*/
//这个client_Control_Info数组是一个全局变量,在下面函数中用来存在接收客户端发送给服务器端的控制连接的相关数据
int recv_from_client(int client_sock)
{
int num;
if((num=recv(client_sock,client_Control_Info,MAX_INFO,))<)
{
perror("信息接收失败");
return;
}
client_Control_Info[num]='\0';
printf("Client %d Message:%s\n",pthread_self(),client_Control_Info);
//获得线程自身的ID。pthread_t的类型为unsigned long int;
//参考:https://blog.csdn.net/liangzhao_jay/article/details/79746794
if(strncmp("USER", client_Control_Info, ) == ||strncmp("user", client_Control_Info, ) == )
return ;
return ;
}
/*strncmp函数的用法:
原型:int strncmp (const char *s1, const char *s2, size_t size)
strncmp函数指定比较size个字符。也就是说,如果字符串s1与s2的前size个字符相同,
函数返回值为0。
为什么呢?
你可以去看看服务器端的 “int send_to_server(const char *s1, const char *s2, int sock_fd)”
函数的实现,它将两个参数s1和s2拼接起来放在send_buf[256]数组中,然后使用 send(sock_fd, send_buf, len, 0);
将数组发送给客户端,所以此时只需比较收到的前四为是否我USER即可; 当然,服务器使用client_Control_Info数组来接收上述从客户端发送过来的数据
*/

服务器是如何识别客户端发送给自己的数据所要实现的是什么功能?

服务器使用了一个strncmp函数来处理从客户端得到的字符命令,先处理前n位,得出是什么类型的命令,后续再处理命令后面的其他信息。

对accept函数的一丢丢理解

 int main(int argc,char *argv[])
{
pthread_t thread;
struct ARG arg;
struct sockaddr_in server; if((ftp_server_sock=socket(AF_INET,SOCK_STREAM,))==-)
{
perror("套接字创建失败");
exit();
} int opt=SO_REUSEADDR;
setsockopt(ftp_server_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
/* 获取或者设置与某个套接字关联的选 项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。
当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,
应该将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。
*/ bzero(&server,sizeof(server));
server.sin_family=AF_INET;
server.sin_port=htons(FTP_SERVER_PORT);
server.sin_addr.s_addr=htonl(INADDR_ANY);
//服务器端要用 bind() 函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理;
//而客户端要用 connect() 函数建立连接。 if(bind(ftp_server_sock,(struct sockaddr *)&server,sizeof(struct sockaddr))==-)
{
perror("套接字绑定错误");
exit();
}
//bind起来之后,服务器端就可以进入listen()监听的状态;
if(listen(ftp_server_sock,LISTEN_QENU)==-) //listen函数进入监听的状态,第二个参数是监听队列的长度
{
perror("监听错误");
exit();
}
// 接收客户端请求
int ftp_client_sock; //即将在accept()函数中产生的新套接字的描述
struct sockaddr_in client;
int sin_size=sizeof(struct sockaddr_in); //accept函数中的第三个参数
while() //使用一个while循环主要是因为对面的等待处理是个对列来的
{ //accept函数提取出所监听套接字的等待连接队列中第一个连接请求,创建一个新的套接字,并返回指向该套接字的文件描述符
//也就是这里的ftp_client_sock是accept后新创建的套接字的描述符
//新建立的套接字准备发送send和接收数据recv(),所以下面你会看到arg.client_sock=ftp_client_sock;赋值给client_sock的操作
//然后这个client_sock作为一个全局变量就开始在服务器和客户端的send和recv之间开始忙碌地奔波起来
//accept函数中的第一个参数是服务器一开始创建的套接字描述符
//accept函数中的第二个参数保存了客户端的IP地址和端口号
if((ftp_client_sock=accept(ftp_server_sock,(struct sockaddr *)&client,&sin_size))==-)
{
perror("接收错误");
exit();
} arg.client_sock=ftp_client_sock;
memcpy((void*)&arg.client,&client,sizeof(client)); if(pthread_create(&thread,NULL,Handle_Client_Request,(void*)&arg))
{
perror("多线程创建错误");
exit();
}
}
close(ftp_server_sock);
}

备注:以上都是我自己的理解,可能有些地方有误,如果你发现有错的地方,希望你能在评论区指出来,大家一起学习,感谢!

文档原文链接:https://wenku.baidu.com/view/3e9d8a98856a561253d36f2e.html?from=search

linux下模拟FTP服务器(笔记)的更多相关文章

  1. Linux下部署FTP服务器

    Linux下部署FTP服务器 下载安装包 在这里介绍的是离线部署FTP,首先下载对应的rpm包,下载链接为: 下载vsftpd服务 下载FTP客户端 安装ftp服务器 关闭防火墙 service ip ...

  2. win10与Ubantu双系统:Linux下开启FTP服务器与创建无线热点(实现文件共享)

    如何在win系统下使用filelizza这个软件搭建FTP服务器,然后建立一个无线局域网,让平板终端连接以后,访问电脑硬盘的文件. 如果是只在win7环境下,一切都很简单,按照上文提供的教程就可以实现 ...

  3. Linux下搭建FTP服务器

    实习的公司有一台老服务器转作为FTP服务器,老大把这个任务交给了我.这两天边学边卖的捣腾起来,总算搞成.现在记录下来,加深映像,也以便以后查看复习. 服务器安装的是:Red Hat Enterpris ...

  4. linux 下安装ftp服务器

    最后重启    # service vsftpd restart   1.查看是否安装vsftp rpm -qa | grep ftp 如果出现    vsftpd-2.0.5-16.el5_5.1 ...

  5. Linux下搭建FTP服务器(Ubuntu16.04)

    搞了下FTP服务器,基本上能遇到的问题都遇到了-.-! 先说步骤: 1.安装vsftpd软件包 sudo apt-get install vsftpd 2.打开配置文件 vim /etc/vsftpd ...

  6. 在linux下搭建ftp服务器【转】

    1 安装 vsftpd yum install vsftpd 2 配置 vsftpd 打开 vsftpd 文件: vi /etc/vsftpd/vsftpd.conf 初次修改前建议备份该文件 2.1 ...

  7. Linux 下搭建ftp服务器 指定用户指定目录及其他操作

    搭建 Linux下 rpm -qa |grep vsftpd查看是否安装 没安装yum安装 /etc/vsftpd/目录下有vsftpd.conf配置文件 根据需求 进行配置  是否使用匿名用户以及文 ...

  8. Linux下用ftp更新web内容!

    使用ftp更新web!让网页更新一次OK! 配置如下: 1.在Linux下安装ftp服务器! yum -y install vsftpd #ftp由vsftpd提供! 2.配置主配置文件/etc/vs ...

  9. Linux CentOS 6.5 下 vsftpd ftp服务器搭建

    Linux CentOS 6.5 下 vsftpd ftp服务器搭建 by:授客 QQ:1033553122   操作系统环境:CentOS 6.5-x86_64 下载地址:http://www.ce ...

随机推荐

  1. unity3d-物理引擎

    简介 物理引擎就是在游戏中模拟真实的物理效果,比如,场景中有两个立方体对象,一个在空中,一个在地面上,在空中的立方体开始自由下落,然后与地面上的立方体对象发生碰撞,而物理引擎就是用来模拟真实碰撞的效果 ...

  2. linux命令:帮助命令

    帮助命令:man 命令名称:man 命令英文原意:manual 命令所在路径:/usr/bin/man 执行权限:所有用户 语法:man [命令或配置文件] 功能描述:获得帮助信息 范例:$man l ...

  3. 第七章之main函数和启动例程

    main函数和启动例程 为什么汇编程序的入口是_start,而C程序的入口是main函数呢?本节就来解释这个问题.在讲例 18.1 “最简单的汇编程序”时,我们的汇编和链接步骤是: $ as hell ...

  4. 016-sed

    行处理:一次处理一行.正则选定文本 ----->>sed处理格式:一.命令行格式:sed [options] 'command' files(如果没有则是通过管道)1.options: - ...

  5. shell篇(二)

    Linux的shell种类比较多,常见的有:Bourne Shell(/user/bin/sh或者/bin/sh), Bourne Again Shell(/user/bin/bash或者/bin/b ...

  6. LLVM/Clang编译相关研究

    https://blog.csdn.net/guojin08/article/details/54310858 https://juejin.im/entry/5c64da44518825620b45 ...

  7. javascript 中function(){},new function(),new Function(),Function 摘录

    函数是JavaScript中很重要的一个语言元素,并且提供了一个function关键字和内置对象Function,下面是其可能的用法和它们之间的关系. function使用方式 var foo01 = ...

  8. SpringBoot之集成Socket

      1.Socket是什么,这里不做介绍.开发环境:jdk1.8,win7_64旗舰版,idea   2.初始化一个springboot项目   3.开始Socket服务端实现,Socket相关接口在 ...

  9. UVA756 Biorhythms

    UVA756 Biorhythms crt crt裸题 因为模数已知所以有些值能直接求 #include<iostream> #include<cstdio> using na ...

  10. c++性能之map实现性能比较

    http://www.cnblogs.com/zhjh256/p/6346501.html讲述了基本的map操作,在测试的时候,发现map的性能极为低下,与java相比相差了接近200倍.测试的逻辑如 ...