多线程多客户端聊天服务器

时间:2013-04-17 04:43:38

标签: java multithreading sockets chat outputstream

标题很糟糕我知道,但让我解释一下我的情况。好的,我正在制作一台靠近侧面的服务器并与我的Minecraft客户端进行通信。我可以让一个客户端连接并传输数据和第四个。当我尝试连接第二个客户端时,我收到错误:

java.net.SocketException: Connection reset by peer: socket write error
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(Unknown Source)
at java.net.SocketOutputStream.write(Unknown Source)
at java.io.ObjectOutputStream$BlockDataOutputStream.drain(Unknown Source)
at java.io.ObjectOutputStream$BlockDataOutputStream.setBlockDataMode(Unknown Source)
at java.io.ObjectOutputStream.<init>(Unknown Source)
at ConnectionHandler.setupStreams(ConnectionHandler.java:71)
at ConnectionHandler.HandleChat(ConnectionHandler.java:30)
at ConnectionHandler.<init>(ConnectionHandler.java:23)
at Server.waitForConnection(Server.java:118)
at Server.RunAll(Server.java:65)
at Server.run(Server.java:50)
at ServerTest.main(ServerTest.java:9)

现在结果是,结果是:

setupStreams:第71行

public static void setupStreams() throws IOException
{
    for(int i = 1; i <= Server.ConnectionArray.size(); i++)
    {
        Socket TempSocket = (Socket) Server.ConnectionArray.get(i - 1);
        output = new ObjectOutputStream(TempSocket.getOutputStream());//Line 71
        output.flush();
        input = new ObjectInputStream(TempSocket.getInputStream());
        whileChatting();
    }
}

现在我觉得最好解释一下这些代码是如何设置的:

它从名为ServerTest.Java的类开始 代码:

public class ServerTest
{
    public static void main(String[] args)
    {
        Server Minecraft = new Server();
        Minecraft.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Minecraft.run();
    }
}

我把对象称为“我的世界”。

现在它引用的类也实现了Runnable类,并以run()方法开头。这是方法:

@Override
public void run()
{  
    RunAll();
}

public void RunAll()
{
    ConnectionHandler.showMessage("Waiting for client connections...");
    try
    {

        server = new ServerSocket(1337, 10);
        while(true)
        {
            try 
            {
                waitForConnection();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
    catch(IOException Exception)
    {
        Exception.printStackTrace();
    }
    finally
    {
       ConnectionHandler.closeCrap();
    }
}

现在从这里等待连接,我认为它反复循环,但我不知道。以下是该方法的代码:

public void waitForConnection() throws IOException
{
   connection = server.accept();
   ConnectionArray.add(connection);
   ConnectionHandler connections = new ConnectionHandler(connection);
   connection = null; 
}

现在我认为这个方法所做的是接受来自客户端的传入连接然后将该连接添加到我的套接字数组,然后将套接字交给处理程序类,然后将原始套接字设置回Null以等待另一个传入连接。

现在这将我们带到ConnectionHandler类。 在这里它需要一个套接字X并将其设置为全局套接字,如下所示:

static Socket Socket;
public PrintWriter Out;
String Message = "";
public static ObjectOutputStream output;
public static ObjectInputStream input;
public static int Players = 0;

public ConnectionHandler(Socket X)
{
    this.Socket = X;
    HandleChat();
}

该类还实现了Runnable类。但正如您所看到的那样,它将全局套接字设置为X,然后运行HandleChat();这是方法:

public static void HandleChat()
{
    try
    {
        setupStreams();
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }

}

正如您所看到的,正在运行的是setupStreams();方法。这是方法:

public static void setupStreams() throws IOException
{
    for(int i = 1; i <= Server.ConnectionArray.size(); i++)
    {
        Socket TempSocket = (Socket) Server.ConnectionArray.get(i - 1);
        output = new ObjectOutputStream(TempSocket.getOutputStream());
        output.flush();
        input = new ObjectInputStream(TempSocket.getInputStream());
        whileChatting();
    }
}

现在我“思考”这个方法创建了一个名为TempSocket的新套接字,并使其成为数组中的最新连接,然后将输出和输入流设置为该套接字,从而为该单个客户端生成一个套接字,然后运行whileChatting();以下是生成的代码:

public static void whileChatting() throws IOException
{
    String message = "";
    do
     {
         try
         {
             message = (String) input.readObject();
             showMessage("\n" + message);
             sendMessage("", message); 
         }
         catch(ClassNotFoundException classNotFoundException)
         {
             ConnectionHandler.showMessage("\n idk wtf that user sent!");
         }
      }while(!message.equals("/stop"));
}

再次与“思考”在这里。我认为read对象被设置为String消息;然后被发送到客户端并显示在控制台屏幕上。 (Swing)以下是两种方法(发送)和(显示)的代码:

public static void sendMessage(String message, String returnedMessage)
{
    try
    {
    if(!message.isEmpty())
    {
        output.writeObject("\247c[Server]\247d " + message);
    }
    else
    {
        output.writeObject(returnedMessage);
    }
        output.flush();
        showMessage("\n[Server] " + message);
    }
    catch(IOException ioException)
    {
        Server.chatWindow.append("\n ERROR: DUDE I CANT SEND THAT MESSAGE");
    }
 }

static void showMessage(final String text)
   {
      SwingUtilities.invokeLater
      (
         new Runnable()
         {
            public void run()
            {
                Server.chatWindow.append(text);
            }
         }
      );
   }

现在你可以在这里看到,当我想在客户端显示消息时;我希望它看起来像:[服务器]文本。你也会注意到它也是sys返回的消息。我将客户端设置为仅发送到服务器然后显示屏幕上返回的内容,所以我可以说我输入“Hay”它会在发送时在我的屏幕上显示任何内容,但是一旦服务器收到它,它将向我发送确切的文本大家。那就是sendMessage();方法。现在显示方法相当简单。它在收到后显示文字。

简而言之,这就是代码。我宁愿给你留下太多的信息,而不是让你坐在这里想知道发生了什么。我将再次更深入地解释我的情况。我正在制作一个与我的Minecraft客户端通信的服务器。一个客户端连接正常,但当另一个客户端尝试连接错误时,当第一个客户端断开连接时,服务器将不允许客户端重新连接。我认为这可能与连接数组有关,并且在断开连接时没有从数组中删除套接字但我不知道。最后,我的问题是如何解决这些错误?提前感谢我对此问题的任何回复。

3 个答案:

答案 0 :(得分:0)

似乎在每个用户连接上调用setupStreams循环遍历所有连接。

这意味着如果用户1连接,则为用户打开输出套接字 第二个用户再次连接您循环所有连接并再次打开已经开启的用户1的输出流。 你需要setupStreams你不能为用户打开两次输出 我也认为你这里有错误

   for(int i = 1; i <= Server.ConnectionArray.size(); i++)

从1开始,而不是从0开始,并与&lt; = not&lt;进行比较。 我不知道为什么,ConnectionArray的类型是什么。

答案 1 :(得分:0)

public void waitForConnection() throws IOException
{
   connection = server.accept();
   ConnectionArray.add(connection);
   ConnectionHandler connections = new ConnectionHandler(connection);
   connection = null; 
}
找不到

connection,因此这不是实际代码,或者您将此临时变量存储在成员或全局中。在任何一种情况下,增加临时变量的范围以跨越不同的功能是不好的,多线程就像杂耍链锯一样。 BTW:将连接处理程序存储在列表中比创建它们并将其套接字存储在列表中更自然。这是一个责任不明确的情况,导致代码不清楚和错误。

虽然上述情况仍然无害,但仍然显示出危险和危险的风格。你继续使用这种风格(至少它是一致的!)“......它需要一个套接字X并将其设置为全局套接字,如图所示......”:

static Socket Socket;

public ConnectionHandler(Socket X)
{
    this.Socket = X;
}

使Socket Socket静态,它在此类的所有实例之间共享,这显然不是您想要的。你现在应该知道为什么全局变量被认为是坏的,为什么class-statics只是伪装的全局变量。

摘要:决定谁访问哪些部分。如果它只是一个对象,则使部件成为私有成员,因此编译器确保它实际上只是一个对象。尽量不要分享国家。如果多个对象和线程访问一个对象,请计划它们如何不以其他方式进入。

答案 2 :(得分:0)

我建议使用某种API,因为这只是(抱歉)凌乱的代码。This 是我使用的那个。它为您完成所有事情,它创建线程,打开套接字,它是基于事件的!因此,您不必遍历读取所有数据的所有套接字,当有可用的内容时,您会收到通知!