一、框架简介
- common 模块 <头文件定义>
- sysutil 模块 <公有工具定义>
- miniftp 模块 <检测是否root启动,主进程完成客户端的连接,以及子进程的创建, 开启会话>
- session 模块 <会话结构的定义,实现会话函数,创建子进程,区分出ftp进程和nobody进程,实现ftp与nobody之间的通讯连接,更改nobody进程getpwnam>
- ftpproto 模块 <ftp进程>
- priparent 模块 <nobody进程>
- Makefile 文件
二、各个模块代码框架
1、common 模块 <头文件定义>
#ifndef _COMMON_H_
#define _COMMON_H_
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pwd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
//ERR_EXIT函数宏定义
#define ERR_EXIT(m) \
do{ \
perror(m);\
exit(EXIT_FAILURE);\
}while(0);
#define MAX_COMMAND_LINE 1024 //获取的最大命令
#define MAX_COMMAND 32 //获取的最大命令数
#define MAX_ARG 1024 //获取的最大参数
#endif /* _COMMOM_H_ */
2、sysutil 模块 <公有工具定义>
① sysutil.h 头文件
#ifndef _SYSUTIL_H_
#define _SYSUTIL_H_
#include"common.h"
int tcp_server(const char *host, unsigned short port);
int accept_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 readline(int sockfd, void *buf, size_t maxline); //按行读取函数
ssize_t recv_peek(int sockfd, void *buf, size_t len);
#endif
② sysutil.c 源文件
//公有工具定义
#include"sysutil.h"
/**
* tcp_server - 启动tcp服务器
* @host: 服务器IP地址或者服务器主机名
* @port: 服务器端口
* 成功返回监听套接字
*/
int tcp_server(const char *host, unsigned short port)
{
int listenfd; //监听套接字
//(PF_INET网际协议, SOCK_STREAM(套接字类型-流式套接字),
//0(自动选择TCP协议));
if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
ERR_EXIT("tcp_server");
//struct socketaddr_in
//{
// short int sin_family;
//}
struct sockaddr_in servaddr;//地址套接字变量
memset(&servaddr, 0, sizeof(servaddr));//用0初始化servaddr内容
servaddr.sin_family = AF_INET;//地址家族-AF_INET
if (host != NULL)
{
//inet_aton 将点分十进制的IP地址转换成一个网络字节序的32位的整数,
//将host的转换IP地址sinaddr放到servaddr.sin_addr里,若返回值等于零
//则返回的是无效的IP地址,说明host很可能是服务器的主机名,则我们
//可以根据主机名称得到IP地址
if (inet_aton(host, &servaddr.sin_addr) == 0)
{
struct hostent *hp;
//通过主机名获得所有的IP地址
hp = gethostbyname(host);
if (hp == NULL)
ERR_EXIT("gethostbyname");
//强转类型再取地址放到servaddr.sin_addr中去,这样就得到了IP地址。
servaddr.sin_addr = *(struct in_addr*)hp->h_addr;
}
}
//如果host是空指针,则我们认为它绑定的是本地所有的IP地址。
//htonl() 函数原型为 unit32_t htonl(unit32_t hostlong);
//函数名中h-host 主机地址,to-to转换,n-net网络, l-unsigned long无符号的长整型;
//函数的返回值是一个32位的网络字节顺序;
//函数的作用是将一个32位数从主机字节顺序转换成网络字节序,简单来说,就是把一个
//32位数高低位互换。
else
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//填充IP地址
//htonl() 函数原型为 unit16_t htons(unit16_t hostlong);
//函数名中h-host 主机地址,to-to转换,n-net网络, s-signed long无符号的短整型;
//函数的返回值是一个16位的网络字节顺序;
//函数的作用是将一个16位数从主机字节顺序转换成网络字节序,简单来说,就是把一个
//16位数高低位互换。
servaddr.sin_port = htons(port);//填充端口号
int on = 1;//设置地址重复利用
//setsockopt函数使用到的头文件有:
//#include<sys/types.h> #include<sys/socket.h>
//功能:一般用来设置参数listenfd所指定的socket状态,
//第二个参数一般设为SOL_SOCKET以存取socket层
//第三个参数SO_REUSEADDR表示允许bind()过程中本地地址可以重复使用
//第四个参数表示想要设置的值
//第五个参数表示想要设置值的长度
if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on))) < 0)
ERR_EXIT("setsockopt");
//绑定
//使用到的头文件#include<sys/socket.h>
//函数原型: int bind(int sockfd, const struct sockaddr, socklen_t addrlen);
//参数分析:
//第一个参数为一个监听套接字
//第二个参数为一个指向特定协议的地址结构的指针
//第三个参数为地址结构的长度
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
//监听
//使用到的头文件#include<sys/types> #include<sys/socket.h>
//函数原型:int listen(int sockfd, int backlog);
//函数功能:将sockfd指定为接受连接请求的套接字
//参数说明:
//socket:指定的套接字描述符。
//backlog:等待连接的队列大小。
//在connect请求过来的时候,完成三次握手后先将连接放到这个队列
//中,直到被accept处理。如果这个队列满了,且有新的连接的时候,
//对方可能会收到出错信息。
if (listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");
return listenfd;
}
/**
* 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 > 0)
{
fd_set accept_fdset;
struct timeval timeout;
FD_ZERO(&accept_fdset);
FD_SET(fd, &accept_fdset);
timeout.tv_sec = wait_seconds;
timeout.tv_usec = 0;
do {
ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout);
} while (ret < 0 && errno == EINTR);
if (ret == -1) {
return -1;
}
else if (ret == 0) {
errno = ETIMEDOUT;
return -1;
}
}
if (addr != NULL)
ret = accept(fd, (struct sockaddr*)addr, &addrlen);
else
ret = accept(fd, NULL, NULL);
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 > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
}
else if (nread == 0)
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 > 0) {
if ((nwritten = write(fd, bufp, nleft)) < 0) {
if (errno == EINTR)
continue;
return -1;
}
else if (nwritten == 0)
continue;
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
/**
* 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 (1) {
ret = recv_peek(sockfd, bufp, nleft);
if (ret < 0)
return ret;
else if (ret == 0)
return ret;
nread = ret;
int i;
for (i=0; i<nread; i++) {
if (bufp[i] == '\n') {
ret = readn(sockfd, bufp, i+1);
if (ret != i+1)
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 -1;
}
/**
* recv_peek - 仅仅查看套接字缓冲区数据,但不移除数据
* @sockfd: 套接字
* @buf: 接收缓冲区
* @len: 长度
* 成功返回>=0,失败返回-1
*/
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1) {
int ret = recv(sockfd, buf, len, MSG_PEEK);
if (ret == -1 && errno == EINTR)
continue;
return ret;
}
}
3、miniftp 模块 <检测是否root启动,主进程完成客户端的连接,以及子进程的创建>
#include "common.h"
#include"sysutil.h"
#include"session.h"
int main(int argc, char *argv[])
{
//判断是否为root用户启动
if(getuid() != 0)
{
printf("miniftp:must be started as root\n");
exit(EXIT_FAILURE);
}
//会话的初始化
session_t sess =
{
/* 控制连接 */
-1, "", "", "",
/*父子进程通道*/
-1, -1
};
int listenfd = tcp_server(NULL, 5188);
int conn;
pid_t pid;
while(1)
{
//创建连接
conn = accept_timeout(listenfd, NULL, 0);
if(conn == -1)
ERR_EXIT("accept_timeout");
pid = fork();
if (pid == -1)
ERR_EXIT("fork");
//若进程为子进程,则开始会话
if(pid == 0)
{
close(listenfd);
sess.ctrl_fd = conn;
begin_session(&sess); //函数实现在session.c中
}
else
close(conn);
}
return 0;
}
4、session 模块 <会话结构的定义和实现,创建子进程实现ftp与nobody之间的通讯连接>
① 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_ */
② session.c 源文件
#include "common.h"
#include "session.h"
#include "ftpproto.h"
#include "privparent.h"
void begin_session(session_t *sess)
{
struct passwd *pw = getpwnam("nobody");
if(pw == NULL)
return;
//要先该组ID,再改用户ID,因为先改用户ID就可能没有权限修改组ID了
if(setegid(pw->pw_gid) < 0)
ERR_EXIT("setepid");
if(seteuid(pw->pw_uid) < 0)
ERR_EXIT("seteuid");
int sockfds[2];
//创建套接字对
if(socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds) < 0)
ERR_EXIT("socketpair");
pid_t pid;
pid = fork();
if (pid < 0)
ERR_EXIT("fork");
if(pid == 0)
{
//ftp服务进程
close(sockfds[0]);
sess->child_fd = sockfds[1]; //把写入的东西放到child_fd中
handle_child(sess);
}
else
{
//nobody进程
close(sockfds[1]);
sess->parent_fd = sockfds[0];
handle_parent(sess);
}
}
5、ftpproto 模块 <ftp进程>
① ftpproto.h 头文件
#ifndef _FTPPROTO_H_
#define _FTPPROTO_H_
#include"session.h"
void handle_child(session_t *sess); //处理子进程会话
#endif
② ftpproto.c 源文件
#include"ftpproto.h"
#include"sysutil.h"
void handle_child(session_t *sess)
{
writen(sess->ctrl_fd, "220 (miniftp 0.1)\r\n", strlen("220 (miniftp 0.1)\r\n"));
while(1)
{
memset(sess->cmdline, 0, sizeof(sess->cmdline));
memset(sess->cmd, 0, sizeof(sess->cmd));
memset(sess->arg, 0, sizeof(sess->arg));
readline(sess->ctrl_fd, sess->cmdline, MAX_COMMAND_LINE);
//解析FTP命令与参数
//处理FTP命令
}
}
6、priparent 模块 <nobody进程>
① privparent.h 头文件
#ifndef _PRIVPARENT_H_
#define _PRIVPARENT_H_
#include"session.h"
void handle_parent(session_t *sess); //处理父进程会话
#endif
② privparent 源文件
#include"privparent.h"
void handle_parent(session_t *sess)
{
char cmd;
while(1)
{
read(sess->parent_fd, &cmd, 1);
//解析内部命令
//处理内部命令
}
}
7、Makefile 文件
CC=gcc
CFLAG=-Wall -g
OBJS=miniftp.o sysutil.o session.o privparent.o ftpproto.o
LIBS=
BIN=miniftpd
$(BIN):$(OBJS)
$(CC) $(CFLAGS) $^ -o $@ $(LIBS)
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY:clean
clean:
rm -fr *.o $(BIN)
8、运行状况
编译:make
运行:./miniftpd
LeapFtp连接: