【问题标题】:Socket server with epoll gives unknown bytes at disconnect带有 epoll 的套接字服务器在断开连接时提供未知字节
【发布时间】:2012-02-17 16:00:21
【问题描述】:

我有一个 C++ 的套接字服务器,我正在使用 epoll。我正在向服务器发送一个包含 HeaderPacket 和 NormalPacket 的字符。 首先我正在阅读 HeaderPacket,然后我正在阅读 NormalPacket。

现在的问题是,当我关闭客户端时(我试图使用 close 和 shutdown - 相同的输出),我在第一个 recv(读取标头数据包的那个)和分段错误之后得到了一些奇怪的字节。

此外,当我将内容字符的大小从 HeaderPacket 更改为另一个值(例如 120)时,我不会遇到分段错误,但是当我将其设置为 40 或其他值时,我会遇到分段错误。

#define BUFFERSIZE 256
#define CHARSIZE 40

这是我用来阅读的功能:

void PacketHandler::ReadBytes(int fd, struct HeaderPacket &hp, char buffer[])

{

    int reading = 0;

    ssize_t hpCount, cpCount;

    char hpBuffer[6];



    hpCount = recv(fd, hpBuffer, 6, 0);

    if(hpCount <= 0)

    {

       reading = 1;

    } else {  

        this->UnserializeHeaderPacket(hpBuffer, hp);

        print(DEBUG, Helpers::IntegerToString(hp.length)); 

        cpCount = hp.length;

        char cpBuffer[cpCount];

        memset(cpBuffer, 0, sizeof(cpBuffer));

        char* iterator = cpBuffer;

        int bytesLeft = sizeof(cpBuffer) - sizeof(char);

        print(DEBUG, Helpers::IntegerToString(bytesLeft)); 

        if(bytesLeft < 0)

        {

            reading = 1;

        }

        while(bytesLeft > 0)

        {

            ssize_t curr;

            curr = recv(fd, iterator, bytesLeft, 0);



            if(curr == -1)

            {

                if(errno != EAGAIN)

                {

                    reading = 1;

                    print(WARNING, "reading error at content packet");

                }

                break;

            } else if(curr == 0) {

                reading = 1;

                break;

            } 



            iterator += curr;

            bytesLeft -= curr;                 





        }



            memcpy(buffer, cpBuffer, sizeof cpBuffer);     

    }



    if(reading)

    {

        print(NOTICE, "Closed connection with the descriptor " + Helpers::IntegerToString(fd));

        close(fd);

    }

}

这里是 epoll 的函数

void EventHandler::RunningLoop(int fd)

{

    while(1)

    {

        int availableEvents, i;



        availableEvents = epoll_wait(this->efd, this->events, MAXEVENTS, -1);

        for(i = 0; i < availableEvents; i++)

        {

            if(this->events[i].data.fd == fd)

            {

                // Accepting new connection

                this->AcceptClient(fd);

                continue;

            }

            else if((this->events[i].events & EPOLLERR) || (this->events[i].events & EPOLLHUP) || (!(this->events[i].events & EPOLLIN)))

            {

                print(WARNING, "epoll error on reading from fd");

                close (this->events[i].data.fd);

                continue;

            } 

            else if(this->events[i].events & EPOLLRDHUP) 

            {

                print(WARNING, "intern close socket");

                close (this->events[i].data.fd);



            } else {

                // Reading packets

                this->run->InitializePacket(this->events[i].data.fd); // cals the read function

            }

        }

    }



    free(this->events);

    close(fd);

}

我的数据包:

struct HeaderPacket

{

    uint16_t opcode;

    uint32_t length;

};



struct HelloWorldPacket

{

    uint16_t byteOrder;

    char content[CHARSIZE];

};

序列化函数:

void PacketHandler::SerializeHeaderPacket(HeaderPacket packet, char buffer[])

{

    uint16_t u16;

    uint32_t u32;



    u16 = htons(packet.opcode);

    memcpy(buffer+0, &u16, 2);

    u32 = htonl(packet.length);

    memcpy(buffer+2, &u32, 4);

}



void PacketHandler::UnserializeHeaderPacket(char buffer[], HeaderPacket &packet)

{

    uint16_t u16;

    uint32_t u32;



    memcpy(&u16, buffer+0, 2);

    packet.opcode = ntohs(u16);

    memcpy(&u32, buffer+2, 4);

    packet.length = ntohl(u32);

}



void PacketHandler::SerializeHelloWorldPacket(HelloWorldPacket packet, char buffer[])

{

    uint16_t u16;



    u16 = htons(packet.byteOrder);

    memcpy(buffer+0, &u16, 2);

    memcpy(buffer+2, &packet.content, sizeof packet.content);

}



void PacketHandler::UnserializeHelloWorldPacket(char buffer[], HelloWorldPacket &packet)

{

    uint16_t u16;



    memcpy(&u16, buffer+0, 2);

    packet.byteOrder = ntohs(u16);



    strcpy(packet.content, buffer+2);



}

这就是我向服务器发送数据的方式:

int EventHandler::SendHelloWorld(int fd)

{

    HeaderPacket hp;

    HelloWorldPacket hc;

    char buffer[256];

    int sendResult;

    char message[] = "hello_first_message\r\n";





    hp.opcode = HELLOWORLD;

    hp.length = sizeof message;

    memcpy(hc.content, message, sizeof message);





    packets->SerializeHeaderPacket(hp, buffer);

    packets->SerializeHelloWorldPacket(hc, buffer+6);



    sendResult = write(fd, buffer, sizeof buffer);







    return sendResult;



}

感谢您的帮助。

【问题讨论】:

  • 编译时没有错误和警告吗?
  • 连接关闭时(recv返回0),是否将socket从epoll集合中移除?
  • @Joachim PileBorg 我使用 close(fd) 还不够吗?谢谢。
  • @stefanB 是的,它编译时没有警告和错误。
  • 无视我的评论。如果您不复制套接字描述符(例如dup 等),那么它应该在关闭时自动从集合中删除。

标签: c++ c sockets serialization epoll


【解决方案1】:

我发现了问题。 当我写入服务器时,我使用的是 sizeof buffer 并且缓冲区的长度为 256,我不读取 256 字节。

感谢您的帮助。

【讨论】:

  • 您的意思是客户端可以使您的服务器崩溃?我希望你也打算在服务器端解决这个问题。您的“unserializeHelloWorld”函数本质上是不安全的。我需要详细说明为什么吗?此外,“valgrind”是诊断此类神秘崩溃的非常有用的工具。它可能会在崩溃之前在代码中的特定位置标记您的缓冲区溢出。
  • @selbie 你能解释一下为什么不安全吗?谢谢,我会试试 valgrind。
  • 错误 #1:“ReadBytes”函数。它读取 6 个字节并将其提供给 UnserializeHeaderPacket。一个糟糕的购买(使用他自己的客户端代码)连接到您的服务器并将这 6 个字节作为头包字节流发送:{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}。 UnserializeHeaderPacket 会将“hp.length”成员初始化为 4294967295(即 4GB)。然后您将通过调用“char cpBuffer[cpCount];”来分配该内存量我很确定这会使您的服务器崩溃。如果没有,函数底部的“memcpy(buffer, cpBuffer, sizeof cpBuffer)”语句肯定会。
  • 错误 #1.1:知道您的缓冲区大小为“256”,我可以发送以下字节流 {0xff, 0xff, 0x00, 0x00, 0xff, 0xff}。这只是 cpBuffer 声明的 64K 分配。现在我的客户端流发送了 64K 的数据——其中大部分是 x86 操作码。当您在 ReadBytes 底部执行 memcpy 语句时,我可以触发“缓冲区溢出”,现在让您的服务器执行我的程序代码。较新的编译器可能会插入保护措施来防止这种情况发生,但无论如何它都会崩溃。这称为缓冲区溢出。在这里阅读:en.wikipedia.org/wiki/Buffer_overflow
  • 错误 #2:UnserializeHelloWorldPacket 调用 strcpy 将网络缓冲区复制到 packet.content 缓冲区(只有 40 个字节)。如果发送者正好发送了 40 个字节,但没有 null 终止他的消息怎么办?即使您事先 memset(cpBuffer) ,如果我向您发送了那么多字节而没有空字符,您的 strcpy 也会复制到无效内存中。如果我发送了超过 40 个字节,我已经在你的代码中利用了另一个缓冲区溢出。
猜你喜欢
  • 2014-12-02
  • 2012-01-07
  • 1970-01-01
  • 2021-12-28
  • 2011-01-01
  • 1970-01-01
  • 2014-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多