KEEP K.I.S.S.

tk's blog

虚拟机socket通信丢失头字节奇怪问题一枚

问题描述:在虚拟机里的Linux上用socket TCP (C 编写)连接 Win 主机上 socket(python 编写) 服务器,获取的数据经常少一个字节,而且都是数据的头字节。使用 telnet 连接也是如此。在 Win 主机上用 Socket 调试工具和telnet 都是正常,不会丢失。

硬件环境:虚拟机为 Orcale VM VritualBox 4.1.2,虚拟机上系统为 Debian 6 x86-x64,虚拟机网络类型为 NAT。主机系统为 WinXP SP3 x86。

虚拟机linux下代码:

#include    "unp.h"

int
main(int argc, char **argv)
{
    int    sockfd, n;
    char    recvline[MAXLINE + 1];
    struct sockaddr_in    servaddr;

    if (argc != 2)
        err_quit("usage: a.out <IPaddress>");

    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        err_sys("socket error");

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port   = htons(4013);    /* daytime server */
    if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
        err_quit("inet_pton error for %s", argv[1]);

    if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
        err_sys("connect error");

    while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
        recvline[n] = 0;    /* null terminate */
        printf("%d read from the socket\n", n);
        if (fputs(recvline, stdout) == EOF)
            err_sys("fputs error");
    }
    if (n < 0)
        err_sys("read error");

    exit(0);
}

这代码其实就是《UNIX 网络编程卷1》(UNP)中的第一个程序( unpv13e/intro/daytimetcpcli.c ),用 tcp 连接服务器,读取时间信息的。上述代码做了微小改动。UNP的第三版代码 unpv13e 可以在这里这里下载。

为了测试这个代码我在 Win 主机上用 python 写了一个服务程序:

import socket
import time

host = '192.168.1.68'
port = 4013

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, port))
s.listen(5)

while True:
    connection, address = s.accept()
    t = time.strftime("%a %X %x", time.localtime()).encode("ascii")
    c = connection.send(t)
    print("{0:d} sent of {1:d}: ".format(c, len(t)), "\"{0}\"".format(t.decode("ascii")), "TO client {0[0]}:{0[1]}".format(address))
    connection.close()

其中 192.168.1.68 是主机在局域网中的地址,绑定到这个地址上虚拟机中程序也可以访问到服务器。

当在虚拟机中用编译好的程序或者 telnet 访问 Win 上服务器时,经常会输出如下

"ri 16:22:52 12/28/12"

少了头部一个字符 "F" ,读取只是20个字节,但是也有时候正常,但是正常的几率低于 40%(目测)。

而在 Win 主机上打印输出都是正常的,'21 sent of 21:  "Fri 16:54:40 12/28/12" TO client 192.168.1.68:18289' 这样的,的确都是把 21 个字节发送完毕。

在 Win 主机上用 telnet 或者 TCP/UDP socket 调试助手来测试,获取的数据也都正常,为21字节,没有丢失头字节的情况发生。

(⊙﹏⊙) 

感觉可能是虚拟机 NAT 模式的问题或者还是什么,但是只丢失头字节的确是好奇怪。。。。

·-·-·-·-·-·--·-·-· 2012-12-29 分割线 -·-·-·-·-·-·-·-·--·-·-·-·

今天又测试了下,这次通信获取数据大部分情况下是正常的,只有约10%的几率会丢失头字节。在虚拟机里装了tcpdump 工具来看看情况。

两次通信

tisyang@debian:~/Sources/unpv13e/intro$ ./daytimetcpcli 192.168.1.68
20 read from the socket
at 09:14:47 12/29/12

tisyang@debian:~/Sources/unpv13e/intro$ ./daytimetcpcli 192.168.1.68
21 read from the socket
Sat 09:14:51 12/29/12

说明第一次通信丢失了头字节 'S',第二次通信正常。在此同时,我已经运行 tcpdump 来截获和主机 192.168.1.68 的通信包,命令和输出如下

tisyang@debian:~$ sudo tcpdump  host 192.168.1.68
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes

09:14:46.337897 IP 10.0.2.15.43314 > 192.168.1.68.4013: Flags [S], seq 3451477296, win 14600, options [mss 1460,sackOK,TS val 398218 ecr 0,nop,wscale 6], length 0
09:14:46.341664 IP 192.168.1.68.4013 > 10.0.2.15.43314: Flags [S.], seq 46272001, ack 3451477297, win 65535, options [mss 1460], length 0
09:14:46.341712 IP 10.0.2.15.43314 > 192.168.1.68.4013: Flags [.], ack 1, win 14600, length 0
09:14:46.341747 IP 192.168.1.68.4013 > 10.0.2.15.43314: Flags [S.], seq 46272001, ack 3451477297, win 65535, options [mss 1460], length 0
09:14:46.341837 IP 10.0.2.15.43314 > 192.168.1.68.4013: Flags [.], ack 1, win 14600, length 0
09:14:46.341865 IP 192.168.1.68.4013 > 10.0.2.15.43314: Flags [FP.], seq 1:21, ack 1, win 65535, length 20
09:14:46.342276 IP 10.0.2.15.43314 > 192.168.1.68.4013: Flags [F.], seq 1, ack 22, win 14600, length 0
09:14:46.342360 IP 192.168.1.68.4013 > 10.0.2.15.43314: Flags [.], ack 2, win 65535, length 0


09:14:49.632861 IP 10.0.2.15.43315 > 192.168.1.68.4013: Flags [S], seq 3496530236, win 14600, options [mss 1460,sackOK,TS val 399041 ecr 0,nop,wscale 6], length 0
09:14:49.633743 IP 192.168.1.68.4013 > 10.0.2.15.43315: Flags [S.], seq 46720001, ack 3496530237, win 65535, options [mss 1460], length 0
09:14:49.633771 IP 10.0.2.15.43315 > 192.168.1.68.4013: Flags [.], ack 1, win 14600, length 0
09:14:49.633983 IP 192.168.1.68.4013 > 10.0.2.15.43315: Flags [P.], seq 1:22, ack 1, win 65535, length 21
09:14:49.634048 IP 192.168.1.68.4013 > 10.0.2.15.43315: Flags [F.], seq 22, ack 1, win 65535, length 0
09:14:49.634245 IP 10.0.2.15.43315 > 192.168.1.68.4013: Flags [.], ack 22, win 14600, length 0
09:14:49.634749 IP 10.0.2.15.43315 > 192.168.1.68.4013: Flags [F.], seq 1, ack 23, win 14600, length 0
09:14:49.634815 IP 192.168.1.68.4013 > 10.0.2.15.43315: Flags [.], ack 2, win 65535, length 0

输出中的分段是我添加的,为了区分第一次通信和第二次通信。显然,第一次通信中就只获取到了 20 个字节(length 20),而第二次正常(length 21)。

TCP 连接要经过3次握手,而关闭需要4次握手,从第二次通信就可以看出来。

而在第一次通信中,3次握手tcp建立后,服务器很奇怪的发来一个 SYN 包,而且 FIN 包(用于连接终止)和 PUSH 包(传送数据)是在同一个包内。而在第二次通信里,没有这种情况。