从今天开始,正式进入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. 微软3389远程漏洞CVE-2019-0708批量检测工具

    0x001 Win下检测 https://github.com/robertdavidgraham/rdpscan C:\Users\K8team\Desktop\rdpscan-master\vs1 ...

  2. Mac下安装VirtualBox并在VirtualBox中安装CentOS7

    VirtualBox (百科)VirtualBox 是一款开源虚拟机软件.VirtualBox 是由德国 Innotek 公司开发,由Sun Microsystems公司Sun Microsystem ...

  3. 基于java的简单Socket编程

    1TCP协议与UDP协议     1.1 TCP               TCP是(Tranfer Control Protocol)的简称,是一种面向连接的保证可靠传输的协议.通过TCP协议传输 ...

  4. pytorch1.0进行Optimizer 优化器对比

    pytorch1.0进行Optimizer 优化器对比 import torch import torch.utils.data as Data # Torch 中提供了一种帮助整理数据结构的工具, ...

  5. Python26之字典2(内置函数)

    一.工厂函数的概念 和序列类型的工厂函数一样,dict()也是一个工厂函数,本质上是一个类,Python程序无处不对象的概念可见一斑 二.字典类型内置函数的用法 1.fromkeys(iterable ...

  6. 【转载】Maven入门实践

    Maven 确确实实是个好东西,用来管理项目显得很方便,但是如果是通过 Maven 来远程下载 JAR 包的话,我宿舍的带宽是4兆的,4个人共用,有时候用 Maven 来远程下载 JAR 包会显得很慢 ...

  7. jdk8新特性--使用lambda表达式的延迟执行特性优化性能

    使用lambda表达式的延迟加载特性对代码进行优化:

  8. Istio技术与实践6:Istio如何为服务提供安全防护能力

    凡是产生连接关系,就必定带来安全问题,人类社会如此,服务网格世界,亦是如此. 今天,我们就来谈谈Istio第二主打功能---保护服务. 那么,便引出3个问题: l  Istio凭什么保护服务? l  ...

  9. 十分钟快速创建 Spring Cloud 项目

    一般来说,Intelij IDEA 可以通过 Maven Archetype 来快速生成Maven项目,其实 IDEA 集成了 Spring 官方提供的 Spring Initializr,可以非常方 ...

  10. Consul 注册中心介绍

    在 Spring Cloud 体系中,几乎每个角色都会有两个以上的产品提供选择,比如在注册中心有:Eureka.Consul.zookeeper.etcd 等:网关的产品有 Zuul.Spring C ...