在 Android 上同时从服务器循环读取和写入

Reading and Writing from Server in loop in the same time on Android

我需要创建一个到服务器的连接并在一段时间内从服务器读取消息。同时,我需要每X秒向服务器发送一个字符串来更新数据。我正在使用 HttpUrlConnection 和以下代码:

NTRIPClient.kt

class NTRIPClient {

private val bufferSize = ByteArray(64)
private val crlf = "\r\n"

private var inputStream: BufferedInputStream? = null

fun startConnection(gga: String, usbService: UsbService) {
    con = url.openConnection() as HttpURLConnection?
    con?.doOutput = true
    con?.setChunkedStreamingMode(0)
    con?.setRequestProperty("Ntrip-GGA", gga)
    con?.setRequestProperty("Connection", "Keep-Alive")
    con?.setRequestProperty("Accept-Encoding", "identity")

    try {
        inputStream = BufferedInputStream(con!!.inputStream)
        var readBuffer = inputStream!!.read(bufferSize)
        while (readBuffer > 0) {
            usbService.write(bufferSize)
            readBuffer = inputStream!!.read(bufferSize)
        }
    } catch (e: IOException) {
        e.printStackTrace()
    } finally {
        con?.disconnect()
    }
}

fun updateGGA(gga: String) {
    try {
        val outputStream = OutputStreamWriter(con!!.outputStream)  //this line create error
        outputStream.write(gga + crlf)
        outputStream.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

在ViewModel.kt

中调用 NTRIPClient
fun createNtripConnection(usbService: UsbService){
        ntripScope.launch {
            awaitAll(
                async {
                    Log.d(TAG, "createNtripConnection: START")
                    ntripClient.startConnection(currentGGA, usbService)
                },
                async {
                    delay(5000)
                    ntripClient.updateGGA(currentGGA)
                }
            )
        }
    }

当我 运行 方法 createNtripConnection 一切正常,但是当需要执行方法 updateGGA 时,我收到以下错误:

W/System.err: java.io.IOException: unexpected end of stream on com.android.okhttp.Address@5170b14c
        at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:203)
        at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:129)
        at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:750)
        at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:622)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:475)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:411)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:248)
        at com.hq.advian.newtwork.NTRIPClient.startConnection(NTRIPClient.kt:43)
        at com.hq.advian.viewmodel.ArViewModel$createNtripConnection.invokeSuspend(ArViewModel.kt:207)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
W/System.err:     at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
    Caused by: java.io.EOFException: \n not found: size=0 content=...
        at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:202)
        at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:188)
        ... 14 more

如果在 updateGGA 一开始我尝试关闭 inputStream,在这一行我得到 java.lang.NullPointerException。你能帮忙吗?或者有其他工具可以解决这个问题?

所以我找到了解决方案。 HttpUrlConnection不能同时读写数据。为此,您需要使用 Socket。首先你需要创建一个Socket并连接到主机:

InetAddress address = InetAddress.getByName(new URL("http://fi.nrtk.eu:7801/MSM5").getHost());
Socket socket = new Socket(address, 7801);
System.out.println(socket.isConnected());

如果您连接成功,那么您将在控制台中看到 true

现在您必须打开两个流才能与服务器通信:InputStreamOutputStream:

PrintWriter output = new PrintWriter(socket.getOutputStream(), true);
BufferedInputStream input = new BufferedInputStream(socket.getInputStream());

现在您可以发送 GET、POST 和其他请求。为此,我们将使用 PrintWriter:

String requestMessage = "GET /MSM5 HTTP/1.1\r\n";
output.println(requestMessage);

我们使用消息GET /{path} {protocol (HTTP/1.1 for example)}。在每一行的末尾,您必须使用 CRLF \r\n。然后可以根据scheme添加必要的header:

output.println("{header name}: {header value}\r\n");

output.println("Connection: Keep-Alive");
output.println("Accept-Encoding: identity");

之后就可以从服务器读取数据了。就我而言,这是无穷无尽的消息流,因此我将使用以下选项:

byte[] buffer = new byte[128];
int readBuffer = input.read(buffer);
while (readBuffer > 0) {
   System.out.println(Arrays.toString(buffer));
   readBuffer = input.read(buffer);
}

如果在读取数据的过程中需要更改header,可以重新向服务器发送字符串:

output.println("Connection: close");

完整方法如下所示:

void createSocket() {
        try {
            InetAddress address = InetAddress.getByName(new URL("http://fi.nrtk.eu:7801/MSM5").getHost());
            Socket socket = new Socket(address, 7801);
            System.out.println(socket.isConnected());

            PrintWriter output = new PrintWriter(socket.getOutputStream(), true);

            BufferedInputStream input = new BufferedInputStream(socket.getInputStream());

            String requestMessage = "GET /MSM5 HTTP/1.1\r\n";
            output.println(requestMessage);
            output.println("Ntrip-Version: Ntrip/2.0\r\n");
            output.println("Connection: Keep-Alive");
            output.println("Accept-Encoding: identity");
            output.println("\r\n");

            byte[] buffer = new byte[128];

            int readBuffer = input.read(buffer);
            while (readBuffer > 0) {
                System.out.println(Arrays.toString(buffer));
                readBuffer = input.read(buffer);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }