我是靠谱客的博主 舒适心情,最近开发中收集的这篇文章主要介绍网络编程 1、简单服务器epoll实现,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

简单服务器

参考链接:https://github.com/aceld/libevent/blob/master/26-%E7%AE%80%E5%8D%95%E7%9A%84epoll%E6%9C%8D%E5%8A%A1%E5%99%A8.md

 编译及执行操作。

#编译及运行

gcc server.c -o server

./server

 代码带有注释,参考注释即可。

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <sys/epoll.h>


#define SERVER_PORT         (8080)
#define EPOLL_MAX_NUM       (2048)
#define BUFFER_MAX_LEN      (4096)

char buffer[BUFFER_MAX_LEN];

void str_toupper(char *str)
{
    int i;
    for (i = 0; i < strlen(str); i ++) {
        str[i] = toupper(str[i]);
    }
}

int main(int argc, char **argv)
{
    int listen_fd = 0;
    int client_fd = 0;
    /*
        struct sockaddr_in
        {
            sa_family_t    sin_ family;  // 地址族
            uint16_t       sin_port;     // 16位TCP/UDP端口号
            struct in_addr sin_addr;     // 32位IP地址
            char           sin_zero[8];  // 不使用
        }
    该结构体在#include<arpa/inet.h>中定义,主要解决sockaddr的缺陷,把
    端口号和地址分开存储
    */
    struct sockaddr_in server_addr;
    struct sockaddr_in client_addr;
    /*
        typedef int socketlen_t
    主要用于作为accept()的传参,记录struct sockaddr_un的长度
    */
    socklen_t           client_len;

    int epfd = 0;
    /*
        typedef union epoll_data
        {
            void*      ptr;
            int        fd;
            unit32_t   u32;
            unit64_t   u64
        } epoll_data_t;

        struct epoll_event
        {
            uint32_t     events;  // 明确表示事件类型,为EPOLLIN或EPOLLOUT等
            epoll_data_t data;    // 传参,比如传输socket句柄或者线程间通信
        }
    参考链接: https://www.dazhuanlan.com/2019/12/09/5dedf96a76470/
    */
    struct epoll_event event, *my_events;

    /*
        int                   // sockfd是socket描述符,唯一标识一个socket
        socket(  
            int protofamily,  // 协议族,有AF_INET(即IPV4),AF_INET6(即IPV6),AF_LOCAL,AF_ROUTE等
            int type,         // 指定socket类型,有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
            int protocol      // 协议类型,有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等
        )
    注意,type和protocol并不是可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。
    当protocol为0时,会自动选择type类型对应的默认协议
    */
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    server_addr.sin_family = AF_INET;
    /*
        long htonl(long value)
        {
          return ((value <<24 )|((value<<8)&0x00FF0000)|((value>>8)&0x0000FF00)|(value>>24));
        }
    该函数将小端转换成大端,把一个32位数从主机字节顺序转换成网络字节顺序
    */
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    /*
        uint16_t htons(uint16_t hostshort)
    把机器上的整数转换成“网络字节序”, 网络字节序是大端,也就是整数的高位字节存放在内存的低地址处
    */
    server_addr.sin_port = htons(SERVER_PORT);

    /*
        int                                  // 0-->成功,-1-->失败
        bind(
            int sockfd,                      // socket描述符
            const struct sockaddr *addr,     // 指向要绑定的协议地址
            socklen_t addrlen                // 对应的地址长度
        )
    */
    bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    /*
        int                                  // 0-->成功,-1-->失败
        listen(
            int sockfd,                      // socket描述符
            int backlog                      // 维护一个以该值作为上限的队列以跟踪完成的连接但服务器进程还没有接手处理或正在进行的连接
        )
    */
    listen(listen_fd, 10);

    /*
        int                                  // epoll文件描述符
        epoll_create (
            int __size                       // 申请内核空间,表示在该epoll文件描述符下可以关注的最大socketfd数目
        )
    */
    epfd = epoll_create(EPOLL_MAX_NUM);
    if (epfd < 0) {
        perror("epoll create");
        goto END;
    }

    /*
    events可以是以下几个宏的集合:
        EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
        EPOLLOUT:表示对应的文件描述符可以写;
        EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
        EPOLLERR:表示对应的文件描述符发生错误;
        EPOLLHUP:表示对应的文件描述符被挂断;
        EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
    */
    event.events = EPOLLIN;
    event.data.fd = listen_fd;
    /*
        #define EPOLL_CTL_ADD 1 // Add a file descriptor to the interface. 
        #define EPOLL_CTL_DEL 2 // Remove a file descriptor from the interface. 
        #define EPOLL_CTL_MOD 3 // Change file descriptor epoll_event structure. 

        int 
        epoll_ctl(
            int __epfd,                     // 由epoll_create生成的epoll专用的文件描述符
            int __op,                       // 进行的操作例如EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL
            int __fd,                       // 关联的socket文件描述符
            struct epoll_event *__event     // 记录监听的数据或者事件等等
        )
    */
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event) < 0) {
        perror("epoll ctl add listen_fd ");
        goto END;
    }

    my_events = malloc(sizeof(struct epoll_event) * EPOLL_MAX_NUM);
    
    while (1) {
        /*
            int                                 // 返回需要处理的事件数目,返回0表示已超时
            epoll_wait(
                int epfd,                       // epoll描述符
                struct epoll_event * events,    // 返回发生的事件
                int maxevents,                  // 一次返回的最大事件数目,通常与预分配的事件数组大小相等
                int timeout                     // 没有检测到事件发生时最多等待毫秒时间,若为0则立刻返回,若为-1则一直等待
            )
        等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
        并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则
        需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,
        因为socket fd并未清空,只是事件类型清空。这一步非常重要。
        */
        int active_fds_cnt = epoll_wait(epfd, my_events, EPOLL_MAX_NUM, -1);
        for (int i = 0; i < active_fds_cnt; i++) {
            // if fd == listen_fd
            if (my_events[i].data.fd == listen_fd) {
                /*
                    int                         // 返回客户端的socket描述符,包含其ip和port信息
                    accept(
                        int sockfd,             // socket描述符
                        struct sockaddr *addr,  // 客户端地址
                        socklen_t *addrlen      // 记录客户端地址长度
                    )
                */
                client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
                if (client_fd < 0) {
                    perror("accept");
                    continue;
                }

                char ip[20];
                /*
                htonl()-->"Host to Network Long"
                ntohl()-->"Network to Host Long"
                htons()-->"Host to Network Short"
                ntohs()-->"Network to Host Short"

                    const char*                 // 点分十进制ip地址字符串        
                    inet_ntop(
                        int family,             // AF_INET(ipv4)或AF_INET6(ipv6)
                        const void *addrptr,    // 数值格式的地址
                        char *strptr,           // 字符串指针
                        size_t len              // 字符串长度,以免溢出缓冲区。若不足以容纳结果,则返回空指针
                    )
                将数值格式转化为点分十进制的ip地址格式

                    uint16_t 
                    ntohs(
                        uint16_t netshort
                    )
                将一个无符号短整形数从网络字节顺序转换为主机字节顺序
                */
                printf("new connection[%s:%d]n", inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip)), ntohs(client_addr.sin_port));

                event.events = EPOLLIN | EPOLLET;
                event.data.fd = client_fd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event);
            }
            else if (my_events[i].events & EPOLLIN) {
                printf("EPOLLINn");
                client_fd = my_events[i].data.fd;

                buffer[0] = '';
                /*
                    ssize_t                     // 返回读取到的字节数,若为0,表示已经读取结束,小于0表示读取错误,如ECONNRESET时表示连接被对方closed
                    read(
                        int fd,                 // 文件描述符
                        void *buf,              // 接受的存储空间指针
                        size_t nbyte            // 一次读取的字节数
                    )
                在网络上传递数据时,一般把数据转换为char类型,接收的时候也是一样的的,没必要在网络上传递指针
                例如:
                        char buffer[sizeof(struct student)];
                        struct student* my_student;
                        read(sock,(void *)buffer,sizeof(struct student));
                        my_student=(struct student)buffer;
                */
                int n = read(client_fd, buffer, 5);
                if (n < 0) {
                    perror("read");
                    continue;
                }
                else if (n == 0) {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &event);
                    /*
                        int 
                        close(
                            int sockfd
                        )
                    close(fd)调用会将描述字的引用计数减1,只有当socket描述符的引用计数为0时,才关闭socket,即发送FIN包,
                    因此,在fork()模式中,父进程在accept()返回后,fork()子进程,由子进程处理connfd,而父进程将close(connfd);
                    由于connfd这个socket描述符的引用计数不为0,因此并不引发FIN,所以就没有关闭和客户端的连接。      
                    */
                    close(client_fd);
                }
                else {
                    printf("[read]: %sn", buffer);
                    buffer[n] = '';

                    str_toupper(buffer);
                    /*
                        ssize_t                 // 返回成功写的字节数
                        write(
                            int fd,             // 描述符
                            const void *buf,    // 发送的空间区指针
                            size_t nbytes       // 发送的字节数
                        )
                    
                    */
                    write(client_fd, buffer, strlen(buffer));
                    printf("[write]: %sn", buffer);
                    memset(buffer, 0, BUFFER_MAX_LEN);
                }
            }
            else if (my_events[i].events & EPOLLOUT) {
                printf("EPOLLOUTn");
            }
        }
    }
    


END:
    close(epfd);
    close(listen_fd);
    return 0;
}

 

最后

以上就是舒适心情为你收集整理的网络编程 1、简单服务器epoll实现的全部内容,希望文章能够帮你解决网络编程 1、简单服务器epoll实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部