我正在为一项大学任务实现一个 Java 客户端-服务器应用程序,但我陷入了以下困境:我必须使用客户端-服务器,并且每当数据库中的数据发生变化时也必须更新视图。我所做的是,每当数据库发生更改时,我都会用“CHANGE IN DATA”消息通知所有客户端,然后客户端应该阅读并理解此消息,以便调用将更新其图形界面的方法。但是,或者我错误地读取了客户端的部分,或者由于某些错误,客户端不会读取“数据更改”消息,因此整个过程会卡在此时并且视图不会更新。
这里有一些相关代码! 服务器类别:
public class FinesPaymentServer implements Runnable {
private Database database;
private UserGateway userGateway;
private FineGateway fineGateway;
private DriverGateway driverGateway;
private Socket connection;
private int ID;
static ArrayList<Socket> clientsConnected;
/**
* Constructor of the class connecting to the database and initializing the socket
* @param database the database used
* @param connection the socket for the server
* @param ID the id
*/
private FinesPaymentServer(Database database, UserGateway userGateway, FineGateway fineGateway, DriverGateway driverGateway, Socket connection, int ID) {
this.connection = connection;
this.userGateway = userGateway;
this.fineGateway = fineGateway;
this.driverGateway = driverGateway;
this.database = database;
this.ID = ID;
}
/**
* Run method of the threads for each socket on the server
*/
public void run() {
try {
while(true)
readFromClient(connection);
} catch (IOException | SQLException e) {
System.out.println(e);
}
}
/**
* Read method from the client
* @param client the client socket from where to read
* @throws IOException
* @throws SQLException
*/
public void readFromClient(Socket client) throws IOException, SQLException {
BufferedInputStream is = new BufferedInputStream(client.getInputStream());
InputStreamReader reader = new InputStreamReader(is);
StringBuffer process = new StringBuffer();
int character;
while((character = reader.read()) != 13) {
process.append((char)character);
}
System.out.println("[SERVER READ]: "+process);
String[] words = process.toString().split("\\s+");
switch (process.charAt(0)) {
case 'a' :
{
int type = database.verifyLogin(words[1], words[2]);
sendMessage(client, ""+type + " ");
break;
}
case 'b' :
{
String rs = userGateway.getUsers();
sendMessage(client, rs);
break;
}
case 'c' :
{
userGateway.createUser(words[1], words[2], words[3]);
notifyClients();
break;
}
case 'd' :
{
userGateway.updateUser(words[1], words[2], words[3]);
notifyClients();
break;
}
case 'e' :
{
userGateway.deleteUser(words[1]);
notifyClients();
break;
}
}
try {
Thread.sleep(1000);
} catch (Exception e){}
String time_stamp = new java.util.Date().toString();
String returnCode = "Single Socket Server responded at " + time_stamp + (char) 13;
sendMessage(client, returnCode);
}
/**
* Method for sending messages from the server to the client
* @param client the client socket where to send the message
* @param message the message itself to be sent
* @throws IOException
*/
private void sendMessage(Socket client, String message) throws IOException {
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(client.getOutputStream()));
writer.write(message);
System.out.println("[SERVER WRITE]: "+message);
writer.flush();
}
public void notifyClients() throws IOException
{
for(Socket s : clientsConnected)
{
sendMessage(s, "CHANGE IN DATA ");
}
}
/**
* @param args the command line arguments
* @throws java.sql.SQLException
*/
public static void main(String[] args) throws SQLException {
Database database = new Database();
UserGateway userGateway = new UserGateway();
FineGateway fineGateway = new FineGateway();
DriverGateway driverGateway = new DriverGateway();
clientsConnected = new ArrayList<>();
// Setting a default port number.
int portNumber = 2015;
int count = 0;
System.out.println("Starting the multiple socket server at port: " + portNumber);
try {
ServerSocket serverSocket = new ServerSocket(portNumber);
System.out.println("Multiple Socket Server Initialized");
//Listen for clients
while(true) {
Socket client = serverSocket.accept();
clientsConnected.add(client);
Runnable runnable = new FinesPaymentServer(database, userGateway, fineGateway, driverGateway, client, ++count);
Thread thread = new Thread(runnable);
thread.start();
}
} catch (Exception e) {}
}
}
客户类别:
public class FinesPaymentClient implements Runnable {
private String hostname = "localhost";
private int port = 2015;
Socket socketClient;
AdministratorModel adminModel;
PoliceModel policeModel;
PostModel postModel;
/**
* Constructor of the class
* @param hostname the host name of the connection
* @param port the port of the connection
* @throws UnknownHostException
* @throws IOException
*/
public FinesPaymentClient(String hostname, int port, AdministratorModel adminModel, PoliceModel policeModel, PostModel postModel) throws UnknownHostException, IOException
{
this.hostname = hostname;
this.port = port;
this.adminModel = adminModel;
this.policeModel = policeModel;
this.postModel = postModel;
connect();
}
/**
* Method for connecting to the host by a socket
* @throws UnknownHostException
* @throws IOException
*/
public void connect() throws UnknownHostException, IOException {
System.out.println("Attempting to connect to " + hostname + ":" + port);
socketClient = new Socket(hostname, port);
System.out.println("Connection Established");
}
/**
* Method for reading response from the server
* @return the string read from the server
* @throws IOException
*/
public String readResponse() throws IOException {
String userInput;
BufferedReader stdIn = new BufferedReader(
new InputStreamReader(socketClient.getInputStream()));
System.out.println("[CLIENT READ]:");
while ((userInput = stdIn.readLine()) != null) {
System.out.println(userInput);
return userInput;
}
return userInput;
}
/**
* Method for closing connection between client and server
* @throws IOException
*/
public void closeConnection() throws IOException {
socketClient.close();
}
/**
* Method for writing messages to the server
* @param message the message to be sent
* @throws IOException
*/
public void writeMessage(String message) throws IOException {
String time_stamp = new java.util.Date().toString();
// Please note that we placed a char(13) at the end of process...
// we use this to let the server know we are at the end
// of the data we are sending
String process = message + (char) 13;
BufferedWriter stdOut = new BufferedWriter(
new OutputStreamWriter(socketClient.getOutputStream()));
stdOut.write(process);
System.out.println("[CLIENT WRITE]: "+process);
// We need to flush the buffer to ensure that the data will be written
// across the socket in a timely manner
stdOut.flush();
}
@Override
public void run() {
try {
String response;
while(true)
{
response = readResponse();
System.out.println("HERE"+response.substring(0, 13));
if(response.substring(0, 13).equals("CHANGE IN DATA"))
{
adminModel.setChange();
}
}
} catch (IOException e) {
System.out.println(e);
}
}
/**
* Main method of the application
* @param arg the parameters given as arguments
* @throws SQLException
* @throws UnknownHostException
* @throws IOException
*/
public static void main(String arg[]) throws SQLException, UnknownHostException, IOException {
AdministratorModel adminModel = new AdministratorModel();
PoliceModel policeModel = new PoliceModel();
PostModel postModel = new PostModel();
FinesPaymentClient client = new FinesPaymentClient("localhost", 2015, adminModel, policeModel, postModel);
Runnable client2 = new FinesPaymentClient("localhost", 2015, adminModel, policeModel, postModel);
Thread thread = new Thread(client2);
thread.start();
Login login = new Login();
ClientSide clientSide = new ClientSide(login, client, adminModel, policeModel, postModel);
}
}
客户端类:
public class ClientSide {
private final Login login;
private FinesPaymentClient client;
AdministratorModel adminModel;
PoliceModel policeModel;
PostModel postModel;
/**
* Constructor instantiating needed classes
* @param login an instance of the login class
* @param client the client needing the control logic
* @param adminModel
* @param policeModel
* @param postModel
* @throws SQLException using classes connecting to a database sql exceptions can occur
*/
public ClientSide(Login login, FinesPaymentClient client, AdministratorModel adminModel, PoliceModel policeModel, PostModel postModel) throws SQLException
{
this.login = login;
this.client = client;
this.adminModel = adminModel;
this.policeModel = policeModel;
this.postModel = postModel;
login.addButtonListener(new ButtonListener());
}
/**
* Listener for the login button. Reads, verifies and provides the interface according to logged in user type.
*/
class ButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
try
{
client.writeMessage("a " + login.field1.getText()+ " " + login.field2.getText());
String response = client.readResponse();
if(response.charAt(0) == '1')
{
login.setVisible(false);
AdministratorGUI administratorGUI = new AdministratorGUI(adminModel, client);
AdministratorController adminController = new AdministratorController(client, administratorGUI, adminModel);
}
//if user is post office employee
else if(response.charAt(0) == '2')
{
login.setVisible(false);
PostGUI postGUI = new PostGUI();
PostController postController = new PostController(client, postGUI, postModel);
}
//if user is police employee
else if(response.charAt(0) == '3')
{
login.setVisible(false);
PoliceGUI policeGUI = new PoliceGUI();
PoliceController policeController = new PoliceController(client, policeGUI, policeModel);
}
else
{
JOptionPane.showMessageDialog(null,"Login failed! Please try again!");
}
}
catch (IOException ex)
{
Logger.getLogger(ClientSide.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
我 99% 确信错误发生在客户端读取从服务器发送的消息作为通知时,但我根本不知道如何检索该消息。现在我在客户端线程运行方法中尝试过,但不起作用。其他类和其他功能工作得很好,这是我唯一的问题。您知道错误可能是什么吗?我将不胜感激任何帮助。
对于我的解决方案,我需要服务器端所有已连接客户端的列表:
public class ClientListModel {
private List<ClientHandler> liste = new LinkedList<>();
private ClientListView clientView;
public List<ClientHandler> getListe() {
return liste;
}
public void setListe(List<ClientHandler> liste) {
this.liste = liste;
}
public ClientListView getClientView() {
return clientView;
}
public void setClientView(ClientListView clientView) {
this.clientView = clientView;
}
public void update() {
clientView.update();
}
public void add(ClientHandler echoClientHandler) {
liste.add(echoClientHandler);
update();
}
public void remove(ClientHandler echoClientHandler) {
liste.remove(echoClientHandler);
update();
}
}
视图类将信息打印到控制台:
public class ClientListView {
ClientListModel clientModel;
public ClientListView() {
super();
}
public void update() {
System.out.println("*** connected clients ***");
for (ClientHandler client : clientModel.getListe()) {
System.out.println(client.getClientSocket().getInetAddress());
}
}
public ClientListModel getClientModel() {
return clientModel;
}
public void setClientModel(ClientListModel clientModel) {
this.clientModel = clientModel;
}
}
实际服务器拥有这两个类。它可以向所有注册的客户端处理程序发送更新触发器。
public class Server implements Runnable {
private Thread t;
private int port;
private ServerSocket serverSocket;
private ClientListModel clientListModel = new ClientListModel();
private ClientListView clientListView = new ClientListView();
private PostgresMock postgres;
public Server(int port) {
this.port = port;
this.postgres = new PostgresMock("me", "pass123");
// initiate mvc
getClientListModel().setClientView(clientListView);
clientListView.setClientModel(getClientListModel());
t = new Thread(this);
t.start();
}
@Override
public void run() {
try {
serverSocket = new ServerSocket(port);
System.out.println("listening on port " + port);
while (true) {
new ClientHandler(serverSocket.accept(), this);
}
} catch (IOException e) {
System.out.println(e);
}
}
public void updateClients() {
for (ClientHandler client : getClientListModel().getListe()) {
client.sendMsg("trigger update");
}
}
public void close() throws IOException {
this.serverSocket.close();
}
public ClientListModel getClientListModel() {
return clientListModel;
}
public PostgresMock getPostgres() {
return postgres;
}
public static void main(String[] args) {
Server server = new Server(5555);
}
}
这是客户端处理程序:
public class ClientHandler implements Runnable {
private Thread t;
private Socket clientSocket;
private PrintWriter out;
private BufferedReader in;
private Server server;
public ClientHandler(Socket socket, Server server) {
this.clientSocket = socket;
this.server = server;
server.getClientListModel().add(this);
t = new Thread(this);
t.start();
}
@Override
public void run() {
try {
out = new PrintWriter(clientSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
// IN
System.out.println("in: " + inputLine);
JSONArray data = server.getPostgres().execute(inputLine);
if (!isSelectStatement(inputLine)) {
server.updateClients();
}
if (data == null) {
sendMsg("nil");
continue;
}
sendMsg(data.toString());
// OUT
System.out.println("out: " + data.toString());
}
server.getClientListModel().remove(this);
} catch (IOException e) {
System.err.println(e);
server.getClientListModel().remove(this);
}
}
public Socket getClientSocket() {
return clientSocket;
}
private boolean isSelectStatement(String sql) {
return sql.toUpperCase().startsWith("SELECT") || sql.toUpperCase().startsWith("WITH");
}
public void sendMsg(String msg) {
out.println(msg);
}
}
它检测是否要执行 select 语句或其他语句。如果不是 select 语句,服务器将触发更新。
Postgres 类只是一个模拟类。
public class PostgresMock {
public PostgresMock(String nutzer, String passwort) {
}
public JSONArray execute(String sqlStatement) {
return new JSONArray();
}
}
最后是客户端类。它做了很多事情,但最重要的是它触发了实际的更新方法。
public class Client implements Runnable {
private volatile String msgOut = null;
private volatile String msgIn = null;
private volatile boolean done = true;
private int queryTimeout = 1000;
private Socket clientSocket;
private BufferedReader in;
private PrintWriter out;
private String ip;
private int port;
private Thread t;
private static volatile boolean active = true;
private static int clockRate = 1;
public Client(String ip, int port) {
this.ip = ip;
this.port = port;
t = new Thread(this);
t.start();
}
@Override
public void run() {
try {
connect();
String response;
while (active) {
if (getMsgOut() != null) {
out.println(getMsgOut());
setMsgOut(null);
}
response = readResponse();
if (response != null) {
System.out.println(java.time.LocalDateTime.now() + ": in: " + response);
}
pollResponse(response);
// slow down loop
Thread.sleep(clockRate);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void connect() {
while (clientSocket == null && active) {
try {
clientSocket = new Socket(ip, port);
out = new PrintWriter(clientSocket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
} catch (UnknownHostException e) {
System.out.println(e);
} catch (IOException e) {
System.out.println(e);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
public String readResponse() throws IOException {
String userInput = null;
while (in.ready() && (userInput = in.readLine()) != null) {
return userInput;
}
return userInput;
}
private void pollResponse(String response) {
if (response == null) {
return;
}
if (response.equals("trigger update")) {
System.out.println("do update stuff here ..."); // trigger your update event here
} else {
setMsgIn(response);
}
}
public String query(String sql, long timeout) {
while (!done) {} // I don't know if this is actually necessary
done = false;
long start = System.currentTimeMillis();
setMsgIn(null);
setMsgOut(sql);
while ((System.currentTimeMillis() - start < timeout) && getMsgIn() == null) {}
setMsgOut(null);
done = true;
return getMsgIn();
}
public String query(String sql) {
return query(sql, queryTimeout);
}
String getMsgOut() {
return msgOut;
}
void setMsgOut(String msgOut) {
this.msgOut = msgOut;
}
String getMsgIn() {
return msgIn;
}
void setMsgIn(String msgIn) {
this.msgIn = msgIn;
}
public static void main(String[] args) {
Client c = new Client("127.0.0.1", 5555);
c.query("select * from atable;");
c.query("insert into atable (avalue) values ('anything');");
}
}
我没有对代码做很多优化,但希望它有帮助。