smallchat改造

一、简介

smallchat是redis作者使用c开发的聊天服务器,实现了聊天室的功能。核心代码200多行,使用了IO多路复用技术。是一款适合学习IO多路复用的开源软件。原实现是使用select实现了IO多路复用。本文改造了原代码,改用epoll实现,并使用reacotor模型,使代码更精简,功能更聚合,方便使用和理解。

二、smallchat-server改造

server和client都使用了select,本文仅使用epoll改造server。smallchat-server改造如下:

/* smallchat.c -- Read clients input, send to all the other connected clients.
 *
 * Copyright (c) 2023, Salvatore Sanfilippo <antirez at gmail dot com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the project name of nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/select.h>
#include <unistd.h>
#include <sys/epoll.h>

#include "chatlib.h"

/* ============================ Data structures =================================
 * The minimal stuff we can afford to have. This example must be simple
 * even for people that don't know a lot of C.
 * =========================================================================== */

#define MAX_CLIENTS 1000 // This is actually the higher file descriptor.
#define SERVER_PORT 7711
#define MAX_EPOLL_EVENTS 200

/* This structure represents a connected client. There is very little
 * info about it: the socket descriptor and the nick name, if set, otherwise
 * the first byte of the nickname is set to 0 if not set.
 * The client can set its nickname with /nick <nickname> command. */
struct client {
    int fd;     // Client socket.
    char *nick; // Nickname of the client.
};

/* This global structure encapsulates the global state of the chat. */
struct chatState {
    int serversock;     // Listening server socket.
    int numclients;     // Number of connected clients right now.
    int maxclient;      // The greatest 'clients' slot populated.
    struct client *clients[MAX_CLIENTS]; // Clients are set in the corresponding
                                         // slot of their socket descriptor.
};

// reactor Data structures
typedef void (*CALLBACK) (int epollfd, int fd);
void recv_callback(int epollfd, int fd);
void accept_callback(int epollfd, int sockfd);

struct sockitem {
    int sockfd;
    CALLBACK callback;
};

struct chatState *Chat; // Initialized at startup.

/* ====================== Small chat core implementation ========================
 * Here the idea is very simple: we accept new connections, read what clients
 * write us and fan-out (that is, send-to-all) the message to everybody
 * with the exception of the sender. And that is, of course, the most
 * simple chat system ever possible.
 * =========================================================================== */

/* Create a new client bound to 'fd'. This is called when a new client
 * connects. As a side effect updates the global Chat state. */
struct client *createClient(int fd, int epollfd) {
    char nick[32]; // Used to create an initial nick for the user.
    int nicklen = snprintf(nick,sizeof(nick),"user:%d",fd);
    struct client *c = chatMalloc(sizeof(*c));
    socketSetNonBlockNoDelay(fd); // Pretend this will not fail.
    c->fd = fd;
    c->nick = chatMalloc(nicklen+1);
    memcpy(c->nick,nick,nicklen);
    assert(Chat->clients[c->fd] == NULL); // This should be available.
    Chat->clients[c->fd] = c;
    /* We need to update the max client set if needed. */
    if (c->fd > Chat->maxclient) Chat->maxclient = c->fd;
    Chat->numclients++;

    struct sockitem *si = (struct sockitem *)malloc(sizeof(struct sockitem));
    si->sockfd = fd;
    si->callback = recv_callback;
    struct epoll_event ep_event;
    ep_event.data.ptr = si;
    ep_event.events = EPOLLIN;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ep_event);
    return c;
}

/* Free a client, associated resources, and unbind it from the global
 * state in Chat. */
void freeClient(struct client *c, int epollfd) {
    free(c->nick);
    close(c->fd);
    Chat->clients[c->fd] = NULL;
    Chat->numclients--;
    if (Chat->maxclient == c->fd) {
        /* Ooops, this was the max client set. Let's find what is
         * the new highest slot used. */
        int j;
        for (j = Chat->maxclient-1; j >= 0; j--) {
            if (Chat->clients[j] != NULL) {
                Chat->maxclient = j;
                break;
            }
        }
        if (j == -1) Chat->maxclient = -1; // We no longer have clients.
    }
    free(c);
    epoll_ctl(epollfd, EPOLL_CTL_DEL, c->fd, NULL);
}

/* Allocate and init the global stuff. */
void initChat(void) {
    Chat = chatMalloc(sizeof(*Chat));
    memset(Chat,0,sizeof(*Chat));
    /* No clients at startup, of course. */
    Chat->maxclient = -1;
    Chat->numclients = 0;

    /* Create our listening socket, bound to the given port. This
     * is where our clients will connect. */
    Chat->serversock = createTCPServer(SERVER_PORT);
    if (Chat->serversock == -1) {
        perror("Creating listening socket");
        exit(1);
    }
}

/* Send the specified string to all connected clients but the one
 * having as socket descriptor 'excluded'. If you want to send something
 * to every client just set excluded to an impossible socket: -1. */
void sendMsgToAllClientsBut(int excluded, char *s, size_t len) {
    for (int j = 0; j <= Chat->maxclient; j++) {
        if (Chat->clients[j] == NULL ||
            Chat->clients[j]->fd == excluded) continue;

        /* Important: we don't do ANY BUFFERING. We just use the kernel
         * socket buffers. If the content does not fit, we don't care.
         * This is needed in order to keep this program simple. */
        write(Chat->clients[j]->fd,s,len);
    }
}

void accept_callback(int epollfd, int sockfd)
{
    int connfd = acceptClient(sockfd);
    struct client *c = createClient(connfd, epollfd);
    /* Send a welcome message. */
    char *welcome_msg =
        "Welcome to Simple Chat! "
        "Use /nick <nick> to set your nick.\n";
    write(c->fd,welcome_msg,strlen(welcome_msg));
    printf("Connected client fd=%d\n", connfd);
}

void recv_callback(int epollfd, int fd)
{
    char readbuf[256];
    int nread = read(fd,readbuf,sizeof(readbuf)-1);
    if (nread <= 0) {
        /* Error or short read means that the socket
            * was closed. */
        printf("Disconnected client fd=%d, nick=%s\n",
            fd, Chat->clients[fd]->nick);
        freeClient(Chat->clients[fd], epollfd);
    } else {
        /* The client sent us a message. We need to
            * relay this message to all the other clients
            * in the chat. */
        struct client *c = Chat->clients[fd];
        readbuf[nread] = 0;

        /* If the user message starts with "/", we
            * process it as a client command. So far
            * only the /nick <newnick> command is implemented. */
        if (readbuf[0] == '/') {
            /* Remove any trailing newline. */
            char *p;
            p = strchr(readbuf,'\r'); if (p) *p = 0;
            p = strchr(readbuf,'\n'); if (p) *p = 0;
            /* Check for an argument of the command, after
                * the space. */
            char *arg = strchr(readbuf,' ');
            if (arg) {
                *arg = 0; /* Terminate command name. */
                arg++; /* Argument is 1 byte after the space. */
            }

            if (!strcmp(readbuf,"/nick") && arg) {
                free(c->nick);
                int nicklen = strlen(arg);
                c->nick = chatMalloc(nicklen+1);
                memcpy(c->nick,arg,nicklen+1);
            } else {
                /* Unsupported command. Send an error. */
                char *errmsg = "Unsupported command\n";
                write(c->fd,errmsg,strlen(errmsg));
            }
        } else {
            /* Create a message to send everybody (and show
                * on the server console) in the form:
                *   nick> some message. */
            char msg[256];
            int msglen = snprintf(msg, sizeof(msg),
                "%s> %s", c->nick, readbuf);

            /* snprintf() return value may be larger than
                * sizeof(msg) in case there is no room for the
                * whole output. */
            if (msglen >= (int)sizeof(msg))
                msglen = sizeof(msg)-1;
            printf("%s",msg);

            /* Send it to all the other clients. */
            sendMsgToAllClientsBut(fd,msg,msglen);
        }
    }    
}


/* The main() function implements the main chat logic:
 * 1. Accept new clients connections if any.
 * 2. Check if any client sent us some new message.
 * 3. Send the message to all the other clients. */
int main(void) {
    initChat();

    int epollfd;
    struct epoll_event events[MAX_EPOLL_EVENTS];
    if ((epollfd = epoll_create1(0)) == -1) {
        perror("epoll_create1 error");
        exit(1);
    }

    struct sockitem *si = (struct sockitem *)malloc(sizeof(struct sockitem));
    si->sockfd = Chat->serversock;
    si->callback = accept_callback;
    struct epoll_event ep_event;
    ep_event.data.ptr = si;
    ep_event.events = EPOLLIN;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, Chat->serversock, &ep_event);

    int timeout = 1000;
    while(1) {
        int retval;    
        retval = epoll_wait(epollfd, events, MAX_EPOLL_EVENTS, timeout);
        if (retval == -1) {
            perror("epoll() error");
            close(Chat->serversock);
            close(epollfd);
            exit(1);
        } else if (retval) {
            for (int i = 0; i < retval; i++) {
                if (events[i].events & EPOLLIN) {
                    struct sockitem *si = (struct sockitem *)events[i].data.ptr;
                    si->callback(epollfd, si->sockfd);
                }
            }
        } else {
            /* Timeout occurred. We don't do anything right now, but in
             * general this section can be used to wakeup periodically
             * even if there is no clients activity. */
        }
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值