Miniftp 项目学习笔记(二) 系统框架搭建

本文档详细介绍了Miniftp项目的系统框架,包括common模块的头文件定义,sysutil模块的公有工具定义,miniftp模块的主进程及子进程创建,session模块的会话结构及ftp/nobody通讯,ftpproto模块的ftp进程,priparent模块的nobody进程,以及Makefile文件。编译运行后,可通过LeapFtp进行连接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、框架简介

  • 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连接:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值