我需要一个java 7 TCP / IP客户端,它将阻塞,直到它收到用户指定的字符序列(在我的情况下是一个消息终止符/分隔符 - 这会自动将数据“块”成单个消息以便进一步处理)。我预计这将是网上免费提供的非常标准的代码 - 但到目前为止还没有运气。
使事情变得复杂,使用标准行分隔符(例如Oracle的KnockKnock客户端中的readLine())“分块”接收的数据是不可能的,因为这些字符是消息中的有效数据。消息格式是国际标准,无法更改。
在尝试了一些事情后(见下文),我想知道我是否采取了正确的方法。在某个地方是否有一个免费软件示例我可以借鉴它的灵感?或者也许是 班级会议我的需求已经存在于“rt.jar”或其他地方的某个地方。 (顺便说一下,我使用eclipse来看看rt.jar的内容 - 大量的包/类(根据http://www.findjar.com/jar/com.sun/jars/rt.jar.html?all=true JVM 6包含13200+类)使得手动搜索变得不切实际。)
我使用Oracles示例“KnockKnock”客户端作为起点。我的第一个想法是,所有必要的是修改一行:
while ( (fromServer = in.readLine()) != null )
类似于:
while ( (fromServer = in.readLine( separator = UserSpecifiedRegExValue )) != null )
不幸的是,Java中不存在readLine()非常有用的重载/泛化。
Oracle的示例有效,因为readLine()会阻塞,直到它在TCP / IP链接上接收到行分隔符值。我的想法是readLine()的通用版本也会阻塞,直到它收到用户指定的字符串(即消息终止符),从而给我我想要的内容。由于该方法不可用,我的下一个想法是用getNextMessage()函数替换readLine(),该函数将阻塞,直到TCP / IP收到用户指定的字符串。根据其他帖子,我提出了这个功能:
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特定的行分隔符,如此变体中所做的那样:
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()文档说“可能” - 即不保证 - 阻止。)如何获得阻止效果?
我在两个版本中看到的另一个问题是,每次调用函数时,它们都会毫无意义地重新创建扫描程序。
或者我是否被迫使用更原始的创建缓冲区的方法,使用.read()并搜索指定的字符串?
解决方案:已移至已接受的答案
答案 0 :(得分:1)
考虑从PushbackInputStream派生的这个InputStream:
public static class TerminatorInputString extends PushbackInputStream {
private String terminator;
public TerminatorInputString(InputStream inputStream, String string) {
super(inputStream, 256);
terminator = string;
}
public String nextMessage() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] data = new byte[8];
int len = read(data, 0, data.length);
while(len > 0) {
baos.write(data, 0, len);
byte[] all = baos.toByteArray();
int idx = baos.toString().indexOf(terminator);
if(idx >= 0) {
String message = baos.toString().substring(0, idx);
byte[] unread = new byte[all.length-idx-terminator.length()];
System.arraycopy(all, idx+terminator.length(), unread, 0, unread.length);
super.unread(unread);
return message;
}
len = read(data, 0, data.length);
}
baos.flush();
return new String(baos.toByteArray());
}
}
读取终止符之前读取,然后跳过终止符并在此之后继续。流结束将关闭最终消息。
测试框架:
public static void main(String[] args) {
try {
//System.in is a bad stream (if used in eclipse at least)
// - it will only flush and make data available
// on new line
TerminatorInputString tis = new TerminatorInputString(System.in, "SCHWARZENEGGER");
String message = tis.nextMessage();
while(message != null) {
System.out.println("MSG:>" + message + "<");
message = tis.nextMessage();
}
} catch (Exception e) {
e.printStackTrace();
}
}
使用此输入
oneSCHWARZENEGGERtwoSCHWARZENEGGERthreeSCHWARZENEGGER
生成此输出:
MSG:>one<
MSG:>two<
MSG:>three<
答案 1 :(得分:1)
根据@ kayman的建议,该解决方案已移至此处并进行了改进,以使用InputStreamReader的字符编码选项。在我的情况下,编码是预先确定的,您可能需要使用getEncoding()代替。
当我使用System.getProperty(“line.separator”)的结果作为用户时,此代码结合使用Scanner的useDelimiter()和正则表达式的\ Q \ E形式(见下文),对我有用指定的行分隔符:
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"