问题描述
我需要一个 java 7 TCP/IP 客户端,它会阻塞直到它接收到用户指定的字符序列(在我的例子中是消息终止符/分隔符 - 这会自动将数据分块"成单独的消息以供进一步处理).我预计这将是网络上免费提供的非常标准的代码 - 但到目前为止还没有运气.
I need a java 7 TCP/IP client that will block until it receives a user specified character sequence (in my case a message terminator/separator - this would automatically "chunk" the data into individual messages for further processing). I expected that this would be very standard code freely available on the web - but so far no luck.
更复杂的是,使用标准行分隔符(例如 Oracle KnockKnock Client 中的 readLine())分块"接收到的数据是不可能的,因为这些字符是消息中的有效数据.消息格式是国际标准,无法更改.
Complicating things, "chunking" the received data using standard line separators (e.g. readLine() in Oracle's KnockKnock Client), is not possible since those characters are valid data inside the messages. The message format is an international standard and can't be changed.
在尝试了几件事后(见下文),我想知道我是否采取了正确的方法.是否有一个免费软件示例可以让我从中汲取灵感?或者也许一个满足我需求的类已经存在于rt.jar"或其他地方的深处.(顺便说一句,我使用 eclipse 来查看 rt.jar 的内容 - 大量的包/类(根据 http://www.findjar.com/jar/com.sun/jars/rt.jar.html?all=true JVM 6 包含 13200+类)使得手动搜索不切实际).
After trying a few things (see below) I'm wondering if I'm taking the right approach. Is there a freeware example somewhere that I could draw on for inspiration? Or perhaps aclass meeting my needs already exists somewhere in the depths of "rt.jar" or elsewhere. (BTW I used eclipse to take a look at rt.jar's contents - the huge number of packages/classes (according to http://www.findjar.com/jar/com.sun/jars/rt.jar.html?all=true JVM 6 contains 13200+ classes) makes a manual search impractical).
我使用 Oracle 示例KnockKnock"客户端作为起点.我的第一个想法是只需要修改一行:
I've used Oracles example "KnockKnock" client as a starting point. My first thought was that all that would be necessary is to modify one line:
while ( (fromServer = in.readLine()) != null )
类似于:
while ( (fromServer = in.readLine( separator = UserSpecifiedRegExValue )) != null )
不幸的是,这种非常有用的 readLine() 重载/泛化在 Java 中不存在.
Unfortunately this extremely useful overloading/generalization of readLine() does not exist in Java.
Oracle 的示例有效,因为 readLine() 会阻塞,直到它在 TCP/IP 链接上接收到行分隔符值.我的想法是 readLine() 的通用版本也会阻塞,直到它收到用户指定的字符串(即消息终止符),从而准确地给我我想要的东西.由于该方法不可用,我的下一个想法是将 readLine() 替换为 getNextMessage() 函数,该函数将阻塞,直到 TCP/IP 接收到用户指定的字符串.根据其他帖子,我想出了这个功能:
Oracle's example works because readLine() blocks until it receives the line separator value on the TCP/IP link. My thinking was that a generalized verson of readLine() would also block until it received the user specified character string (i.e. the message terminator) thus giving me exactly what I want. Since that approach isn't available my next thought was to replace readLine() with a getNextMessage() function that would block until the user specified character string was received by TCP/IP. Based on other posts I came up with this function:
static String getNextMessage( java.io.BufferedReader MessageSource,
String EndOfMessage_RegEx )
{
try ( java.util.Scanner s = new java.util.Scanner( MessageSource ) )
{
return s.useDelimiter( EndOfMessage_RegEx ).hasNext() ? s.next() : "";
}
}
并通过模拟 readLine() 进行测试,传入 O/S 特定的行分隔符,如在此变体中所做的那样:
and tested it by emulating readLine(), passing in the O/S specific line separator, as done in this variant:
final static String LineSeparator = System.getProperty( "line.separator" ); // LineSeparator = ODOA (<CR><LF>) on Win7
final static String MessageSeparator = Pattern.quote( LineSeparator ); // MessageSeparator = 5C510D0A5C45 (the RegEx string "\Q<CR><LF>\E")
final static Pattern EndOfMessageRegEx = Pattern.compile( MessageSeparator );
static String getNextMessage( java.io.BufferedReader MessageSource )
// This function needs to block until a complete message (terminated by
// "EndOfMessageRegEx") is received by TCPIP from the other machine.
{
try ( java.util.Scanner s = new java.util.Scanner( MessageSource ).useDelimiter( EndOfMessageRegEx ) )
{
if ( s.hasNext() )
{
return s.next();
}
else
{
return "";
}
}
}
不幸的是,两个版本总是返回空字符串,立即终止我的客户端 - 如果 hasNext() 没有阻塞,这是有道理的.(hasNext() 文档说它可能" - 即不保证 - 阻止.)我如何获得阻止效果?
Unfortunately both versions always return the null string, immediately terminating my client - which makes sense if hasNext() does not block. (The hasNext() documentation says it "may" - i.e. not guaranteed to - block.) How do I get the blocking effect?
我在两个版本中看到的另一个问题是,每次调用该函数时,它们都毫无意义地重新创建扫描仪.
Another problem I see with both versions is that they pointlessly recreate a scanner every time the function is invoked.
或者我被迫使用更原始的方法来创建缓冲区,使用 .read() 并搜索指定的字符串?
Or am I forced into using the much more primitive approach of creating a buffer, using .read() and searching for the specified character string instead?
解决方案:移至已接受的答案
SOLUTION: Moved to accepted answer
推荐答案
根据@kayman 的建议,解决方案已移至此处并改进为使用 InputStreamReader 的字符编码选项.在我的情况下,编码是预先确定的,您可能需要考虑使用 getEncoding().
As per @kayman's suggestions, the solution has been moved here and improved to use InputStreamReader's character encoding option. In my case the encoding is predetermined, you may need to look at using getEncoding() instead.
这段代码,结合使用 Scanner 的 useDelimiter() 和 \Q\E 形式的正则表达式(见下文),当我使用 System.getProperty("line.separator") 的结果作为用户时对我有用指定的行分隔符:
This code, combined with using Scanner's useDelimiter() and the \Q\E form of regex expression (see below), worked for me when I used the results of System.getProperty( "line.separator" ) as the user specified line separator:
import java.io.*;
import java.net.*;
import java.util.Scanner;
import java.util.regex.Pattern;
///------------------------------------------------------------------------------
public class ZZ
{
final static String LineSeparator = System.getProperty( "line.separator" ); // ODOA (<CR><LF>) on Win7
final static String MessageSeparator = Pattern.quote( LineSeparator ); // 5C510D0A5C45 = RegEx string "\Q<CR><LF>\E" on Win7
final static Pattern EndOfMessageRegEx = Pattern.compile( MessageSeparator );
final static String CharacterEncoding = "US-ASCII"; // or UTF-8, UTF-16, ISO-8859-1, etc,
//----------------------------------------------------------------------------------
public static void main( String[] args )
throws IOException
{
String hostName = "localhost"; // = 127.0.0.1
int portNumber = 14576;
try (
Socket TcpipLink = new Socket( hostName, portNumber );
BufferedReader FromServer = new BufferedReader( new InputStreamReader( TcpipLink.getInputStream(), CharacterEncoding ) );
Scanner ReceivedData = new Scanner( FromServer ).useDelimiter( EndOfMessageRegEx );
) {
String ReceivedMessage;
while ( (ReceivedMessage = ReceivedData.next()) != null ) {
//Process the Inbound message
}
System.out.println( "Client fell out off message handler loop" ); // should never get here
}
catch ( UnknownHostException e ) {
System.err.println( "Don't know about host " + hostName );
System.exit( 1 );
}
catch ( IOException e ) {
System.err.println( "Could not connect to " + hostName + "on port" + portNumber );
System.exit( 1 );
}
System.out.println( "Client exited" );
} // end function main()
} // end class "ZZ"
这篇关于需要 TCPIP 客户端阻塞直到接收到特定的字符序列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!