【问题标题】:Android socket read not working unless connection is shutdown before timeout除非连接在超时前关闭,否则 Android 套接字读取不起作用
【发布时间】:2016-08-23 17:00:28
【问题描述】:

我正在尝试在我的桌面上创建一个套接字服务器并从我的 android 连接到它,但是,除非服务器在发送数据后关闭连接,否则 android 无法从套接字读取。

桌面服务器代码:

#define SERVER_BUFLEN   512
#define SERVER_PORT     "27033"

SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;

int main () {
    setvbuf(stdout, NULL, _IONBF, 0);

    WSADATA wsaData;
    int iResult;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int iSendResult;
    char recvbuf[SERVER_BUFLEN];
    int recvbuflen = SERVER_BUFLEN;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo(NULL, SERVER_PORT, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        ListenSocket = INVALID_SOCKET;
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);


    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        ListenSocket = INVALID_SOCKET;
        WSACleanup();
        return 1;
    }

    printf ("waiting for new client\n");
    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
    }
    printf ("new client connected\n");

    //int flag = 1;
    //setsockopt(ClientSocket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));

    do {
        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

            iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed with error: %d\n", WSAGetLastError());
            }
            printf("Bytes sent: %d\n", iSendResult);
            //iResult = shutdown(ClientSocket, SD_SEND);
        } else if (iResult == 0) {
            printf("Connection closing...\n");
        } else  {
            printf("recv failed with error: %d\n", WSAGetLastError());
        }
    } while (iResult > 0);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
    }
    closesocket(ClientSocket);
    printf ("client left\n");

    if (ListenSocket != INVALID_SOCKET) {
        closesocket(ListenSocket);
    }
    WSACleanup();
    return 0;
}

Android 客户端代码:

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate");


        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);

        try {
            Socket sock = new Socket();
            //sock.setReceiveBufferSize(10);
            sock.connect(new InetSocketAddress("192.168.0.101", 27033));

            OutputStream os = sock.getOutputStream();
            InputStream is = sock.getInputStream();

            String msg = "Hello World!\r\n\0";
            byte[] arr = msg.getBytes(Charset.forName("UTF-8"));

            os.write(arr);
            os.flush();

            Log.d(TAG, "RECV MSG: " + is.read ());

            sock.close();
        } catch (Exception e) {
            Log.d(TAG, e.getMessage());
        }


    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}

服务器的输出是:

waiting for new client
new client connected
Bytes received: 15
Bytes sent: 15
recv failed with error: 10060
client left

客户端的输出是:

08-23 18:29:55.997 26472-26472/com.example.greg.systemremote D/MainActivity: onCreate
08-23 18:29:55.997 26472-26472/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: hostname=192.168.0.101; servname=(null); netid=0; mark=0
08-23 18:29:55.997 26472-26472/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: ai_addrlen=0; ai_canonname=(null); ai_flags=4; ai_family=0
08-23 18:33:54.254 26472-26472/com.example.greg.systemremote D/MainActivity: recvfrom failed: ETIMEDOUT (Connection timed out)

请注意,客户端没有收到任何消息。

我尝试使用

禁用 Nagle
int flag = 1;
setsockopt(ClientSocket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));

但它并没有改变任何东西,所以我尝试使用 Wireshark 检查网络流量,从输出中可以看到,数据正在发送到 android: (192.168.0.183 是安卓设备)


但是,如果我取消注释该行

iResult = shutdown(ClientSocket, SD_SEND);

从而在发送数据后关闭连接,在android端接收到,网络流量如下:

android 输出是

08-23 18:47:41.984 13164-13164/com.example.greg.systemremote D/MainActivity: onCreate
08-23 18:47:41.985 13164-13164/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: hostname=192.168.0.101; servname=(null); netid=0; mark=0
08-23 18:47:41.985 13164-13164/com.example.greg.systemremote D/libc-netbsd: [getaddrinfo]: ai_addrlen=0; ai_canonname=(null); ai_flags=4; ai_family=0
08-23 18:47:42.318 13164-13164/com.example.greg.systemremote D/MainActivity: RECV MSG: 72

(名义上的)

所以,我的问题是,如何在不关闭连接的情况下在此套接字连接上发回和第四次发送数据?另请注意,我打算发送二进制数据,而不是文本。

【问题讨论】:

    标签: java android c++ sockets network-programming


    【解决方案1】:

    您在 C++ 端的错误来源是:do-while loop

    do {
            iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
            if (iResult > 0) {
                printf("Bytes received: %d\n", iResult);
    
                iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
                if (iSendResult == SOCKET_ERROR) {
                    printf("send failed with error: %d\n", WSAGetLastError());
                }
                printf("Bytes sent: %d\n", iSendResult);
                //iResult = shutdown(ClientSocket, SD_SEND);
            } else if (iResult == 0) {
                printf("Connection closing...\n");
            } else  {
                printf("recv failed with error: %d\n", WSAGetLastError());
            }
        } while (iResult > 0);
    

    对于第一次迭代,它进入循环,接收数据并将数据成功发送到 Android 设备。一切都很顺利。而且,然后在 Android 端,只有在接受一个响应后才关闭连接。

    try {
                Socket sock = new Socket();
                //sock.setReceiveBufferSize(10);
                sock.connect(new InetSocketAddress("192.168.0.101", 27033));
    
                OutputStream os = sock.getOutputStream();
                InputStream is = sock.getInputStream();
    
                String msg = "Hello World!\r\n\0";
                byte[] arr = msg.getBytes(Charset.forName("UTF-8"));
    
                os.write(arr);
                os.flush();
    
                Log.d(TAG, "RECV MSG: " + is.read ());
    
                sock.close();
            } catch (Exception e) {
                Log.d(TAG, e.getMessage());
            }
    

    由于C++代码在do-while循环中,iResult>0,所以会进入循环内部,再次尝试接收数据;但是,不幸的是,Android 端的连接已经关闭。

    因此,您需要检查连接是否打开,然后才能继续接收数据。目前,您的编码逻辑无法接收数据。

    它会在当前状态下继续抛出异常。

    另外,来自错误代码Windows Sockets Error Code 10060

    WSAETIMEDOUT 10060

    连接超时。

    连接尝试失败,因为连接方没有 一段时间后正确响应,或建立连接 失败,因为连接的主机没有响应。


    编辑:

    Android 端的错误来源似乎是您在 onCreate() 方法中创建网络连接!在任何地方都不鼓励这样做(在 Android 中)。

    您应该只使用onCreate() 方法来创建和实例化您将在应用程序中使用的对象。不应有任何阻塞调用,如您的情况(sock.connect(IP,port))。

    来自Android's Developer Guide on Service

    服务是一个应用程序组件,代表一个 应用程序希望在不执行长时间运行的操作时 与用户交互或为其他人提供功能 使用的应用程序。

    请注意,服务与其他应用程序对象一样,在主程序中运行 他们的托管过程的线程。这意味着,如果您的服务是 将执行任何 CPU 密集型(例如 MP3 播放)或阻塞(例如 作为网络)操作,它应该产生自己的线程 做这项工作(强调我的)。

    另外,来自Processes and Threads

    Android 可能会在某个时候决定关闭进程,当内存 低并且其他更直接的进程需要 为用户服务。

    在决定要杀死哪些进程时,Android 系统会权衡它们的 对用户的相对重要性。例如,它更容易关闭 关闭不再可见的进程托管活动 屏幕,与托管可见活动的流程相比。决定 因此,是否终止进程取决于进程的状态 在该进程中运行的组件。

    另外,Android 的单线程模型只有两条规则:

    • 不要阻塞 UI 线程

    • 不要从 UI 线程外部访问 Android UI 工具包。

    建议的解决方案:

    创建一个单独的服务来实现网络连接的建立(检查local service sample)。您可以参考如何通过服务实现网络连接的链接,而不是活动本身!

    【讨论】:

    • 不幸的是,这不是问题,因为在 android 端,我什至没有收到第一条消息:08-23 18:33:54.254 26472-26472/com.example.greg.systemremote D/MainActivity: recvfrom failed: ETIMEDOUT (Connection timed out) 如果我删除 while 循环并在关闭连接之前放置 Sleep(100000); ,同样的事情也会发生。
    • @19greg96 - 我做了一些修改,请尝试一下,看看是否有帮助!并且,通过查看答案和 Android 官方文档链接来避免一些不良做法。
    • 我阅读了文档,我知道这是不好的做法,但这不是我的问题,因为建立连接可以正常工作,如果我在编写后关闭连接,阅读也可以正常工作。如果我不关闭连接,问题在于阅读,因为没有收到数据。我只是把这个放在onCreate方法中,是为了简化代码,方便测试。
    • @19greg96 - 那看起来很奇怪!很难评论为什么 Android 不能直接连接!并且,一旦 C++ 端记录数据,连接就会关闭。
    猜你喜欢
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    • 2012-11-17
    • 2016-10-02
    • 2014-01-13
    • 2017-10-29
    • 2018-06-05
    相关资源
    最近更新 更多