zhllxt / asio2

Header only c++ network library, based on asio,support tcp,udp,http,websocket,rpc,ssl,icmp,serial_port,socks5.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

关于tcp server从client接收数据但data和size不匹配的问题

sirlis opened this issue · comments

尝试在server端解码收到的client发出来的消息。比如格式为 i-typeID:ID 然后将对应的 typeID 和 ID 拿出来。server的代码如下

void split(const std::string &s, std::vector<std::string> &tokens, const std::string &delimiters = " ")
{
    string::size_type lastPos = s.find_first_not_of(delimiters, 0);
    string::size_type pos = s.find_first_of(delimiters, lastPos);
    while (string::npos != pos || string::npos != lastPos)
    {
        tokens.emplace_back(s.substr(lastPos, pos - lastPos));
        lastPos = s.find_first_not_of(delimiters, pos);
        pos = s.find_first_of(delimiters, lastPos);
    }
}
.bind_recv([&](std::shared_ptr<asio2::tcp_session> &session_ptr, std::string_view data)
           {
                std::vector<std::string> tokens;
                split(data.data(), tokens, "-");
                if (tokens[0] == "i") // i-typeID:ID, register a client with typeid and id, e.g., `i-1:1`
                {
                    std::string ip_port = session_ptr->remote_address() + ":" + std::to_string(session_ptr->remote_port());
                    std::string typeid_id = tokens[1];
                    client.insert(std::pair<std::string, std::string>(ip_port, typeid_id)); // connect ip:port with ids
                    std::vector<std::string> sockets;
                    split(typeid_id.data(), sockets, ":");
                    printf("client: %s:%u register typeid-%.*s, id:%.*s, total:%zu\n",
                        session_ptr->remote_address().c_str(), session_ptr->remote_port(),
                        (int)sockets[0].size(), sockets[0].data(),
                        (int)sockets[1].size(), sockets[1].data(), client.size());
                    printf("data %zu %s\n", data.size(), data.data());
                }
                // directly send received data to client
                session_ptr->async_send(data); })

client连接后开始发送数据

  • 发送i-1:1server接收无异常,长度为5
  • 发送i-1:11111server接收无异常,长度为9
  • 发送i-1:2server接收异常,显示收到 i-1:21111,但长度为5。如果立即发送返回,client收到内容为i-1:2

如下图

image

实测百分号前面的 typeID 也存在类似问题,或者说整个字符串都存在问题。

image

为何接收到的最新数据没有完全覆盖历史数据呢?是否是因为 split 函数写的有问题?

发送i-1:2,server接收异常,显示收到 i-1:21111,但长度为5

这说明什么问题?这说明接收的数据长度和数据内容都是正确的。

那为什么打印出来的看上去却不正确?因为打印日志的语句有问题。问题在哪里呢?

把你的打印数据的语句从printf("data %zu %s\n", data.size(), data.data());改为printf("data : %zu %.*s\n", data.size(), (int)data.size(), data.data());即可。

原因是什么?

网络接收数据,必须要有一个缓冲区,假如这个缓冲区是10M,第一次收到的数据是9M,那么缓冲区前面的9M都会被填充成接收到的数据,如果第二次只收到1个字节,那么在收到这1个字节以后,我们需要把10M的缓冲区中其它的1010241024-1=10485760-1=10485759这么多字节都要清0吗?

需要吗?有必要吗? -- 完全没有。这是几乎完全无意义的操作,浪费CPU,降低效率。

所以原因就是在每次接收数据后,缓冲区中的,后面的,以前的数据,没有清0,此时只需要打印日志时注意数据长度即可。

其实bind_recv里已经给你传的数据格式就是std::string_view data了,你只需要std::cout<<data<<std::endl;即可。不用上面那个printf的奇怪的格式了,你也不容易理解 。

你后面的问题我没看。

发送i-1:2,server接收异常,显示收到 i-1:21111,但长度为5

这说明什么问题?这说明接收的数据长度和数据内容都是正确的。

那为什么打印出来的看上去却不正确?因为打印日志的语句有问题。问题在哪里呢?

把你的打印数据的语句从printf("data %zu %s\n", data.size(), data.data());改为printf("data : %zu %.*s\n", data.size(), (int)data.size(), data.data());即可。

原因是什么?

网络接收数据,必须要有一个缓冲区,假如这个缓冲区是10M,第一次收到的数据是9M,那么缓冲区前面的9M都会被填充成接收到的数据,如果第二次只收到1个字节,那么在收到这1个字节以后,我们需要把10M的缓冲区中其它的10_1024_1024-1=10485760-1=10485759这么多字节都要清0吗?

需要吗?有必要吗? -- 完全没有。这是几乎完全无意义的操作,浪费CPU,降低效率。

所以原因就是在每次接收数据后,缓冲区中的,后面的,以前的数据,没有清0,此时只需要打印日志时注意数据长度即可。

其实bind_recv里已经给你传的数据格式就是std::string_view data了,你只需要std::cout<<data<<std::endl;即可。不用上面那个printf的奇怪的格式了,你也不容易理解 。

你后面的问题我没看。

感谢解答。

如果需要按照特定格式解出收到的信息,应该如何操作呢?比如数据格式i-X:Y是甲方要求的我没法改动,想要解读出XY。目前在收到string_view数据后转换为string再进行处理。

std::string recvdata{data};

string_view 已经有了数据内容和数据长度,完全没有必要转换为string再去处理。
string是怎么处理的,string_view用同样的方法一样的处理,这个转换完全是无意义的。
这个转换,收到数据后,该怎么转换,就怎么转换就行。
目前在收到string_view数据后转换为string再进行处理。 -- 这说明你不是已经可以转换了吗。再优化下,不要转成string,而是直接用string_view 处理即可。
去B站搜索一下tcp粘包的教程,看一下再说吧,你问的都是基础问题。

string_view 已经有了数据内容和数据长度,完全没有必要转换为string再去处理。 string是怎么处理的,string_view用同样的方法一样的处理,这个转换完全是无意义的。 这个转换,收到数据后,该怎么转换,就怎么转换就行。 目前在收到string_view数据后转换为string再进行处理。 -- 这说明你不是已经可以转换了吗。再优化下,不要转成string,而是直接用string_view 处理即可。 去B站搜索一下tcp粘包的教程,看一下再说吧,你问的都是基础问题。

收到,感谢。可能是我没有找到合适的方法把 string_view 按照收到的实际长度进行预处理。对于发送 1:2 但收到的缓存字符串为 1:21111 的情况,用 data.substr(0, data.size()); 然后对 : 进行切分没有得到预期结果。所以我才把 string_view 转为 string ,他会自动根据长度来得到实际的字符串而不是整个 buffer ,从而得到 12。我感觉这和粘包无关。