0%

Linux下使用epoll的C语言服务端

Linux下使用epoll的C语言服务端

  • 该服务器使用了epoll处理多个同时链接的客户端的事件。
  • 流程为开始使用epoll_create创建一个epoll的Fd,然后将以服务器socket为基础的事件使用epoll_ctl将其添加处理。
  • 然后整个程序进入无限循环,调用epoll_wait等待事件,然后获取到事件列表之后,使用for循环逐个处理,此处仅仅处理收取数据的事件,实际上常见的事件为EPOLLINEPOLLETEPOLLOUT,三者的详细介绍在此不详细解释。
    #include <stdio.h>
    #include <stdlib.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <net/if.h>
    #include <sys/ioctl.h>

    // #include <pthread.h>
    #include <signal.h>

    #include <sys/epoll.h>
    #include <unordered_map>

    // using namespace std;

    #define SERVERPORT 8080

    int sockfd;

    /*linux上支持(Android上也支持), 此函数不仅能获取IP,还可以获取MAC地址、掩码和广播地址等*/
    int get_local_ip_using_ifconf(char *str_ip)
    {
    int sock_fd, intrface;
    struct ifreq buf[INET_ADDRSTRLEN];
    struct ifconf ifc;
    char *local_ip = NULL;
    int status = -1;

    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)
    {
    ifc.ifc_len = sizeof(buf);
    ifc.ifc_buf = (caddr_t)buf;
    if (!ioctl(sock_fd, SIOCGIFCONF, (char *)&ifc))
    {
    intrface = ifc.ifc_len/sizeof(struct ifreq);
    while (intrface-- > 0)
    {
    if (!(ioctl(sock_fd, SIOCGIFADDR, (char *)&buf[intrface])))
    {
    local_ip = NULL;
    local_ip = inet_ntoa(((struct sockaddr_in*)(&buf[intrface].ifr_addr))->sin_addr);
    if(local_ip)
    {
    strcpy(str_ip, local_ip);
    status = 0;
    if(strcmp("127.0.0.1", str_ip))
    {
    break;
    }
    }

    }
    }
    }
    close(sock_fd);
    }
    return status;
    }

    typedef struct clientString
    {
    char* ipAdd;
    int port;
    } clientStruct;


    void SIGINTHandler(int sig)
    {
    close(sockfd);
    printf("\nServer stopping, already closed socket %d. \n", sockfd);
    exit(EXIT_SUCCESS);
    }

    int main()
    {
    struct sockaddr_in server_addr = {0};
    struct sockaddr_in client_addr = {0};
    char ip_str[20] = {0};

    int addrlen = sizeof(client_addr);
    int ret;
    char localIPBuf[20];

    signal(SIGINT, (sig_t)SIGINTHandler);

    /* 打开套接字,得到套接字描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > sockfd)
    {
    perror("socket error");
    exit(EXIT_FAILURE);
    }

    /* 将套接字与指定端口号进行绑定 */
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(SERVERPORT);

    char buf[20];
    get_local_ip_using_ifconf(buf);
    puts(buf);


    inet_ntop(AF_INET, &(server_addr.sin_addr), localIPBuf, sizeof(localIPBuf));
    // puts(localIPBuf);

    ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (0 > ret)
    {
    perror("bind error");
    close(sockfd);
    exit(EXIT_FAILURE);
    }
    /* 使服务器进入监听状态 */
    ret = listen(sockfd, 50);
    if (0 > ret)
    {
    perror("listen error");
    close(sockfd);
    exit(EXIT_FAILURE);
    }
    /* 阻塞等待客户端连接 */
    int epfd, connfd;
    epfd = epoll_create(256);
    struct epoll_event ev, events[10];
    ev.data.fd = sockfd;
    ev.events = EPOLLIN|EPOLLET|EPOLLOUT;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    int nOfEvent = 0;
    char *ipStr;
    int clientFd;
    int n;
    char line[256];
    std::unordered_map<int, clientStruct> m;
    while(1)
    {
    nOfEvent = epoll_wait(epfd, events, 10, -1);
    for(int i = 0;i<nOfEvent;++i)
    {
    if(events[i].data.fd == sockfd)
    {
    clientStruct clientTemp;
    connfd = accept(sockfd, (sockaddr* )&client_addr, (socklen_t*)&addrlen);
    if(connfd<0)
    {
    printf("Error: Accept Failure!\n");
    exit(1);
    }
    ipStr = inet_ntoa(client_addr.sin_addr);
    printf("client: %s\n", ipStr);
    //将客户端socket与客户端IP的映射关系存入map
    clientTemp.ipAdd = ipStr;
    clientTemp.port = ntohs(client_addr.sin_port);
    m.insert(std::pair<int, clientStruct>(connfd, clientTemp));
    ev.data.fd = connfd;
    ev.events = EPOLLIN|EPOLLET;
    //修改epoll
    epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
    }
    else if (events[i].events & EPOLLIN)
    {
    printf("EPOLLIN!\n");
    if((clientFd = events[i].data.fd)<0)continue;
    if((n = read(clientFd, line, 256))<=0)
    {
    close(clientFd);
    printf("Client Error!\n");
    //从映射关系中抹除之前断开的socket
    m.erase(connfd);
    }
    else
    {
    line[n-1] = '!';
    line[n] = 0;
    printf("Got message from: %s:", m.count(connfd)>0?m[connfd].ipAdd:"Unkown Client");
    printf("%4d\n", m.count(connfd)>0?m[connfd].port:0);
    printf("data length = %d\n", strlen(line));
    //回显
    write(clientFd, line, strlen(line)+1);
    memset(line, 0, sizeof(line));
    }

    ev.data.fd = clientFd;
    ev.events = EPOLLIN|EPOLLET;
    // epoll_ctl(epfd, EPOLL_CTL_MOD, clientFd, &ev);
    }
    /*
    else if(events[i].events&EPOLLOUT)
    {
    printf("Sent!");
    clientFd = events[i].data.fd;
    // write(clientFd, line, strlen(line));

    ev.data.fd = clientFd;
    ev.events = EPOLLOUT|EPOLLIN|EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_MOD, clientFd, &ev);
    }*/

    }
    }


    /* 关闭套接字 */
    close(sockfd);
    exit(EXIT_SUCCESS);
    }
  • 上述服务器提供了一个基于C++unordered_map实现的映射,将每次接收到的事件的fd映射到一个存储了该客户端socket对应的IP地址和端口号的结构体上,服务器将其读出即可获取此客户端的相关信息。映射关系是在该客户端初次连接到服务器的时候创建的,并且会在连接关闭或者出现异常的时候从映射中移除。
  • 下面为客户端
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdlib.h>

    #define SERVER_PORT 8080
    #define SERVER_IP "0.0.0.0"
    int sockFd = 0;
    void SIGINTHandler(int sig)
    {
    close(sockFd);
    printf("\nClient stopping, already closed socket %d. \n", sockFd);
    exit(EXIT_SUCCESS);
    }

    int main()
    {

    int ret;
    struct sockaddr_in sock_server_addr;
    char sendBuf[100] = {'\0'};
    char readBuf[2] = {0};
    int send_len;

    sockFd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockFd <0)
    {
    printf("Open socket Error!\n");
    exit(EXIT_FAILURE);
    }
    sock_server_addr.sin_family = AF_INET;
    sock_server_addr.sin_port = htons(SERVER_PORT);

    inet_aton(SERVER_IP, &sock_server_addr.sin_addr);
    memset(sock_server_addr.sin_zero, 0, 8);
    printf("Client started, trying to connect %s:%4d. \n", SERVER_IP, SERVER_PORT);
    ret = connect(sockFd, (struct sockaddr*)&sock_server_addr, sizeof(struct sockaddr));
    printf("Client Connected!\n");
    if(ret<0)
    {
    printf("connect Error!\n");
    exit(EXIT_FAILURE);
    }
    while(1)
    {
    scanf("%s", (char *)&sendBuf);
    write(sockFd, sendBuf, strlen(sendBuf)+1);
    int n = 0;
    n = read(sockFd, readBuf, 1);
    puts("Server Repeat: ");
    // printf(readBuf);
    while (readBuf[0]!=0&&n>0)
    {
    // puts(":");
    printf(readBuf);
    n = read(sockFd, readBuf, 1);
    }
    printf("\n");

    memset(sendBuf, sizeof(sendBuf), 0);
    memset(readBuf, sizeof(readBuf), 0);
    }
    }