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);     //阻塞读
    int Read(char *buf, int count, int sec);    //超时读
//  int Read(char *buf, int count, CAddress &addr);     //UDP读
    bool Write(char *buf,int count);    //普通写

private:
    int m_fd;
};

stream.cpp:

include "stream.h"

CStream::CStream( int fd /*= -1*/ )
{
    this->m_fd = fd;
}

CStream::~CStream()
{

}

void CStream::SetFd( int fd )
{
    this->m_fd = fd;
}

int CStream::GetFd()
{
    return this->m_fd;
}

int CStream::Read( char *buf, int count )
{
    int nByte;
    nByte = read(m_fd,buf,count);
    if (nByte == -1)
    {
        perror("read");
    }
    return nByte;
}

int CStream::Read( char *buf, int count, int sec )
{
    //用select
    fd_set set;
    int nEvent;
    struct timeval time = {0};

    FD_ZERO(&set);
    FD_SET(m_fd,&set);

    time.tv_sec = sec;
    nEvent = select(m_fd + 1, &set, NULL, NULL, &time);

    if (nEvent == -1)
    {
        perror("select");
    }
    else if (nEvent == 0)
    {
        printf("time out.\n");
    }
    else
    {
        return Read(buf,count);
    }
    return nEvent;
}

bool CStream::Write( char *buf,int count )
{
    int nByte;
    nByte = write(m_fd,buf,count);
    if (nByte == -1)
    {
        perror("write");
        return false;
    }
    return true;
}

socket基类

SocketBase.h:

typedef enum socket_type
{
    tcp_sock,
    udp_sock
}SOCKET_TYPE;

class CSocketBase
{
public:
    CSocketBase(int fd = -1);
    CSocketBase(char *ip, unsigned short port, int fd);

    void SetFd(int fd);
    int GetFd();
    void SetAddr(char *ip, unsigned short port);
    void SetAddr(CAddress &addr);
    CAddress GetAddr();
    bool Socket(int type = tcp_sock);
    bool Bind();
    virtual int Read(char *buf,int count);
    virtual bool Write(char *buf,int count);
    bool Close();

protected:
    int m_fd;
    CAddress m_addr;
    CStream m_stream;
private:
};

SocketBase.cpp:

CSocketBase::CSocketBase( int fd /*= -1*/ )
{
    m_fd = fd;
}

CSocketBase::CSocketBase( char *ip, unsigned short port, int fd )
{
    m_addr.SetIP(ip);
    m_addr.SetPort(port);
    m_fd = fd;
}

void CSocketBase::SetFd( int fd )
{
    m_fd = fd;
}

int CSocketBase::GetFd()
{
    return m_fd;
}

void CSocketBase::SetAddr( char *ip, unsigned short port )
{
    m_addr.SetIP(ip);
    m_addr.SetPort(port);
}

void CSocketBase::SetAddr( CAddress &addr )
{
    m_addr.SetIP(addr.GetIP());
    m_addr.SetPort(addr.GetPort());
}

CAddress CSocketBase::GetAddr()
{
    return m_addr;
}

bool CSocketBase::Socket( int type /*= TCP_SOCK*/ )
{
    if (type == tcp_sock)
    {
        m_fd = socket(PF_INET, SOCK_STREAM ,0);
    }
    else
    {
        m_fd = socket(PF_INET, SOCK_DGRAM ,0);
    }

    if (m_fd == -1)
    {
        perror("socket");
        return false;
    }
    m_stream.SetFd(m_fd);
    return true;
}

bool CSocketBase::Bind()
{
    int ret = bind(m_fd,m_addr.GetAddr(),m_addr.GetAddrSize());
    if (ret == -1)
    {
        perror("bind");
        return false;
    }
    return true;
}

int CSocketBase::Read( char *buf,int count )
{

}

bool CSocketBase::Write( char *buf,int count )
{

}

bool CSocketBase::Close()
{
    if (close(this->m_fd) == -1)
    {
        return false;
    }
    return true;
}

TcpSocket类

TcpSocket.h:

class CTcpServer:public CTcpSocket
{
public:
    CTcpServer(char *ip,unsigned short port,int fd);

    bool Listen(int backlog);
    CTcpClient Accept();

protected:
private:
};

TcpSocket.cpp:

CTcpSocket::CTcpSocket( int fd /*= -1*/ )
:CSocketBase(fd)
{

}

CTcpSocket::CTcpSocket( char *ip,unsigned short port, int fd)
:CSocketBase(ip,port,fd)
{

}

int CTcpSocket::Read( char *buf, int count )
{
    return m_stream.Read(buf,count);
}

bool CTcpSocket::Write( char *buf, int count )
{
    return m_stream.Write(buf,count);
}

客户端、服务器封装

TcpServer类

TcpServer.h:

class CTcpServer:public CTcpSocket
{
public:
    CTcpServer(char *ip,unsigned short port,int fd);

    bool Listen(int backlog);
    CTcpClient Accept();

protected:
private:
};

TcpServer.cpp:

CTcpServer::CTcpServer( char *ip,unsigned short port, int fd)
:CTcpSocket(ip,port,fd)
{

}

bool CTcpServer::Listen( int backlog )
{
    int ret = listen(m_fd,backlog);
    if(ret == -1)
    {
        perror("listen");
        return false;
    }
    return true;
}

CTcpClient CTcpServer::Accept()
{
    CTcpClient client;
    int conn_fd;
    CAddress conn_addr;
    conn_fd = accept(m_fd,conn_addr.GetAddr(),conn_addr.GetAddrSizePtr());
    if (conn_fd == -1)
    {
        perror("accept");
        //抛出异常

    }
    client.SetFd(conn_fd);
    client.SetAddr(conn_addr);

    return client;  //记得重载拷贝构造??深拷贝
}

TcpClient类

TcpClient.h:

class CTcpClient:public CTcpSocket
{
public:
    CTcpClient();
    CTcpClient(char *ip,unsigned short port,int fd);
    bool Connect(CAddress &ser_addr);
protected:
private:
};

TcpClient.cpp:

CTcpClient::CTcpClient()
:CTcpSocket()
{

}

CTcpClient::CTcpClient( char *ip,unsigned short port,int fd )
:CTcpSocket(ip,port,fd)
{

}

bool CTcpClient::Connect( CAddress &ser_addr )
{
    int ret = connect(m_fd,ser_addr.GetAddr(),ser_addr.GetAddrSize());
    if(ret == -1)
    {
        perror("connect");
        return false;
    }
    return true;
}

主函数

??接下来,只要修改我们之前的server.cpp和client.cpp就可以了。
servercpp:

#include "common.h"

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

int main()
{
    int sockfd;
    int connfd;

    int reuse = 0;
    int epfd;
    int nEvent = 0;
    struct epoll_event event = {0};
    struct epoll_event rtlEvents[MAX_EVENTS] = {0};
    char acbuf[100] = "";
    int ret;

    PK_HEAD head = {0};     //包头
    PK_LOGIN login ={0};    //登录包
    PK_CHAT chat = {0};     //聊天包
    int reply;              //登录应答包。 1-成功 0-失败

    //1.socket()
    char ip[20] = "192.168.159.6";
    unsigned short port = 1234;
    CTcpServer server(ip,port,sockfd);
    SOCKET_TYPE type = tcp_sock;
    server.Socket(type);

    //2.bind()
    server.Bind();

    //3.listen()
    server.Listen(MAX_LISTEN_SIZE);

    //4.epoll初始化
    epfd = epoll_create(MAX_EPOLL_SIZE);    //创建
    event.data.fd = server.GetFd();
    event.events = EPOLLIN ;
    epoll_ctl(epfd,EPOLL_CTL_ADD,server.GetFd(),&event);    //添加sockfd

    CTcpClient conn;
    //5.通信
    while(1)
    {
        nEvent = epoll_wait(epfd,rtlEvents,MAX_EVENTS,-1);   //阻塞
        if(nEvent == -1)
        {
            perror("epoll_wait");
            return -1;
        }
        else if(nEvent == 0)
        {
            printf("time out.");
        }
        else
        {
            //有事件发生,立即处理
            for(int i = 0; i < nEvent; i++)
            {
                //如果是 sockfd
                if( rtlEvents[i].data.fd == server.GetFd() )
                {
                    conn = server.Accept();
                    //添加到事件集合
                    event.data.fd = conn.GetFd();
                    event.events = EPOLLIN;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,conn.GetFd(),&event);
                    printf("client ip:%s ,port:%u connect.\n",conn.GetAddr().GetIP(),conn.GetAddr().GetPort());
                }
                else    //否则 connfd
                {
                    ret = read(rtlEvents[i].data.fd,acbuf,100);
                    if( ret == 0) //客户端退出
                    {
                        close(rtlEvents[i].data.fd);
                        //从集合里删除
                        epoll_ctl(epfd,EPOLL_CTL_DEL,rtlEvents[i].data.fd,rtlEvents);
                        //从用户列表删除
                        string username;
                        for (it = userMap.begin(); it != userMap.end(); it++)
                        {
                            if (it->second == rtlEvents[i].data.fd)
                            {
                                username = it->first;
                                userMap.erase(it);
                                break;
                            }
                        }
                        printf("client ip:%s ,port:%u disconnect.\n",conn.GetAddr().GetIP(),conn.GetAddr().GetPort());
                        cout<<"client "<<username<<" exit."<<endl;
                    }
                    else
                    {
                        //解包
                        memset(&head,0,sizeof(head));
                        memcpy(&head,acbuf,HEAD_SIZE);

                        switch(head.type)
                        {
                        case 1:
                            memset(&login,0,sizeof(login));
                            memcpy(&login,acbuf + HEAD_SIZE,LOGIN_SIZE);
                            //通过connfd,区分不同客户端
                            //如果重复登录,失败,让前一个账号下线 ; 如果登录成功,服务器要发送一个应答包给客户端。
                            if ( (it = userMap.find(login.name)) != userMap.end())
                            {
                                reply = LOGIN_FAIL;
                                memset(acbuf,0,100);
                                head.size = 4;
                                memcpy(acbuf,&head,HEAD_SIZE);
                                memcpy(acbuf + HEAD_SIZE , &reply , 4);
                                write(it->second,acbuf,HEAD_SIZE + 4);  //登录失败应答包
                                printf("client %s relogin.\n",login.name);
                            }
                            else
                            {
                                printf("client %s login.\n",login.name);
                            }
                            reply = LOGIN_OK;
                            memcpy(acbuf + HEAD_SIZE , &reply , 4);
                            write(rtlEvents[i].data.fd,acbuf,HEAD_SIZE + 4);    //登录成功应答包
                            userMap.insert(pair<string,int>(login.name,rtlEvents[i].data.fd));

                            break;

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

                                }
                            }
                            else
                            {
                                //私聊
                                if ( (it = userMap.find(chat.toName)) != 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(rtlEvents[i].data.fd, acbuf, HEAD_SIZE + CHAT_SIZE);
                                }
                            }
                            break;

                        case 3:
                            memcpy(acbuf + HEAD_SIZE,&userMap,USERS_SIZE);
                            write(rtlEvents[i].data.fd, acbuf, HEAD_SIZE + USERS_SIZE);
                            break;

                        }

                    }

                }

            }
        }

    }

    return 0;
}

client.cpp:

#include "common.h"

void handler(int no)
{
    exit(0);
}

int main()
{

    int ret;
    int sockfd;
    short int port = 0;
    char ip[20] = "";
    char acbuf[100] = "";

    PK_HEAD head = {0};     //包头
    PK_LOGIN login ={0};    //登录包
    PK_CHAT chat = {0};     //聊天包
    int reply;              //登录应答包
    pid_t pid;

    printf("input ip: ");
    fflush(stdout);
    scanf("%s",ip);
    printf("input port: ");
    fflush(stdout);
    scanf("%d",&port);

    //1.socket();
    CTcpClient client;
    client.Socket();
    sockfd = client.GetFd();

    //2.连接connect() 服务器的地址
    CAddress serAddr(ip,port);
    if (client.Connect(serAddr))
    {
        printf("connect OK.\n");
    }

    //登录
    printf("input username: ");
    fflush(stdout);
    scanf("%s",login.name);

    head.type = 1;
    head.size = LOGIN_SIZE;
    memcpy(acbuf,&head,HEAD_SIZE);
    memcpy(acbuf + HEAD_SIZE,&login,LOGIN_SIZE);
    client.Write(acbuf, HEAD_SIZE + LOGIN_SIZE);

    //登录成功之后,主进程负责发包,子进程负责收包

    pid  = fork();
    if (pid == -1)
    {
        perror("fork");
        return -1;
    }
    else if (pid == 0) //子进程
    {
        while(1)
        {
            //一直读
            memset(acbuf,0,100);
            client.Read(acbuf,100);
            //解包
            memset(&head,0,HEAD_SIZE);
            memcpy(&head,acbuf,HEAD_SIZE);
            switch(head.type)
            {
            case 1:
                //登录
                memcpy(&reply,acbuf + HEAD_SIZE,4);
                if (reply == LOGIN_FAIL)
                {
                    printf("your account has been logged in elsewhere.\n");
                    kill(getppid(),SIGINT); //结束程序
                    return 0;
                }
                else if (reply == LOGIN_OK)
                {
                    printf("login OK.\n");
                }
                else
                {
                    printf("login failed.\n");
                    kill(getppid(),SIGINT); //结束程序
                    return 0;
                }
                break;
            case 2:
                //聊天
                memset(&chat,0,CHAT_SIZE);
                memcpy(&chat,acbuf + HEAD_SIZE,CHAT_SIZE);
                printf("from %s: %s\n",chat.fromName,chat.msg);
                break;

            case 3:
                //用户列表
//              memset(&userMap,0,USERS_SIZE);
//              memcpy(&userMap,acbuf + HEAD_SIZE,USERS_SIZE);
//              printf("========= online users =========\n");
//              for (it = userMap.begin(); it != userMap.end() ; it++)
//              {
//                  cout<<it->first<<endl;
//              }
//              printf("================================\n");

            }

        }

    }
    else
    {
        signal(SIGINT,handler);
        //3.通信
        while(1)
        {
            printf("#");
            fflush(stdout);
            scanf("%s",acbuf);
            if(strcmp(acbuf,"exit") == 0)
            {
                kill(pid,SIGINT);
                break;
            }
            else if(strcmp(acbuf,"chat") == 0)
            {
                printf("to who: ");
                fflush(stdout);
                scanf("%s",chat.toName);
                printf("say: ");
                fflush(stdout);
                scanf("%s",chat.msg);
                strcpy(chat.fromName ,login.name);
                memset(acbuf,0,100);

                head.type = 2;
                memcpy(acbuf,&head,HEAD_SIZE);
                memcpy(acbuf + HEAD_SIZE ,&chat, CHAT_SIZE);
                client.Write(acbuf, HEAD_SIZE + CHAT_SIZE);
            }
            else if (strcmp(acbuf,"users") == 0)
            {
                head.type = 3;
                head.size = USERS_SIZE;
                memset(acbuf,0,100);
                memcpy(acbuf,&head,HEAD_SIZE);
                client.Write(acbuf,HEAD_SIZE + USERS_SIZE);

            }
        }

    }

    //4.close()
    if (!client.Close())
    {
        perror("close");
    }
    return 0;
}

??搞定,(保证正常运行就OK了,不过,这里功能还有问题)下次,封装epoll~

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

时间: 2024-10-31 13:53:50

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

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 e

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

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

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

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

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

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

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

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

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网络编程--聊天程序(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表示失败 处理的办法是每一个客户端连接到服