1.系统框架搭建
common.h<头文件定义>
sysutil<公有工具定义>
int tcp_server(const char *host, unsigned short port);
int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds);
miniftp<检测是否root启动,主进程完成客户端的连接,以及子进程的创建,开启会话>
//检测是否root启动,主进程完成客户端的连接,
//以及子进程的创建, 开启会话
#include"miniftp.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, 9188);
pid_t pid;
int conn;
struct sockaddr_in addrcli;
//接收客户端的连接,每当接受到一个新的连接,就创建一个进程
while(1)
{
//将一个已连接套接字返回给conn
if((conn=accept_timeout(listenfd, &addrcli, 0)) < 0)
ERR_EXIT("accept_timeout");
pid = fork();
//创建进程失败
if(pid == -1)
ERR_EXIT("fork");
//如果创建的进程是子进程,就开始创建会话
//ftp服务进程(子进程)
if(pid == 0)
{
close(listenfd);
sess.ctrl_fd = conn;
begin_session(&sess);
//开始会话,包含两个进程,nobody进程和ftp服务进程
}
//nobody进程(父进程)
else
{
//关闭已连接套接字
close(conn);
}
}
//关闭监听套接字
close(listenfd);
return 0;
}
session<会话结构的定义,实现会话函数,创建子进程,区分出ftp进程和nobody进程,实现ftp和nobody之间的通讯连接,更改nobody进程getpwnam>
//会话结构的定义,实现会话函数,创建子进程,
//区分出ftp进程和nobody进程,实现ftp与nobody之间的通讯连接
//更改nobody进程getpwnam
#include"session.h"
#include"ftpproto.h"
#include"privparent.h"
//会话函数,进行通信
void begin_session(session_t *sess)
{
int sockfds[2];
//函数原型:int socketpair(int domin, int type, int protocol, int socket_vector[2]);
//函数中四个参数说明:协议族、套接口的类型、使用的协议、指向存储文件描述符的指针
//第一个参数,表示协议族,只能为AF_LOCAL或者AF_UNIX。
//第二个参数,表示套接口的类型,可以是SOCK_STREAM(管道流)或者SOCK_DGRAM,这里的管道是双向的
//即每一端都可以读写。
//第三个参数表示协议,只能为0.
//第四个参数是接受两个套接口的整数数组,建立的两个套接字描述符会放在sockfds[0],sockfds[1]中。
//函数返回值:如果成功返回0,失败返回-1,并用errno来表示特定的错误号。
//功能:实现在同一个文件描述符中进行读写(全双工通信)
socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds);
pid_t pid;//定义一个进程ID变量
//创建进程,1则为父进程,0为子进程,失败则返回-1。
pid = fork();
if(pid == -1)
ERR_EXIT("fork");
if(pid == 0)
{
//ftp 服务进程(子进程)
close(sockfds[0]);
handle_child(sess);
}
else
{
//nobody 进程(父进程)
close(sockfds[1]);
handle_parent(sess);
}
}
sysutil
#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 sockaddr_in servaddr;//地址套接字变量
memset(&servaddr, 0, sizeof(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;//设置地址重复利用
if ((setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on))) < 0)
ERR_EXIT("setsockopt");
//绑定
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("bind");
//监听
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;
}
ftpproto<ftp进程>
priparent<nobody进程>