Erlang 服务器,Java 客户端 - TCP 消息被拆分? [英] Erlang server, Java client - TCP messages get split?

查看:16
本文介绍了Erlang 服务器,Java 客户端 - TCP 消息被拆分?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

正如标题所说,我有一个用 Erlang 编写的服务器,一个用 Java 编写的客户端,它们通过 TCP 进行通信.我面临的问题是 gen_tcp:recv 显然不知道何时收到来自客户端的完整"消息,因此将其拆分"为多条消息.

As the title says, I have a server written in Erlang, a client written in Java and they are communicating through TCP. The problem that I am facing is the fact that gen_tcp:recv apparently has no knowledge of when a "complete" message from the client has been received, and is therefore "splitting" it up in multiple messages.

这是我正在做的一个例子(不完整的代码,试图只保留相关部分):

This is an example of what I'm doing (Incomplete code, trying to keep it to only the relevant parts):

-module(server).
-export([start/1]).

-define(TCP_OPTIONS, [list, {packet, 0}, {active, false}, {reuseaddr, true}].

start(Port) ->
   {ok, ListenSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
   accept(ListenSocket).

accept(ListenSocket) ->
    {ok, Socket} = gen_tcp:accept(ListenSocket),
    spawn(fun() -> loop(Socket) end),
    accept(ListenSocket).

loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            io:format("Recieved: ~s~n", [Data]),
            loop(Socket);
        {error, closed} ->
            ok
    end.

Java 客户端

public class Client {
    public static void main(String[] args) {
        Socket connection = new Socket("localhost", Port);
        DataOutputStream output = new DataOutputStream(connection.getOutputStream());
        Scanner sc = new Scanner(System.in);

        while(true) {
            output.writeBytes(sc.nextLine());
        }
    }
}

结果

客户

Hello!

服务器

Received: H
Received: el
Received: lo!

我一直在寻找,如果我理解正确的话,TCP 不知道消息的大小,您需要手动设置某种分隔符.

I have been searching around and if I understand it correctly, TCP has no knowledge of the size of messages, and you need to manually set some kind of delimiter.

我不明白的是,如果我用 Erlang 编写客户端,消息似乎永远不会分开,就像这样:

What I don't get though, is that the messages never seem to split up if I write the client in Erlang instead, like this:

-module(client).
-export([start/1]).

start(Port) ->
    {ok, Socket} = gen_tcp:connect({127,0,0,1}, Port, []),
    loop(Socket).

loop(Socket) ->
    gen_tcp:send(Socket, io:get_line("> ")),
    loop(Socket).

结果

客户

Hello!

服务器

Received: Hello!

这让我想知道它是否可以在 Java 端修复?我在服务器端尝试了几种不同的输出流、写方法和套接字设置的组合,但都没有解决问题.

This makes me wonder if it is something that can be fixed on the Java side? I have tried several combinations of different output streams, write methods and socket settings on the server side, but nothing solves the problem.

此外,网络上有大量的 Erlang(聊天)服务器示例,它们不做任何分隔符的事情,尽管它们通常在两端都用 Erlang 编写.然而,他们似乎假设消息的接收就像它们被发送一样.这只是不好的做法,还是当客户端和服务器都用 Erlang 编写时,是否存在一些关于消息长度的隐藏信息?

Also, there are loads of Erlang (chat) server examples around the net where they don't do any delimiter things, although those are often written in Erlang on both ends. Nevertheless, they seem to assume that the messages are received just like they are sent. Is that just bad practice, or is there some hidden information about message length when both the client and server are written in Erlang?

如果需要进行分隔符检查,我很惊讶我找不到有关该主题的太多信息.如何以实际的方式做到这一点?

If delimiter checks are necessary, I am surprised I can't find much information on the subject. How can it be done in a practical way?

提前致谢!

推荐答案

您需要在服务器和客户端之间定义一个协议,以将 TCP 流拆分为消息.TCP 流按数据包划分,但不能保证这些数据包与您对 send/write 或 recv/read 的调用相匹配.

You need to define a protocol between your server and your client to split the TCP stream into messages. TCP stream is divided in packets, but there is no guarantee that these match your calls to send/write or recv/read.

一个简单而强大的解决方案是为所有消息添加一个长度前缀.Erlang 可以使用 {packet, 1|2|4} 选项透明地做到这一点,其中前缀被编码为 1、2 或 4 个字节.您必须在 Java 端执行编码.如果您选择 2 或 4 个字节,请注意长度应以大端格式编码,与 DataOutputStream.outputShort(int)DataOutputStream 使用的字节顺序相同.outputInt(int) java 方法.

A simple and robust solution is to prefix all messages with a length. Erlang can do this transparently with {packet, 1|2|4} option, where the prefix is encoded on 1, 2 or 4 bytes. You will have to perform the encoding on the Java side. If you opt for 2 or 4 bytes, please be aware that the length should be encoded in big-endian format, the same byte-order used by DataOutputStream.outputShort(int) and DataOutputStream.outputInt(int) java methods.

但是,从您的实现来看,您确实有一个隐式协议:您希望服务器分别处理每一行.

However, it seems from your implementations that you do have an implicit protocol: you want the server to process each line separately.

幸运的是,这也被 Erlang 透明地处理了.您只需要传递 {packet, line} 选项.但是,您可能需要调整接收缓冲区,因为该缓冲区将被截断的行更长.这可以通过 {recbuf, N} 选项来完成.

This is fortunately also handled transparently by Erlang. You simply need to pass {packet, line} option. You might need to adjust the receive buffer, however, as lines longer that this buffer will be truncated. This can be done with {recbuf, N} option.

因此,只需重新定义您的选项即可满足您的需求.

So just redefining your options should do what you want.

-define(MAX_LINE_SIZE, 512).
-define(TCP_OPTIONS, [list, {packet, line}, {active, false}, {reuseaddr, true}, {recbuf, ?MAX_LINE_SIZE}].

这篇关于Erlang 服务器,Java 客户端 - TCP 消息被拆分?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆