需要TCPIP客户端阻止,直到收到特定的字符序列

时间:2015-12-09 16:07:16

标签: java tcp tcp-ip

我需要一个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()并搜索指定的字符串?

解决方案:已移至已接受的答案

2 个答案:

答案 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"