Socket封装之聊天程序(三)

  今天,完成一下epoll的封装。

类图

  首先,还是画下类图,初步设计一下。

  具体函数,我们下面详解。

epoll封装

EpollBase类

CEpollBase.h:

class CEpollBase
{
public:
    CEpollBase(int max_events);
    virtual ~CEpollBase();

    bool Create(int size);
    bool AddEvent(int fd,int events);
    bool ModEvent(int fd,int events);
    bool DelEvent(int fd,int events);
    bool Wait(int timeout = -1);    //监听一次事件的发生
    virtual void onEvent() = 0;
    void Start();
    void Stop();

protected:
    struct epoll_event *m_rlt_events;
    struct epoll_event m_event;
    int m_nEvent;
    int m_epfd;
private:

    int m_max_events;
    bool isRun;

};

  在之前,我们监听事件的发生并进行处理,是放在while(1)循环里的,但是,真正项目里是不能存在死循环的,所以,这里的Wait()就是用来监听一次事件的发生,通过标志isRun的值来决定是否一直监听(即Start(),Stop())。纯虚函数onEvent()用来处理发生的事件,意味着该类是不能实例化的,并且子类都要重写。

CEpollBase.cpp:

CEpollBase::CEpollBase( int max_events )
{
    m_max_events = max_events;
    m_rlt_events = new struct epoll_event[max_events];
    memset(m_rlt_events,0,max_events * sizeof(struct epoll_event));
    isRun = true;
}

CEpollBase::~CEpollBase()
{
    delete []m_rlt_events;
    m_rlt_events = NULL;
}

bool CEpollBase::Create( int size )
{
    m_epfd = epoll_create(size);
    if (m_epfd == -1)
    {
        return false;
    }
    return true;
}

bool CEpollBase::AddEvent( int fd,int events )
{
    m_event.data.fd = fd;
    m_event.events = events ;
    if ( epoll_ctl(m_epfd,EPOLL_CTL_ADD,fd,&m_event) == -1)
    {
        return false;
    }
    return true;
}

bool CEpollBase::ModEvent(int fd,int events)
{
    m_event.data.fd = fd;
    m_event.events = events ;
    if ( epoll_ctl(m_epfd,EPOLL_CTL_MOD,fd,&m_event) == -1)
    {
        return false;
    }
    return true;
}

bool CEpollBase::DelEvent( int fd,int events )
{
    m_event.data.fd = fd;
    m_event.events = events ;
    if ( epoll_ctl(m_epfd,EPOLL_CTL_DEL,fd,&m_event) == -1)
    {
        return false;
    }
    return true;
}

bool CEpollBase::Wait( int timeout /*= -1*/ )
{
    m_nEvent = epoll_wait(m_epfd,m_rlt_events,m_max_events,timeout);     //阻塞
    if(m_nEvent == -1)
    {
        perror("epoll_wait");
        return false;
    }
    else if(m_nEvent == 0)
    {
        printf("time out.");
        return true;
    }
    else
    {
        //有事件发生,立即处理
        onEvent();
    }
}   

void CEpollBase::Start()
{
    while(isRun)
    {
        Wait();
    }
}

void CEpollBase::Stop()
{
    isRun = false;
}

ServerEpoll类

  客户端类应该是作为epoll类的成员的,所以,我们需要编写一个通用的服务器处理事件的类,这个就是CServerEpoll类。
CServerEpoll.h:

class CServerEpoll:public CEpollBase
{
public:
    CServerEpoll(char *ip,unsigned short port,int max_events,int backlog,int size,int type);
    CServerEpoll(CAddress &addr,int max_events,int backlog,int size,int type);

    void onEvent();
    virtual void onChat(char acbuf[],int fd);

protected:
private:
    CTcpServer m_server;
    map<int,CTcpClient> m_clientMap;    //<fd,CTcpClient>存放客户端的地址信息
};

CServerEpoll.cpp:

CServerEpoll::CServerEpoll( char *ip,unsigned short port,int max_events,int backlog,int size,int type = tcp_sock)
:CEpollBase(max_events)
{
    m_server.SetAddr(ip,port);
    m_server.Socket(type);
    m_server.Bind();
    m_server.Listen(backlog);

    Create(size);
    AddEvent(m_server.GetFd(),EPOLLIN);
}

CServerEpoll::CServerEpoll( CAddress &addr ,int max_events,int backlog,int size,int type = tcp_sock)
:CEpollBase(max_events)
{
    m_server.SetAddr(addr);
    m_server.Socket(type);
    m_server.Bind();
    m_server.Listen(backlog);

    Create(size);
    AddEvent(m_server.GetFd(),EPOLLIN);
}

void CServerEpoll::onEvent()
{
    int ret;
    char acbuf[1024] = "";
    CTcpClient conn;

    //有事件发生,立即处理
    for(int i = 0; i < m_nEvent; i++)
    {
        //如果是 sockfd
        if( m_rlt_events[i].data.fd == m_server.GetFd() )
        {
            conn = m_server.Accept();
            //添加到事件集合
            AddEvent(conn.GetFd(),EPOLLIN);
            printf("client ip:%s ,port:%u connect.\n",conn.GetAddr().GetIP(),conn.GetAddr().GetPort());
            //添加到客户端链表当中
            m_clientMap.insert(pair<int,CTcpClient> (conn.GetFd(), conn));
        }
        else    //否则 connfd
        {
            ret = read(m_rlt_events[i].data.fd,acbuf,100);
            if( ret == 0) //客户端退出
            {
                close(m_rlt_events[i].data.fd);
                //从事件集合里删除
                DelEvent(m_rlt_events[i].data.fd, EPOLLIN);
                //从客户端链表中删除
                map<int,CTcpClient>::iterator it;
                for (it = m_clientMap.begin() ; it != m_clientMap.end(); it++)
                {
                    if (it->first == m_rlt_events[i].data.fd)
                    {
                        m_clientMap.erase(it->first);
                        break;
                    }
                }
                printf("client ip:%s ,port:%u disconnect.\n\n",it->second.GetAddr().GetIP(),it->second.GetAddr().GetPort());
            }
            else
            {
                onChat(acbuf,m_rlt_events[i].data.fd);
            }

        }

    }
}

void CServerEpoll::onChat( char acbuf[],int fd )
{
    //做解包的处理
    write(fd,acbuf,1024);

}

  这里的onData()可以是纯虚函数,也可以是虚函数,看自己设计。这个函数是针对不同项目服务器不同处理数据的。在这个项目中,我们还需要一个类:ChatServerEpoll类,该项目中用来处理聊天事件的服务器程序,也就是在这个类中,我们重写onData(),在该函数中处理:登录、聊天、用户列表等等的功能。

ChatServerEpoll类

CChatServerEpoll.h:

class CChatServerEpoll:public CServerEpoll
{
public:
    CChatServerEpoll(char *ip,unsigned short port,int max_events,int backlog,int size,int type);
    CChatServerEpoll(CAddress &addr,int max_events,int backlog,int size,int type);

    void onChat(char acbuf[],int fd);
protected:
private:
    map<string,int> m_userMap;  //<用户名,文件描述符>
};

CChatServerEpoll.cpp:

CChatServerEpoll::CChatServerEpoll( char *ip,unsigned short port,int max_events,int backlog,int size,int type = tcp_sock)
:CServerEpoll(ip,port,max_events,backlog,size,type)
{

}

CChatServerEpoll::CChatServerEpoll( CAddress &addr,int max_events,int backlog,int size,int type = tcp_sock)
:CServerEpoll(addr,max_events,backlog,size,type)
{

}

void CChatServerEpoll::onChat( char acbuf[],int fd )
{
    PK_HEAD head = {0};     //包头
    PK_LOGIN login ={0};    //登录包
    PK_CHAT chat = {0};     //聊天包
    int reply;              //登录应答包。 1-成功 0-失败
    PK_USERLIST userlist = {0}; //用户列表包

    map<string,int>::iterator it;

    //解包
    memset(&head,0,sizeof(head));
    memcpy(&head,acbuf,HEAD_SIZE);

    for(int i = 0; i < m_nEvent; i++)
    {
        switch(head.type)
        {
        case 1: //登录或退出
            memset(&login,0,sizeof(login));
            memcpy(&login,acbuf + HEAD_SIZE,LOGIN_SIZE);

            if(login.isOnline)
            {
                //通过connfd区分不同客户端
                reply = LOGIN_OK;
                memcpy(acbuf + HEAD_SIZE , &reply , 4);
                write(m_rlt_events[i].data.fd,acbuf,HEAD_SIZE + 4); //登录成功应答包
                m_userMap.insert(pair<string,int>(login.name,m_rlt_events[i].data.fd));
                printf("client %s login.\n\n",login.name);
            }
            else
            {
                m_userMap.erase(login.name);
                printf("client %s exit.\n",login.name);
            }

            break;

        case 2:     //聊天
            memset(&chat,0,CHAT_SIZE);
            memcpy(&chat,acbuf + HEAD_SIZE,CHAT_SIZE);
            if(strcmp(chat.toName,"all") == 0)
            {
                //群聊
                for (it = m_userMap.begin(); it != m_userMap.end(); it++)
                {
                    //转发消息
                    if (it->second != m_rlt_events[i].data.fd)
                    {
                        write(it->second, acbuf, HEAD_SIZE + CHAT_SIZE);
                    }

                }
            }
            else
            {
                //私聊
                if ( (it = m_userMap.find(chat.toName)) != m_userMap.end()) //找到了
                {
                    //转发消息
                    write(it->second, acbuf, HEAD_SIZE + CHAT_SIZE);
                }
                else    //用户不存在
                {
                    memset(&chat.msg,0,100);
                    strcpy(chat.msg,"the acccount is not exist.");
                    memcpy(acbuf + HEAD_SIZE, &chat, CHAT_SIZE);
                    write(m_rlt_events[i].data.fd, acbuf, HEAD_SIZE + CHAT_SIZE);
                }
            }
            break;

        case 3:
            memset(&userlist,0,USERLIST_SIZE);
            int j = 0;
            for (it = m_userMap.begin();j<MAX_USERS && it!= m_userMap.end(); it++,j++)
            {
                strncpy(userlist.userName[10*j],it->first.c_str(),10);
            }

            memcpy(acbuf + HEAD_SIZE, &userlist, USERLIST_SIZE);
            write(m_rlt_events[i].data.fd, acbuf, HEAD_SIZE + USERLIST_SIZE);

            break;

        }
    }

}

功能测试

  编写好类之后,我们就可以修改之前的服务器端的代码了,客户端不变。
server.cpp:

#include "common.h"
#include "ChatServerEpoll.h"

#define MAX_LISTEN_SIZE 10
#define MAX_EPOLL_SIZE 1000
#define MAX_EVENTS 20

int main()
{
    char ip[20] = "192.168.159.6";
    unsigned short port = 1234;
    SOCKET_TYPE type = tcp_sock;
    CChatServerEpoll ser_epoll(ip,port,MAX_EVENTS,MAX_LISTEN_SIZE,MAX_EPOLL_SIZE,type);
    ser_epoll.Start();

    return 0;
}

  是不是简化很多了。
  到这里为止,代码已经基本完善。编译的时候,可以分两个文件夹。完整的代码已经上传。

原文地址:http://blog.51cto.com/13097817/2068333

时间: 2024-10-14 18:36:13

Socket封装之聊天程序(三)的相关文章

Socket封装之聊天程序(二)

今天,学习一下socket的封装. 类图 ??首先,我们把需要封装的各个类初步的设计如下: ??接下来,我们建立类与类之间的关系:??其中,CStream类可有可无,这个类是用来封装各种读写流的. socket封装 stream类 stream.h: class CStream { public: CStream(int fd = -1); ~CStream(); void SetFd(int fd); int GetFd(); int Read(char *buf, int count); /

Socket网络编程--聊天程序(9)

这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的.这一节我们将讲多服务器问题(高大上的说法就是负载问题了.)至于聊天程序的文件发送(也即二进制文件发送例如图片)和单点登陆(就是多加一个数组fd_L[],用来记录是否已经登陆过了.),这些问题就不讨论了. 支持多服务器实现负载问题的聊天程序 今天才知道原来我们一直使用的select来处理IO多路复用的这个函数最多只能有1024个连接,因为内部实现里面的数组就是只有1024,多了不行.

Socket网络编程--聊天程序(8)

上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能.就是通过服务器对客户端的数据进行转发到特定的用户上, 实现私聊功能的聊天程序 实现的技术细节是:对客户端发送的数据增加一个标识头,由于我们处理的是纯文本,所以为了讲解的方便就把标识头加到聊天信息的前面,然后在服务器中判断.如果是要在做成产品的话,因为要考虑传送纯文本,图片,文件,特定的结构体等等其他非纯文本信息,那么我们可以对一次聊天信息,发送两次数据,第一次用TCP发送一个结构体

Socket网络编程--聊天程序(5)

上一小节我们讲了使用select来避免使用多进程的资源浪费问题.上次只是实现了从多个客户端发送数据给服务器端,接下来就要实现从服务器端发送数据给各个服务器. 使用select多路转换处理聊天程序2 client.c 使用上一节用的那个,在那个基础上修改下面几句 66 //send-recv 一些返回指没有判断,具体可以看server.c 67 if((pid=fork())<0) 68 { 69 perror("fork error\n"); 70 } 71 else if(pi

Socket网络编程--聊天程序(6)

这一小节将增加一个用户的结构体,用于保存用户的用户名和密码,然后发给服务器,然后在服务器进行判断验证.这里就有一个问题,以前讲的就是发送字符串是使用char类型进行传输,然后在服务器进行用同样是字符串进行接收.然而作为一个结构体是不是也可以呢?如果有看send或recv的函数定义就知道第二个参数是void *类型,也就是说这两个函数对传入的类型其实是不做要求的,只是要你传输个地址,然后后面接一个大小就可以了.就是要send在这个地址取值,去大小为size个,然后传输.学过TCP/IP就知道,我们

Socket网络编程--聊天程序(7)

接上一小节,本来是计划这一节用来讲数据库的增删改查,但是在实现的过程中,出现了一点小问题,也不是技术的问题,就是在字符界面上比较不好操作.比如要注册一个帐号,就需要弄个字符界面提示,然后输入数字表示选择,在依次输入信息.(这一点,用C写过什么管理系统就知道,很是麻烦.)考虑到本程序讲的是网络编程,就不弄增删改查了,就判断一个用户名密码是否正确就行了. 首先是安装mysql和创建数据库,所用到的命令如下: 1 su root //使用root用户 2 yum install mysql-devel

Socket网络编程--聊天程序(4)

上一小节讲到可以实现多客户端与服务器进行通讯,对于每一个客户端的连接请求,服务器都要分配一个进程进行处理.对于多用户连接时,服务器会受不了的,而且还很消耗资源.据说有个select函数可以用,好像还很NB的样子. 使用select多路转换处理聊天程序 下面摘取APUE 14.5小结 I/O多路转接 当从一个描述符读,然后又写到另一个描述符时,可以在下列形式的循环中循环中使用阻塞I/O: while((n = read(STDIN_FILENO, buf, BUFFSIZ))>0) if(writ

Node.js + Web Socket 打造即时聊天程序嗨聊

前端一直是一块充满惊喜的土地,不仅是那些富有创造性的页面,还有那些惊赞的效果及不断推出的新技术.像node.js这样的后端开拓者直接将前端人员的能力扩大到了后端.瞬间就有了一统天下的感觉,来往穿梭于前后端之间代码敲得飞起,从此由前端晋升为'前后端'. 图片来自G+ 本文将使用Node.js加web socket协议打造一个网页即时聊天程序,取名为HiChat,中文翻过来就是'嗨聊',听中文名有点像是专为寂寞单身男女打造的~ 其中将会使用到express和socket.io两个包模块,下面会有介绍

Socket网络编程--聊天程序(3)

上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数据,和服务器向多个客户端发送数据. 多对一,单向,各个客户端都可以向服务器发送数据 close函数 #include <unistd.h> int close(int sockfd); //用于关闭所打开的Socket套接字 返回值:如果为0表示成功,-1表示失败 处理的办法是每一个客户端连接到服