java - 客户端在 Java 中的套接字应用程序中无法正常工作
问题描述
我正在尝试在我的本地主机上制作一个简单的聊天应用程序。它在 CMD 上运行良好,但每当我尝试在 GUI 上构建它时,错误不会让我一个人呆着。
你看?我打开的第一个应用程序按预期工作,但是当我尝试打开另一个应用程序时,我打开的第二个应用程序将数据发送到第一个应用程序。
桂
package program;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
public class gui
{
private boolean basladi=false;
Client client;
server _server;
JFrame pencere;
JButton button;
static JTextArea area;
JTextField type;
public gui(){
pencere = new JFrame("oxChat");
pencere.setSize(640,480);
pencere.setLayout(null);
button = new JButton("gönder");
button.addActionListener( new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
if(!basladi){
basladi=true;
client = new Client("127.0.0.1",4000);
}else{
client.sendData(type.getText());
}
}
});
area = new JTextArea();
type = new JTextField();
pencere.add(type);
pencere.add(area);
pencere.add(button);
area.setBounds(0,0,640,350);
type.setBounds(0,370,640,25);
button.setBounds(640/2-80/2,400,80,30);
pencere.setVisible(true);
pencere.setResizable(false);
pencere.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
_server = new server();
_server.start(4000);
}
public static void main(String[] args) throws IOException {
gui app = new gui();
}
}
客户
package program;
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class Client
{
// initialize socket and input output streams
private Socket socket = null;
private DataInputStream input = null;
private DataOutputStream out = null;
// constructor to put ip address and port
public Client(String address, int port)
{
// establish a connection
try
{
socket = new Socket(address, port);
System.out.println("Connected");
// takes input from terminal
input = new DataInputStream(socket.getInputStream());
// sends output to the socket
out = new DataOutputStream(socket.getOutputStream());
}
catch(UnknownHostException u)
{
System.out.println(u);
}
catch(IOException i)
{
System.out.println(i);
}
}
void sendData(String data){
try{
out.writeUTF(data);
}catch(IOException i)
{
}
}
}
服务器
package program;
// A Java program for a Server
import java.net.*;
import java.io.*;
public class server
{
private Socket socket;
private ServerSocket server;
public static String data;
// constructor with port
public void start(int port){
try {
server = new ServerSocket(port);
while(true){
socket = server.accept();
new Thread (new ConnectionHandler(socket)).start();
}
}catch(IOException i){
}
}
}
class ConnectionHandler extends Thread{
gui app;
private String data;
private Socket socket = null;
private DataInputStream in = null;
private DataOutputStream out = null;
public ConnectionHandler(Socket socket){
this.socket=socket;
}
@Override
public void run() {
try
{
System.out.println("Waiting for a client ...");
System.out.println("Client accepted");
in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
out = new DataOutputStream(socket.getOutputStream());
String line = "";
// reads message from client until "Over" is sent
while (!line.equals("Over"))
{
try
{
line = in.readUTF();
app.area.append(line+"\n");
}
catch(IOException i)
{
System.out.println(i);
}
}
System.out.println("Closing connection");
// close connection
socket.close();
in.close();
}
catch(IOException i)
{
System.out.println(i);
}
}
public String getServerData(){
return data;
}
}
解决方案
再次阅读您的代码后,它只是偶然地适用于第一个实例。
让我们通过代码来看看发生了什么:
在gui.java
中,您创建一个新服务器:
_server = new server();
_server.start(4000);
依次尝试监听给定端口:
server = new ServerSocket(port);
这显然不适用于第二个实例,但是您可以捕获结果IOException
并将其丢弃:
try {
server = new ServerSocket(port);
while(true) {
socket = server.accept();
new Thread(new ConnectionHandler(socket)).start();
}
} catch(IOException i) {
// Please do something here
}
因此,第二个实例不会有在端口上列出的服务器。
您不应该只是忽略异常,但这是一个不同的问题。所以你只有一台服务器在运行。这很好。一台服务器,许多客户端。
上面的方法还有一个问题,就是这个方法永远不会返回(除非它不能绑定到端口)。
现在,当客户端向您发送数据时,服务器将在此处接收它:
while (!line.equals("Over")) {
try {
line = in.readUTF();
app.area.append(line+"\n");
} catch(IOException i) {
System.out.println(i);
}
}
您对接收到的数据所做的唯一事情就是将其添加到 gui。
其他客户端不会看到此消息。
您必须将数据发送给所有其他客户端。这需要您保留所有已连接客户端的列表。
这就是事情变得复杂的地方:
您已经共享了由多个线程访问的可变状态。
这意味着您必须使用同步。
好的,让我们这样做:
添加一个
List<ConnectionHandler>
到您的服务器类:List<ConnectionHandler> clients = new ArrayList<>();
为了更好的衡量,一个锁:
Object lock = new Object();
然后我们需要将任何新连接的客户端添加到该列表中:
socket = server.accept(); ConnectionHandler client = new ConnectionHandler(this, socket) synchronized (lock) { clients.add(client); } new Thread(client).start();
现在我们只需要一个方法来分发服务器类中的所有传入消息:
void distributeMessage(String message) { List<ConnectionHandler> clientsCopy; synchronized (lock) { clientsCopy = new ArrayList<>(clients); } for (ConnectionHandler client : clientsCopy) { client.sendMessage(message); } }
现在我们需要更改 ConnectionHandler,我们首先清理字段:
private Socket socket; private DataInputStream in; private DataOutputStream out; private server server;
这些都是我们需要的领域。
接下来我们需要改变这个类的构造函数:
public ConnectionHandler(server server, Socket socket) { this.server = server; this.socket = socket; this.in = new DataInputStream(new BufferedInputStream(socket.getInputStream())); this.out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); }
如果可能,所有字段都应在构造函数中初始化。
然后我们必须添加我们的新
sendMessage(String message)
方法:public void sendMessage(String message) { try { out.writeUTF(message); out.flush(); } catch (IOException e) { // TODO: Here you HAVE to check if the connection was closed // And if it was closed, call a method in the server class to // remove this client. e.printStackTrace(); } }
快完成了。现在客户端实际上需要收听他们收到的消息。我把它留给你去做。这与您之前在服务器中所做的基本相同。
推荐阅读
- javascript - 为什么我不能在 JavaScript 中做 array[-1]?
- python - 除了计算之外,使用现有数据框选择性地构建新数据框
- javascript - Vue/AXIOS 没有完全下载
- java - 数据库 [默认] 未找到驱动程序 - Playframework 1.4.x 中的 MySQL
- listview - 如何在 Flutter 的同一屏幕上沿 ListView 显示小部件?
- python - 如果在 MainProcess 中执行 `glfw.create_window`,为什么多进程 glfw 应用程序会停止?
- azure-active-directory - Azure:服务主体 ID 与应用程序 ID
- android - 以编程方式取消(和隐藏)Android 通知
- uml - 具有一个产品类别的在线商店的简单 UML 类图
- raku - 是否可以在 Perl 6 中使编译时代码不缓存?