Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/sockets/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java:套接字消息丢失_Java_Sockets - Fatal编程技术网

Java:套接字消息丢失

Java:套接字消息丢失,java,sockets,Java,Sockets,我们正在用Java(1.6)开发一个服务器应用程序,它是一个事务服务器,通过TCP套接字侦听连接。每个新连接创建一个新线程,该线程在连接关闭之前保持活动状态。每个客户机都会将事务发送到要处理的服务器,然后将响应发送回客户机 这个很好用。当我们想通过同一个套接字发送许多异步事务(或消息)时,问题就出现了。我编写了一个小应用程序,它以10毫秒的间隔发送1000个事务。应用程序是异步的,所以发送消息,响应在中间。 这是来自处理输入消息并将其发送到另一个要处理的组件(此组件具有线程池)的部分的代码: p

我们正在用Java(1.6)开发一个服务器应用程序,它是一个事务服务器,通过TCP套接字侦听连接。每个新连接创建一个新线程,该线程在连接关闭之前保持活动状态。每个客户机都会将事务发送到要处理的服务器,然后将响应发送回客户机

这个很好用。当我们想通过同一个套接字发送许多异步事务(或消息)时,问题就出现了。我编写了一个小应用程序,它以10毫秒的间隔发送1000个事务。应用程序是异步的,所以发送消息,响应在中间。 这是来自处理输入消息并将其发送到另一个要处理的组件(此组件具有线程池)的部分的代码:

public void run(){
...
...
socketBuf=新的BufferedInputStream(输入);
bas=新的ByteArrayOutputStream();
而((字节读取=socketBuf.read(缓冲区))!=-1){
如果(字节数_读取<0){
错误(“尝试从套接字读取,read()返回<0,正在关闭套接字”);
返回;
}
写入(缓冲区,0,字节\读取);
打破
}
如果(字节读取>=0){
paos.flush();
字节数据[]=bas.toByteArray();
如果(data.length>0){
GWTranData tData=posMessage.decode(数据,false);
如果(tData.getMessageType()>0){
//发送至在线前管理器进行处理
PreOnlineJob newJob=新的PreOnlineJob(tData);
newJob.addJobStatusListener(此);
GWServer.getPreOnlineInstance().addJob(newJob);
}
}
}
否则{
clientSocket.close();
打破
}
}虽然(正确);
} 
在短时间内发送许多事务时,我们面临的问题是一些消息丢失,无法到达服务器。通过深入分析,我们发现当消息发送速度过快时,缓冲区中有多条消息,因此data[]有两条或多条消息,但只执行一条。发送的消息大小为200字节,因此512的缓冲区就足够了

我实现套接字读取的方式有问题吗?有更好的办法吗


谢谢大家。

问题在于如何使用从套接字读取的字节。你的假设是每次阅读都会收到一条“消息”。这种假设是错误的——TCP不知道应用程序消息的边界,但会给您一个字节流,因此您可以一次获得多条消息,或者一条消息的一部分,或者两者兼而有之

您必须缓冲接收到的流中未处理的部分,检查是否收到完整的消息,在收到消息之前再阅读一些内容,处理消息,然后在循环中继续

编辑0: 有几种方法可以在TCP之上设计应用程序级协议:

  • 固定长度消息(简单)
  • 分隔消息(需要明确的字节序列来指定消息的结束/开始,如FIX中的
    SOH
    ,或HTTP中的
    \r\n
  • 正如@Thomas在评论中所建议的,以长度为前缀的消息
  • “自我描述”的消息——比如s表达式之类的,需要解析
  • 也许其他人

您需要调整代码,以便在消息到达时处理它们。目前,您正在将从connect到EOS的整个流累积到一个巨大的字节数组中,然后处理它,就好像它只包含一条消息一样。这不仅从“丢失”信息的角度来看是错误的(丢失信息的是你),而且是对时间和空间的极大浪费。在处理第一条消息之前,无需等待EOS。您需要弄清楚如何读取流,直到您正好收到一条消息,处理它,然后重复处理下一条消息,在EOS终止。

好的,我明白了,但如果我们不能真正确定消息的边界?我们有不同的消息类型和大小,并不确切知道每条消息的开始或结束位置……一个简单的规则是将消息的大小(以字节为单位)写在消息前面,然后再写消息。因此,您的阅读服务器知道您的邮件有多大,并且可以完全阅读邮件。是的,但在这种情况下,我们从终端接收邮件,并且无法修改终端协议,无法要求终端在开始时添加此信息:(好的,谢谢Nikolai,我们将看看是否可以在邮件末尾添加一些字符。
public void run() {
...
...

  socketBuf = new BufferedInputStream(input);
  baos = new ByteArrayOutputStream();

  while ((bytes_read = socketBuf.read(buffer)) != -1) {

    if (bytes_read < 0) {
        log.error("Tried to read from socket, read() returned < 0,  Closing socket.");
        return;
    }

    baos.write(buffer, 0, bytes_read);
    break;
  }

  if (bytes_read >= 0) {

    baos.flush();
    byte data[] = baos.toByteArray();

    if (data.length > 0) {                      
        GWTranData tData = posMessage.decode(data, false);   
        if (tData.getMessageType() > 0) {

            // Send to the Pre-Online Manager to be processed                       
            PreOnlineJob newJob = new PreOnlineJob(tData);
            newJob.addJobStatusListener(this);
            GWServer.getPreOnlineInstance().addJob(newJob);
        }
    }
  }
  else {
    clientSocket.close();
    break;
  }

} while(true);
  }