关于tcp server从client接收数据但data和size不匹配的问题
sirlis opened this issue · 4 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:1
,server
接收无异常,长度为5 - 发送
i-1:11111
,server
接收无异常,长度为9 - 发送
i-1:2
,server
接收异常,显示收到i-1:21111
,但长度为5。如果立即发送返回,client
收到内容为i-1:2
如下图
实测百分号前面的 typeID
也存在类似问题,或者说整个字符串都存在问题。
为何接收到的最新数据没有完全覆盖历史数据呢?是否是因为 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
是甲方要求的我没法改动,想要解读出X
和Y
。目前在收到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 ,从而得到 1
和 2
。我感觉这和粘包无关。