簡體   English   中英

除非超時前關閉連接,否則Android套接字讀取無法正常工作

[英]Android socket read not working unless connection is shutdown before timeout

我試圖在我的桌面上制作一個套接字服務器並從我的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: Wireshark輸出 (192.168.0.183是android設備)


但是,如果我取消注釋

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

(名義上)

因此,我的問題是,如何在不關閉連接的情況下在該套接字連接上向后發送數據? 另外請注意,我正在計划發送二進制數據,而不是文本。

在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套接字錯誤代碼10060

WSAETIMEDOUT 10060

連接超時。

連接嘗試失敗,因為一段時間后被連接方未正確響應,或者建立的連接失敗,因為連接的主機未響應。


編輯:

Android方面的錯誤源似乎是您在onCreate()方法中創建的網絡連接! 到處都建議不要這樣做(在Android中)。

您應該僅使用onCreate()方法來創建和實例化將在應用程序中使用的對象。 不應有任何阻塞呼叫,例如您的情況(sock.connect(IP,port))。

摘自Android服務開發人員指南

服務是一種應用程序組件,代表應用程序執行長時間運行的操作而不與用戶交互或提供功能供其他應用程序使用的願望。

請注意,服務與其他應用程序對象一樣,在其托管過程的主線程中運行。 這意味着, 如果您的服務要執行任何占用大量CPU資源(例如MP3播放)或阻止(例如聯網)的操作,則它應該產生自己的線程來執行該工作 (強調我的意思)。

另外,從進程和線程

當內存不足並需要其他可立即為用戶服務的進程時,Android可能會決定在某個時候關閉該進程。

在決定終止哪些進程時,Android系統會權衡它們對用戶的相對重要性。 例如,與托管可見活動的流程相比,它更容易關閉不再在屏幕上可見的托管活動的流程。 因此,是否終止進程的決定取決於該進程中運行的組件的狀態。

此外,Android的單線程模型只有兩個規則:

  • 不要阻塞UI線程

  • 不要從UI線程外部訪問Android UI工具包。

建議的解決方案:

創建一個單獨的服務以實現網絡連接建立(請檢查本地服務樣本 )。 您可以參考該鏈接,以了解如何通過服務而不是活動本身來實現網絡連接!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM