除非在超时之前关闭连接,否则Android套接字读取不起作用

时间:2016-08-23 17:00:28

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

我正在尝试在我的桌面上创建一个套接字服务器并从我的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 output (192.168.0.183是android设备)

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

iResult = shutdown(ClientSocket, SD_SEND);

因此在发送数据后关闭连接,它在android端接收,网络流量如下: Network traffic with socket shutdown

并且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

(这是名义上的)

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

1 个答案:

答案 0 :(得分: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

  

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

     

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

此外,来自Processes and Threads

  

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

     

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

     

此外,Android的单线程模型只有两个规则:

     
      
  • 不要阻止UI线程

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

  •   

建议的解决方案:

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