从今天开始,正式进入MiniFtp的代码编写阶段了,好兴奋,接下来很长一段时间会将整个实现过程从无到有一点点实现出来,达到综合应用的效果,话不多说正入正题:

这节主要是将基础代码框架搭建好,基于上节介绍的系统逻辑结构,首先建立主控模块

在学习网络编程时积累了不少的工具代码,所以可以将其整合到系统工具模块

sysutil.h:

#ifndef _SYS_UTIL_H_
#define _SYS_UTIL_H_int getlocalip(char *ip); void activate_nonblock(int fd);
void deactivate_nonblock(int fd); int read_timeout(int fd, unsigned int wait_seconds);
int write_timeout(int fd, unsigned int wait_seconds);
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds); ssize_t readn(int fd, void *buf, size_t count);
ssize_t writen(int fd, const void *buf, size_t count);
ssize_t recv_peek(int sockfd, void *buf, size_t len);
ssize_t readline(int sockfd, void *buf, size_t maxline); void send_fd(int sock_fd, int fd);
int recv_fd(const int sock_fd); #endif /* _SYS_UTIL_H_ */

sysutil.c:

#include "sysutil.h"int getlocalip(char *ip)
{
char host[] = {};
if (gethostname(host, sizeof(host)) < )
return -;
struct hostent *hp;
if ((hp = gethostbyname(host)) == NULL)
return -; strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr));
return ;
} /**
* activate_noblock - 设置I/O为非阻塞模式
* @fd: 文件描符符
*/
void activate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == -)
ERR_EXIT("fcntl"); flags |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -)
ERR_EXIT("fcntl");
} /**
* deactivate_nonblock - 设置I/O为阻塞模式
* @fd: 文件描符符
*/
void deactivate_nonblock(int fd)
{
int ret;
int flags = fcntl(fd, F_GETFL);
if (flags == -)
ERR_EXIT("fcntl"); flags &= ~O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
if (ret == -)
ERR_EXIT("fcntl");
} /**
* read_timeout - 读超时检测函数,不含读操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int read_timeout(int fd, unsigned int wait_seconds)
{
int ret = ;
if (wait_seconds > )
{
fd_set read_fdset;
struct timeval timeout; FD_ZERO(&read_fdset);
FD_SET(fd, &read_fdset); timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret = select(fd + , &read_fdset, NULL, NULL, &timeout);
} while (ret < && errno == EINTR); if (ret == )
{
ret = -;
errno = ETIMEDOUT;
}
else if (ret == )
ret = ;
} return ret;
} /**
* write_timeout - 读超时检测函数,不含写操作
* @fd: 文件描述符
* @wait_seconds: 等待超时秒数,如果为0表示不检测超时
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int write_timeout(int fd, unsigned int wait_seconds)
{
int ret = ;
if (wait_seconds > )
{
fd_set write_fdset;
struct timeval timeout; FD_ZERO(&write_fdset);
FD_SET(fd, &write_fdset); timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret = select(fd + , NULL, NULL, &write_fdset, &timeout);
} while (ret < && errno == EINTR); if (ret == )
{
ret = -;
errno = ETIMEDOUT;
}
else if (ret == )
ret = ;
} return ret;
} /**
* accept_timeout - 带超时的accept
* @fd: 套接字
* @addr: 输出参数,返回对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
*/
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in); if (wait_seconds > )
{
fd_set accept_fdset;
struct timeval timeout;
FD_ZERO(&accept_fdset);
FD_SET(fd, &accept_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
ret = select(fd + , &accept_fdset, NULL, NULL, &timeout);
} while (ret < && errno == EINTR);
if (ret == -)
return -;
else if (ret == )
{
errno = ETIMEDOUT;
return -;
}
} if (addr != NULL)
ret = accept(fd, (struct sockaddr*)addr, &addrlen);
else
ret = accept(fd, NULL, NULL);
/* if (ret == -1)
ERR_EXIT("accept");
*/ return ret;
} /**
* connect_timeout - connect
* @fd: 套接字
* @addr: 要连接的对方地址
* @wait_seconds: 等待超时秒数,如果为0表示正常模式
* 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
*/
int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds)
{
int ret;
socklen_t addrlen = sizeof(struct sockaddr_in); if (wait_seconds > )
activate_nonblock(fd); ret = connect(fd, (struct sockaddr*)addr, addrlen);
if (ret < && errno == EINPROGRESS)
{
printf("AAAAA\n");
fd_set connect_fdset;
struct timeval timeout;
FD_ZERO(&connect_fdset);
FD_SET(fd, &connect_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = ;
do
{
/* 一量连接建立,套接字就可写 */
ret = select(fd + , NULL, &connect_fdset, NULL, &timeout);
} while (ret < && errno == EINTR);
if (ret == )
{
ret = -;
errno = ETIMEDOUT;
}
else if (ret < )
return -;
else if (ret == )
{
printf("BBBBB\n");
/* ret返回为1,可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
/* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
int err;
socklen_t socklen = sizeof(err);
int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
if (sockoptret == -)
{
return -;
}
if (err == )
{
printf("DDDDDDD\n");
ret = ;
}
else
{
printf("CCCCCC\n");
errno = err;
ret = -;
}
}
}
if (wait_seconds > )
{
deactivate_nonblock(fd);
}
return ret;
} /**
* readn - 读取固定字节数
* @fd: 文件描述符
* @buf: 接收缓冲区
* @count: 要读取的字节数
* 成功返回count,失败返回-1,读到EOF返回<count
*/
ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count;
ssize_t nread;
char *bufp = (char*)buf; while (nleft > )
{
if ((nread = read(fd, bufp, nleft)) < )
{
if (errno == EINTR)
continue;
return -;
}
else if (nread == )
return count - nleft; bufp += nread;
nleft -= nread;
} return count;
} /**
* writen - 发送固定字节数
* @fd: 文件描述符
* @buf: 发送缓冲区
* @count: 要读取的字节数
* 成功返回count,失败返回-1
*/
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char *bufp = (char*)buf; while (nleft > )
{
if ((nwritten = write(fd, bufp, nleft)) < )
{
if (errno == EINTR)
continue;
return -;
}
else if (nwritten == )
continue; bufp += nwritten;
nleft -= nwritten;
} return count;
} /**
* recv_peek - 仅仅查看套接字缓冲区数据,但不移除数据
* @sockfd: 套接字
* @buf: 接收缓冲区
* @len: 长度
* 成功返回>=0,失败返回-1
*/
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while ()
{
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == - && errno == EINTR)
continue;
return ret;
}
} /**
* readline - 按行读取数据
* @sockfd: 套接字
* @buf: 接收缓冲区
* @maxline: 每行最大长度
* 成功返回>=0,失败返回-1
*/
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = buf;
int nleft = maxline;
while ()
{
ret = recv_peek(sockfd, bufp, nleft);
if (ret < )
return ret;
else if (ret == )
return ret; nread = ret;
int i;
for (i=; i<nread; i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd, bufp, i+);
if (ret != i+)
exit(EXIT_FAILURE); return ret;
}
} if (nread > nleft)
exit(EXIT_FAILURE); nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
exit(EXIT_FAILURE); bufp += nread;
} return -;
} void send_fd(int sock_fd, int fd)
{
int ret;
struct msghdr msg;
struct cmsghdr *p_cmsg;
struct iovec vec;
char cmsgbuf[CMSG_SPACE(sizeof(fd))];
int *p_fds;
char sendchar = ;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
p_cmsg = CMSG_FIRSTHDR(&msg);
p_cmsg->cmsg_level = SOL_SOCKET;
p_cmsg->cmsg_type = SCM_RIGHTS;
p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
p_fds = (int*)CMSG_DATA(p_cmsg);
*p_fds = fd; msg.msg_name = NULL;
msg.msg_namelen = ;
msg.msg_iov = &vec;
msg.msg_iovlen = ;
msg.msg_flags = ; vec.iov_base = &sendchar;
vec.iov_len = sizeof(sendchar);
ret = sendmsg(sock_fd, &msg, );
if (ret != )
ERR_EXIT("sendmsg");
} int recv_fd(const int sock_fd)
{
int ret;
struct msghdr msg;
char recvchar;
struct iovec vec;
int recv_fd;
char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
struct cmsghdr *p_cmsg;
int *p_fd;
vec.iov_base = &recvchar;
vec.iov_len = sizeof(recvchar);
msg.msg_name = NULL;
msg.msg_namelen = ;
msg.msg_iov = &vec;
msg.msg_iovlen = ;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = ; p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));
*p_fd = -;
ret = recvmsg(sock_fd, &msg, );
if (ret != )
ERR_EXIT("recvmsg"); p_cmsg = CMSG_FIRSTHDR(&msg);
if (p_cmsg == NULL)
ERR_EXIT("no passed fd"); p_fd = (int*)CMSG_DATA(p_cmsg);
recv_fd = *p_fd;
if (recv_fd == -)
ERR_EXIT("no passed fd"); return recv_fd;
}

以上两个的具体实现之前都已经学过了,这里就不一一描述了。

对于这些函数会用到一些头文件,这里统一放到一个头文件中,集中管理:

common.h:

#ifndef _COMMON_H_
#define _COMMON_H_ #include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h> #include <stdlib.h>
#include <stdio.h>
#include <string.h> #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} \
while () #endif /* _COMMON_H_ */

下面来编译运行一下,确保目前代码的正确性,所以还需要准备一个Makefile文件:

Makefile:

.PHONY:clean
CC=gcc
CFLAGS=-Wall -g
BIN=miniftpd
OBJS=main.o sysutil.o
$(BIN):$(OBJS)
$(CC) $(CFLAGS) $^ -o $@
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o $(BIN)

再次编译:

目前的代码都正常了,接下来正式一步步编写有效代码,由于ftp是需要root权限的,所以第一步先来做root权限的判断:

编译运行:

接下来,MiniFtp是一个服务器端,它都有一个这样的步骤:创建套接字、绑定监听、接受连接、处理连接,所以可以将其封装到一个函数当中:

接下来具体实现它:

接下来绑定监听,首先准备sockaddr_in参数:

接下来则可以开始绑定地址了:

int tcp_server(const char *host, unsigned short port)
{
//创建套接字
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, )) < )
ERR_EXIT("tcp_server"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
if (host != NULL)
{
if (inet_aton(host, &servaddr.sin_addr) == )
{//证明传过来的是主机名而不是点分十进制的IP地址,接下来要进行转换
struct hostent *hp;
hp = gethostbyname(host);
if (hp == NULL)
ERR_EXIT("gethostbyname"); servaddr.sin_addr = *(struct in_addr*)hp->h_addr;
}
}
else//这时用主机的任务地址
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port);//端口号 //设置地址重复利用
int on = 1;
if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on))) < 0)
ERR_EXIT("gethostbyname"); //绑定
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind"); return listenfd;
}

最后监听:

/**
* tcp_server - 启动tcp服务器
* @host: 服务器IP地址或者服务器主机名
* @port: 服务器端口
* 成功返回监听套接字
*/
int tcp_server(const char *host, unsigned short port)
{
//创建套接字
int listenfd;
if ((listenfd = socket(PF_INET, SOCK_STREAM, )) < )
ERR_EXIT("tcp_server"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
if (host != NULL)
{
if (inet_aton(host, &servaddr.sin_addr) == )
{//证明传过来的是主机名而不是点分十进制的IP地址,接下来要进行转换
struct hostent *hp;
hp = gethostbyname(host);
if (hp == NULL)
ERR_EXIT("gethostbyname"); servaddr.sin_addr = *(struct in_addr*)hp->h_addr;
}
}
else//这时用主机的任务地址
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port);//端口号 //设置地址重复利用
int on = ;
if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on))) < )
ERR_EXIT("gethostbyname"); //绑定
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind"); //监听
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen"); return listenfd;
}

接下来编译一下,程序一步步稳步开发:

编写好这个函数之后,则在main函数中去调用一下:

接着则要编写接受客户端的连接:

下面来新建session模块所需的文件:

session.h:

#ifndef _SESSION_H_
#define _SESSION_H_ #include "common.h" void begin_session(int conn); #endif /* _SESSION_H_ */

session.c:

#include "common.h"
#include "session.h" void begin_session(int conn)
{
}

然后在main.c中包含它:

接下来来实现begin_session这个方法,而根据上次介绍的逻辑结构来看:

所以需要创建两个进程:

然后再把这两个进程做的事也模块化,FTP服务进程主要是处理FTP协议相关的一些细节,模块可以叫ftpproto,而nobody进程主要是协助FTP服务进程,只对内,模块可以叫privparent,所以可以新建如下文件:

所以这里需要建立一个通道来让两进程之间可以相互通信,这里采用socketpair来进行通信:

另外可以定义一个session结构体来代表一个会话,里面包含多个信息:

session.h:

#ifndef _SESSION_H_
#define _SESSION_H_ #include "common.h" typedef struct session
{
// 控制连接
int ctrl_fd;
char cmdline[MAX_COMMAND_LINE];
char cmd[MAX_COMMAND];
char arg[MAX_ARG]; // 父子进程通道
int parent_fd;
int child_fd;
} session_t;
void begin_session(session_t *sess); #endif /* _SESSION_H_ */

上面用到了三个宏,也需要在common.h中进行定义:

这时在main中就得声明一下该session,并将其传递:

这时再回到begin_session方法中,进一步带到父子进程中去处理:

下面则在session的父子进程中进行函数的声明:

ftpproto.h:

#ifndef _FTP_PROTO_H_
#define _FTP_PROTO_H_ #include "session.h" void handle_child(session_t *sess); #endif /* _FTP_PROTO_H_ */

ftpproto.c:

#include "ftpproto.h"
#include "sysutil.h" void handle_child(session_t *sess)
{ }

privparent.h:

#ifndef _PRIV_PARENT_H_
#define _PRIV_PARENT_H_ #include "session.h"
void handle_parent(session_t *sess); #endif /* _PRIV_PARENT_H_ */

privparent.c:

#include "privparent.h"

void handle_parent(session_t *sess)
{ }

在session.c中需要包含这两个头文件:

接下来我们将注意力集中在begin_session函数中,首先我们需要将父进程改成nobody进程,怎么来改呢?这里需要用到一个函数:

下面来编写handle_child()和handle_parent():

另外在连接时,会给客户端一句这样的提示语:

所以:

这个函数暂且这样,接着来编写handle_parent():

这次主要是搭建基本框架,所以里面的基本都是虚实现,下面来编译运行看下效果:

先修改Makefile文件:

查看一下man帮助:

所以在common.h中添加该头文件:

再次编译:

类型参数不对,查看一下,目前还是int类型,应该改为session_t:

修改为:

再次编译:

接下来运行一下:

这时查看下当前进程状态:

接下来开一个FTP客户端来进行连接:

这时再查看进程状态:

而vsftpd的进程模型为:

这时由于还没有处理USER webor2006命令:

处理之后就和vsftpd一样了,以上就是miniftp的一个基本框架,下次继续。

Linux网络编程综合运用之MiniFtp实现(四)的更多相关文章

  1. Linux网络编程综合运用之MiniFtp实现(一)

    春节过后,万物复苏,在这元宵佳节的前一天,决定继续开启新年的学习计划,生命在于运动,提高源于学习,在经过漫长的Linux网络编程学习后,接下来会以一个综合的小项目来将所学的知识点综合运用,首先是对项目 ...

  2. Linux网络编程综合运用之MiniFtp实现(九)

    上次中实现了FTP命令的映射来避免很多if....else的判断,这次主要是开始实现目录列表的传输,先看一下目前实现的: 数据连接创建好之后则开始进行目录列表的传输了,而要传输目录列表,首先要将目录列 ...

  3. Linux网络编程综合运用之MiniFtp实现(五)

    转眼兴奋的五一小长假就要到来了,在放假前夕还是需要保持一颗淡定的心,上次中已经对miniFTP有基础框架进行了搭建,这次继续进行往上加代码,这次主要还是将经历投射到handle_child()服务进程 ...

  4. Linux网络编程综合运用之MiniFtp实现(八)

    上节中实现了"USER"和"PASS"命令,如下: 事实上FTP是有很多命令组成的,如果就采用上面的这种方法来实现的话,就会有很多if...else if语句, ...

  5. Linux网络编程综合运用之MiniFtp实现(七)

    上节中实现了配置文件的解析,这节来实现用户登录的验证,首先用客户端来登录vsftpd来演示登录的过程: 接着再连接miniftpd,来看下目前的效果: 接下来实现它,与协议相关的模块都是在ftppro ...

  6. Linux网络编程综合运用之MiniFtp实现(六)

    间隔了一周时间没写了,由于今年的股势行情貌似不错的样子,对于对股市完全不懂的我也在蠢蠢欲动,所以最近一周业余时间在“不务正业”-----学习炒股.发现学习它其实挺费神的,满脑子都是走势图,而且是神经有 ...

  7. Linux网络编程综合运用之MiniFtp实现(三)

    前面已经对FTP相关的一些概念有了基本的认识,接下来就要进入代码编写阶段了,也是非常兴奋的阶段,在开启这个它之前先对项目需求进行一个梳理,对其我们要实现的FTP服务器是一个什么样子. ftp命令列表 ...

  8. linux网络编程:三次握手与四次挥手

    建立TCP需要三次握手才能建立,而断开连接则需要四次握手.整个过程如下图所示: 其中三次握手即建立连接 四次挥手则为关闭连接 TCP连接的11种状态 客户端独有的:(1)SYN_SENT (2)FIN ...

  9. Linux网络编程学习(九) ----- 消息队列(第四章)

    1.System V IPC System V中引入的几种新的进程间通信方式,消息队列,信号量和共享内存,统称为System V IPC,其具体实例在内核中是以对象的形式出现的,称为IPC 对象,每个 ...

随机推荐

  1. C# RESTful API

    C# RESTful API REST 全称是 Representational State Transfer,有人说它是一种风格,并非一种标准,个人觉得挺有道理.它本身并没有创造新的技术.组件与服务 ...

  2. servlet中ServletContainerInitializer的简单说明

    根据官方文档到的说明 public interface ServletContainerInitializer Interface which allows a library/runtime to ...

  3. python 爬虫实例(四)

    环境: OS:Window10 python:3.7 爬取链家地产上面的数据,两个画面上的数据的爬取 效果,下面的两个网页中的数据取出来 代码 import datetime import threa ...

  4. PWM原理及其在电源中的应用

    熟悉单机片的同学就应该知道pwm,也就是脉冲宽度调制技术,它是通过对一系列脉冲的宽度进行调制,来获得等效的波形.是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,因其操作简单,又灵活等 ...

  5. Appium移动自动化测试-----(九) appium API 之应用操作

    1.安装应用 方法: installApp() 安装应用到设备中去.需要apk包的路径. driver.installApp("path/to/my.apk"); driver.i ...

  6. [Xamarin] - Xamarin.Forms Project with .Net Standard 2.0

    1. Install .NET Core 2.0 SDK .https://www.microsoft.com/net/download/core 2. Install Android 7.1 (AP ...

  7. [Visual Studio] - Unable to launch the IIS Express Web server 问题之解决

    背景 Visual Studio 2015 在 Debug 模式下调试失败. 错误 解决 删除解决方案下 .vs/config 文件夹,重新运行解决方案可进行调试. 参考资料 https://stac ...

  8. git的快速入门

    Git是目前世界上最先进的分布式版本控制系统(注意,仅仅是一个程序,而不是正真意义上的系统). Why为什么需要版本控制? 场景1:大学毕业前夕,你在完成毕业论文,初稿A写好了,找老师修改,老师提出意 ...

  9. Python-04-数据结构

    一.数字 整数 Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等. 计算机由于使用二进制,所以,有时候用十六进制表示 ...

  10. git彻底删除或变更子模块

    今天遇到一个很怪的问题,我想把我的一个子模块切换到另一个上游,我按照网上的方法删除子模块然后新建后,这个子模块依旧跟踪着我先前的上游.自己摸索了一下,可能方法比较傻,不过是可行的,希望能给大家一些帮助 ...