• Linux网络编程综合运用之MiniFtp实现(四)


    从今天开始,正式进入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[100] = {0};
        if (gethostname(host, sizeof(host)) < 0)
            return -1;
        struct hostent *hp;
        if ((hp = gethostbyname(host)) == NULL)
        return -1;
    
        strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr));
        return 0;
    }
    
    
    /**
     * activate_noblock - 设置I/O为非阻塞模式
     * @fd: 文件描符符
     */
    void activate_nonblock(int fd)
    {
        int ret;
        int flags = fcntl(fd, F_GETFL);
        if (flags == -1)
            ERR_EXIT("fcntl");
    
        flags |= O_NONBLOCK;
        ret = fcntl(fd, F_SETFL, flags);
        if (ret == -1)
            ERR_EXIT("fcntl");
    }
    
    /**
     * deactivate_nonblock - 设置I/O为阻塞模式
     * @fd: 文件描符符
     */
    void deactivate_nonblock(int fd)
    {
        int ret;
        int flags = fcntl(fd, F_GETFL);
        if (flags == -1)
            ERR_EXIT("fcntl");
    
        flags &= ~O_NONBLOCK;
        ret = fcntl(fd, F_SETFL, flags);
        if (ret == -1)
            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 = 0;
        if (wait_seconds > 0)
        {
            fd_set read_fdset;
            struct timeval timeout;
    
            FD_ZERO(&read_fdset);
            FD_SET(fd, &read_fdset);
    
            timeout.tv_sec = wait_seconds;
            timeout.tv_usec = 0;
            do
            {
                ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout);
            } while (ret < 0 && errno == EINTR);
    
            if (ret == 0)
            {
                ret = -1;
                errno = ETIMEDOUT;
            }
            else if (ret == 1)
                ret = 0;
        }
    
        return ret;
    }
    
    /**
     * write_timeout - 读超时检测函数,不含写操作
     * @fd: 文件描述符
     * @wait_seconds: 等待超时秒数,如果为0表示不检测超时
     * 成功(未超时)返回0,失败返回-1,超时返回-1并且errno = ETIMEDOUT
     */
    int write_timeout(int fd, unsigned int wait_seconds)
    {
        int ret = 0;
        if (wait_seconds > 0)
        {
            fd_set write_fdset;
            struct timeval timeout;
    
            FD_ZERO(&write_fdset);
            FD_SET(fd, &write_fdset);
    
            timeout.tv_sec = wait_seconds;
            timeout.tv_usec = 0;
            do
            {
                ret = select(fd + 1, NULL, NULL, &write_fdset, &timeout);
            } while (ret < 0 && errno == EINTR);
    
            if (ret == 0)
            {
                ret = -1;
                errno = ETIMEDOUT;
            }
            else if (ret == 1)
                ret = 0;
        }
    
        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 > 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);
    /*    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 > 0)
            activate_nonblock(fd);
    
        ret = connect(fd, (struct sockaddr*)addr, addrlen);
        if (ret < 0 && errno == EINPROGRESS)
        {
            printf("AAAAA
    ");
            fd_set connect_fdset;
            struct timeval timeout;
            FD_ZERO(&connect_fdset);
            FD_SET(fd, &connect_fdset);
            timeout.tv_sec = wait_seconds;
            timeout.tv_usec = 0;
            do
            {
                /* 一量连接建立,套接字就可写 */
                ret = select(fd + 1, NULL, &connect_fdset, NULL, &timeout);
            } while (ret < 0 && errno == EINTR);
            if (ret == 0)
            {
                ret = -1;
                errno = ETIMEDOUT;
            }
            else if (ret < 0)
                return -1;
            else if (ret == 1)
            {
                printf("BBBBB
    ");
                /* ret返回为1,可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/
                /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */
                int err;
                socklen_t socklen = sizeof(err);
                int sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
                if (sockoptret == -1)
                {
                    return -1;
                }
                if (err == 0)
                {
                    printf("DDDDDDD
    ");
                    ret = 0;
                }
                else
                {
                    printf("CCCCCC
    ");
                    errno = err;
                    ret = -1;
                }
            }
        }
        if (wait_seconds > 0)
        {
            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 > 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;
    }
    
    /**
     * 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;
        }
    }
    
    /**
     * 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] == '
    ')
                {
                    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;
    }
    
    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 = 0;
        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 = 0;
        msg.msg_iov = &vec;
        msg.msg_iovlen = 1;
        msg.msg_flags = 0;
    
        vec.iov_base = &sendchar;
        vec.iov_len = sizeof(sendchar);
        ret = sendmsg(sock_fd, &msg, 0);
        if (ret != 1)
            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 = 0;
        msg.msg_iov = &vec;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsgbuf;
        msg.msg_controllen = sizeof(cmsgbuf);
        msg.msg_flags = 0;
    
        p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));
        *p_fd = -1;  
        ret = recvmsg(sock_fd, &msg, 0);
        if (ret != 1)
            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 == -1)
            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 (0)
    
    #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, 0)) < 0)
            ERR_EXIT("tcp_server");
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        if (host != NULL)
        {
            if (inet_aton(host, &servaddr.sin_addr) == 0)
            {//证明传过来的是主机名而不是点分十进制的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, 0)) < 0)
            ERR_EXIT("tcp_server");
    
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        if (host != NULL)
        {
            if (inet_aton(host, &servaddr.sin_addr) == 0)
            {//证明传过来的是主机名而不是点分十进制的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");
    
        //监听
        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的一个基本框架,下次继续。

  • 相关阅读:
    mvc session验证
    mvc登录验证
    PHP中return的用法
    mvc框架类
    php mvc实现比赛列表
    php MySQLDB类
    php header的几种用法
    php isset()与empty()的使用
    jenkins+springboot+svn linux 自动化部署
    基于netty的websocket例子
  • 原文地址:https://www.cnblogs.com/webor2006/p/4438704.html
Copyright © 2020-2023  润新知