Java:多线程与套接字

时间:2009-07-09 14:03:22

标签: java multithreading sockets

我在Java中编写了一个简单的应用程序,其中有两个节点,每个节点都有一个ServerSocket,可以打开一个端口来监听传入的连接。节点每个运行两个线程,通过在发送第一个消息时创建的持久TCP套接字向另一个节点发送1000条消息。但是,节点不会收到所有1000条消息。一个人可能会收到850而另一个人只收到650.这个数字往往会在多次运行中保持不变。

发送代码如下:

public void SendMsg(String dest, Message myMsg) {
    Socket sendsock = null;
    PrintWriter printwr = null;
    try {
        if(printwr == null) {
            sendsock = new Socket(dest, Main.rcvport);
            printwr = new PrintWriter(sendsock.getOutputStream(), true);
        }
        String msgtosend = myMsg.msgtype.toString() + "=" + Main.myaddy + "=" + myMsg.content + "\n";
        printwr.print(msgtosend);
    } catch (UnknownHostException ex) {
        System.out.println(ex);
        //DO: Terminate or restart
    } catch (IOException ex) {
        System.out.println(ex);
        //DO: Terminate or restart
    }
}

如果我使用,性能似乎有所改善                  buffwr = new BufferedWriter(printwr) 同样使用 buffwr.write(...)代替 printwr.print(...),虽然它似乎不是一个完整的解决方案数据丢失。没有例外表明数据包未被传递,因此根据发件人的说法,它们都已成功发送。

在接收端,接受的连接如下处理:

BufferedReader inbuff = new BufferedReader(new InputStreamReader(incoming.getInputStream()));

        while(running) {
            String rcvedln = inbuff.readLine();
            if(rcvedln != null) {
                count++;
                System.out.println(count);
            }
        }

读者和作者的使用方式是否存在导致问题的问题?感谢。

4 个答案:

答案 0 :(得分:4)

SendMsg()每次调用都会创建一个新套接字,因此您不使用持久TCP连接。该方法也没有关闭套接字,因此你有很多开放的集合。您可能正在达到进程可以建立的连接数限制(当对象被垃圾回收时,套接字可能无法关闭)。

最后,正如kd304指出的那样,PrintWriter的Javadoc说明了autoFlush构造函数的PrintWriter参数:“如果为true,则println,printf或format方法将刷新输出缓冲区“。您的代码没有调用执行刷新的方法。

试试这个:

public class MessageSender implements Closeable {
  private final Socket socket;
  private final PrintWriter writer;

  public MessageSender(String dest, int port) {
    socket = new Socket(dest, port);
    writer = new PrintWriter(socket.getOutputStream(), true);
  }

  public void sendMessage(Message message) {
    try {
        writer.println(message.toString());
    } catch (UnknownHostException ex) {
        System.out.println(ex);
        //DO: Terminate or restart
    } catch (IOException ex) {
        System.out.println(ex);
        //DO: Terminate or restart
    }
}

@Override
public void close() throws IOException {
  writer.close();
  socket.close();
}

注意我修改了代码,以便sendMessage()调用Message.toString()来获取格式化的消息。 sendMessage()引用Message中的字段以格式化邮件似乎不正确。您可以在toString()专门为此目的创建一个方法,而不是使用Message

这是服务器端代码:

public class Server implements Runnable {
  private final ServerSocket serverSocket;
  private final ExecutorService executor;
  private volatile boolean running = true;

  public Server(int port, ExecutorService executor) throws IOException {
    serverSocket = new ServerSocket(port);
    this.executor = executor;
  }

  @Override
  public void run() throws IOExeption {
    while (running) {
      Socket socket = serverSocket.accept();
      executor.execute(new ConnectionHandler(socket));
    }
  }

  public boolean stop(long timeout, TimeUnit unit) {
    running = false;
    executor.shutdown();
    return executor.awaitTermination(timeout, unit);
  }
}

您可以使用Executors创建ExecutorService来运行任务。请注意,ConnectionHandler需要关闭它给出的套接字。

答案 1 :(得分:1)

您是否关闭PrintWriter以刷新流?

} finally {
    printwr.close();
    sendsock.close();
}

答案 2 :(得分:0)

对不起,对不起。我不小心从代码中删除了评论。它实际上是这样的:

public void SendMsg(String dest, Message myMsg) {
Socket sendsock = null;
try {
    if(printwr == null) {
        sendsock = new Socket(dest, Main.rcvport);
        printwr = new PrintWriter(sendsock.getOutputStream(), true);
    }
    String msgtosend = myMsg.msgtype.toString() + "=" + Main.myaddy + "=" + myMsg.content + "\n";
    printwr.print(msgtosend);
} catch (UnknownHostException ex) {
    System.out.println(ex);
    //DO: Terminate or restart
} catch (IOException ex) {
    System.out.println(ex);
    //DO: Terminate or restart
}

}

printrw声明并存储在函数外部,因此一旦设置完成,就不需要sentock或重新初始化printrw。在实际的应用程序中,我正在为HashMap中的每个连接存储PrintWriter,并在SendMsg(...)函数的开头检索它。

由于连接是持久的,每次接受一个连接时,一个新线程就是午餐,它运行一个while循环来连续检查数据。这些线程和连接仅在应用程序终止后关闭。除了我之前的问题,还有更有效的方法吗?

早些时候,我实现了这个没有“\ n”的代码,而是使用了println(...)而我仍然遇到了一些没有收到消息的问题,所以我不确定导致问题的是什么。消息的发送方式如下:

public class SendPortal2 implements Runnable {
String dest = null;

SendPortal2 (String dest) {
    this.dest = dest;
}

public void run() {
        for(int i=1; i<1000; i+=2) {
            Message myMsg = new Message("Message", Main.myaddy + " " + String.valueOf(i));
            Main.myCommMgr.SendMsg(dest, myMsg);
        }
}

}

有两个这样的线程正在运行。当我刚刚再次运行代码时,一方获得了999个数据包,而另一方只获得了500个,这让我相信有时整个线程的数据可能被阻止。这可能吗?

感谢您的回复!

答案 3 :(得分:0)

如果我将一个Thread.sleep(2)放在调用SendMsg函数的for循环中,则会正确接收更多消息,但它并不总是1000.系统的资源是否可能被两个线程占用连续循环运行?