Skip to content

Latest commit

 

History

History
129 lines (100 loc) · 6.37 KB

笔记.md

File metadata and controls

129 lines (100 loc) · 6.37 KB

linux chat 开发笔记

回调函数的实现:

function<> 初步认识

std::function<>实现 头文件在#include <functional> 通过typedef std::function<void(event_infor *)> EventCallback; 宏定义一个EventCallback回调函数 <void(event_infor *)> 返回值为void ,传入参数为自定义的事件结构体,目前暂定为下:

struct event_infor {
    std::string ip; //ip地址
    u_int port; //端口
    int fd; //文件描述符
    EventCallback eventCallback; // 回调函数
    int events; //事件的性质 比如EPOLLIN
    //其实应该还要有其它的定义,日后有需求再说
};

event_inforeventCallback函数参数为event_infor这样就可以在函数中获取这些信息,不管用没用到。

epoll_event.eventsevent_infor.events一样、epoll_event.data.ptr指针指向event_infor,注意在写代码中一定要用new分配内存来定义一个event_infor,在定义epoll_event时可以是局部变量因为在执行epoll_ctl()后会把epoll_event拷贝添加进内部的红黑树,会复制epoll_event.data.ptr但不会复制所指向的event_infor,所以用new开辟一块内存空间存放event_infor,然后返回指针把epoll_event.data.ptr指向它。这个坑我弄了一整天才发现,如果是局部变量的话在外面调用回调函数会出现段错误,或者结构体里面的变量默认初始化为0,这样的话当回调函数用这个里面的默认fd = 0,结果导致很多错误,比如read()一个fd = 0而本来客户端的fd = 5,那么程序会永远卡死在这里等待标准输入stdin(因为标准输入文件描述符为0)。图一在event_infor的生命周期有效,当执行回调函数把*event_infor传进去后,地址没变但内容全部为0,因为在这里它生命周期已经结束。

图1:

KXuuut.png 图2:

KXuKDP.png

绑定回调函数

传统的绑定方法:

1.C语言的函数指针

2.std::bind()

3.lambda表达式

第一种就不深入探讨了,在C语言中只能用这个,而在c++中函数的绑定更为简单std::bind()为传统做法,lambda是c++11后才出现的,用这两个区别《effective modern c++ 》中有详细的讨论,作者是建议用lambda的,但是用之前要完全了解lambda的方方面面避免错误。

lambda使用示例:

l_infor.eventCallback = [&](event_infor *infor){ acceptconn(infor);};

通过[&]来额外引入其它变量,最后在调用实际功能函数,实际功能函数参数可以随便你,lambda的作用就是两者的粘合剂,优点简洁。

TCP粘包解决方案

黏包的在客户端发送频率低的情况下粘包不明显,下面是原来的服务器中epoll一个事件可读的回调函数recvdata(event_infor *infor),当调用这个函数然后recv()函数读缓冲区的数据,指定大小为BUFF_MAX-1,而BUFF_MAX我设置为1024,一般单次接收数据小于这个值,所以基本上就是读取了所有缓冲区数据,接收过程:收到数据->epoll可读->调用回调函数->读取缓冲区所有数据->空缓冲区等待下一次接收。在处理客户端发来的聊天消息时还没有发生粘包现象,处理文件传输时一直粘包,问题分析是客户端高速循环调用send()发送数据,也就是说服务器其实recv()多条数据,比如说发送{"code":1}{"code":2}服务器读一次可能是{"code":1}{"co。 原服务器代码:

void Server::recvdata(event_infor *infor) {
    int n = recv(infor->fd, infor->buff, BUFF_MAX - 1, 0);
    if (n > 0) { //收到的数据
        infor->buff[n] = '\0';
        LOG(INFO) << infor->ip << " 发来一条消息: " << infor->buff;
        _messageQueue->push(infor->buff, infor);
    } else if (n == 0) {
        LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
        close(infor->fd);
        infor->status = false;
    } else {
        close(infor->fd);
        infor->status = false;
        LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
    }
    bzero(infor->buff, BUFF_MAX - 1);
}

解决办法见下:

//结构体定义
struct MessageStruct{
    int jsonLen;
    char json[0]; //不能换成char *json
};

客户端代码:

string data = "{\"code\":0,\"data\":{\"passwd\":\"123456\",\"name\":\"daimiaopeng\"}}";
    int len = sizeof(MessageStruct) + data.size();
    MessageStruct *messageStruct = static_cast<struct MessageStruct *>(malloc(len));
    messageStruct->jsonLen = data.size();
    strncpy(messageStruct->json, data.c_str(), data.size());
    //也可以用memcpy(),这里是string->char* 而不是char*->char*
    for (int i = 0; i < 1000; i++) {
        send(sockfd, messageStruct, len, 0);
        }
    free(messageStruct);

改进后的服务器代码:

void Server::recvdata(event_infor *infor) {
    MessageStruct messageStruct;
    //读取结构体
    int n = recv(infor->fd, &messageStruct, sizeof(MessageStruct), 0);
    if (n > 0) { //收到的数据
        //获取结构体中的json长度字段
        char *jsonData = static_cast<char *>(malloc(messageStruct.jsonLen));
        //再读json数据
        if (jsonData== nullptr)
            return;
        recv(infor->fd, jsonData, messageStruct.jsonLen, 0);
        _messageQueue->push(string(jsonData, messageStruct.jsonLen), infor);
        free(jsonData);
    } else if (n == 0) {
        LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
        close(infor->fd);
        infor->status = false;
    } else {
        close(infor->fd);
        infor->status = false;
        LOG(INFO) << "fd: " << infor->fd << " 连接关闭";
    }
}

总的来说是通过定义一个结构体然后二进制发送,读messageStruct的大小数据,sizeof(messageStruct)大小就是int类型的大小,为4,实际上后面还跟了数据,char json[0]不占用空间,而char *json是占用8个字节的,所以不能用char *json代替char json[0]这里是个坑注意一下,转换完成读取jsonLen的值,再读jsonLen大小的数据这样就完成分包了。这里比较难理解,建议网上多看几篇通过结构体解决粘包的文章,结合本文的代码理解,注意在windows平台下的vs编译器会出现“非法的大小为零的数组”错误提示,因为不支持char json[0];写法。