繁体   English   中英

Winsock2 TCP / IP-某些数据包可能会被忽略,原因可能是前一个数据包的终止符为空

[英]Winsock2 tcp/ip - some data packets are ignored probably due to null terminator from the previous packet

我编写了一个简单的客户端-服务器程序。 Network.h是一个头文件,它使用Winsock2.h(TCP / IP模式)创建套接字,以阻止模式接受/连接,以非阻止模式发送/接收。 我这样做是为了让函数string TNetwork::Recv(int size)在遇到WSAWOULDBLOCK错误时将返回字符串“ Nothing”(尚未收到任何数据)

这是我的主要功能:

int main(){
    string Ans;
    TNetwork::StartUp(); //WSA start up, etc
    cin >> Ans;
    if (Ans == "0"){ // 0 --> server
        TNetwork::SetupAsServer(); //accept connection (in blocking mode!)
        while (true){
            TNetwork::Send("\nAss" + '\0'); //without null terminator, the client may read extra bytes, causing undefined behavior (?)
            TNetwork::Send("embly" + '\0');
            cin >> Ans;
        }
    }
    else{ // others --> regard Ans as IP address. e.g. I can type "127.0.0.1"
        TNetwork::SetupAsClient(Ans);
        string Rec;
        while (true){
            Rec = TNetwork::Recv(1000);
            if (Rec != "Nothing"){
                cout << Rec;
            }
        }
    }
    system("PAUSE");
}

假设客户端在连接时以及服务器在其控制台窗口中输入任何内容时都将打印“程序集”。 但是,有时,客户端只会在控制台中打印出“ \\ nAss”而没有“汇编”。

据我了解,TCP / IP确保所有数据都以正确的顺序发送,因此我猜这是两个数据包同时到达的情况,而这通常是在不稳定的Internet上发生的。 并且由于该空终止符,客户端将忽略“汇编”,因为Recv()函数在遇到空终止符时会停止读取。

那么,如何确保客户端将始终正确读取所有数据包?

是的,网络堆栈将以正确的顺序发送数据,而不管您使用哪种终止类型。 这与您如何接收和处理数据 (请注意:不是packet, stream )有关。 如果接收到全部11个字节并将其打印到屏幕上,则打印功能将在到达零时停止,但是其余数据仍然存在。

注意:由于是流,如果仅从流中接收10个字节的数据,会发生什么? 您需要对收到的零进行扫描,以了解是否已收到完整的“零终止的字符串”(如果这是您想要传达数据的方式)。

编辑:另外,我不认为"\\nAss" + '\\0'在做您认为的事情。 而不是在字符串的末尾添加0字符(顺便说一句,该字符已经有一个),而是在字符串指针中添加0。

正如@mark所指出的,TCP全部与流有关,而不是与数据包有关。 TCP负责确保将数据从A可靠地传输到B,并确保按照传输顺序将数据传递给使用者。 是的,数据是通过网络打包的,但是系统上的TCP堆栈会接收这些数据包,并构建可通过recv()函数提供给您的流。 TCP堆栈处理乱序数据,丢失数据和重复数据,以便在您的应用程序看到该数据时,该流是发送方发送时间的镜像副本。

为了正确接收TCP数据,通常需要某种循环,以便在套接字可用时从套接字读取数据。 我通常这样做的方法是拥有一个专用于服务套接字的线程。 线程功能中有一个循环,当套接字可用时从套接字读取数据,否则空闲。 该循环将数据读入一个1 KB的缓冲区中。 一旦从套接字接收到该缓冲区中的数据,该缓冲区将被复制到另一个线程进行处理。 处理线程的线程功能中有一个循环,该循环从套接字线程接收1 KB的缓冲区,并将其添加到主缓冲区(例如1 MB)的后端。 然后,处理线程将消息从此主缓冲区中处理出来,并将其提供给应用程序。

对于一个简单的演示应用程序,两个线程可能会过大。 我所描述的两个线程可以肯定地合并为一个,但是对于我的应用程序来说,拥有两个线程并利用系统上的多个内核更为有效。 关键是,如果您要有一个前端UI,那么就没有办法使用至少一个线程并且仍然要使UI响应。

另一件事。 协议设计有两种常用的机制。 您正在使用一个标记(例如,空终止符等)来表示消息的开始/结束。 我不喜欢这种机制,主要是因为在某些时候标记实际上可能需要成为消息的一部分。 另一种机制是在每条消息上都有一个标头,该标头至少告诉消息多长时间。 我更喜欢这种机制,并且在标题中还包含同步词和消息类型。 例如,

struct Header
{
    __int16 _sync;  // a hex pattern, e.g., 0xABCD
    __int16 _type;
    __int32 _length;
}

总共有8个字节。 因此,当从主缓冲区进行处理时,我读取了前8个字节,验证了同步字并获得了长度。 我确定主缓冲区中是否有“长度”个字节可用。 如果没有,我必须等到套接字线程为我提供更多数据后再进行检查。 如果是这样,我将从主缓冲区中提取“长度”字节,并将其传递给根据指定类型创建的对象,该对象知道如何解释该特定消息。 然后重复。

正如我提到的,我使用1 MB左右的主缓冲区。 在处理消息时,将其从主缓冲区中删除很重要,这样后端就有更多空间可用于数据。 这涉及简单地将未处理的数据(如果有的话)复制到缓冲区的开头。 如果数据输入的速度快于处理速度,则主缓冲区可能需要具有调整自身大小的能力,以容纳其他数据。

我希望那不会压倒一切。 从简单开始,然后添加。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM