0、前言
没有前言,直接开搞!!!
1、准备
集成开发环境:IDEA
JDK:1.8
2、创建项目
3、 项目结构
需创建项目文件如下图
4、代码
ClientUI.java
package client;
import javax.swing.*;
import java.awt.*;
/**
* Echo客户机:根据Echo协议,向服务器发送消息,接收服务器回送的消息
*
* @author KonBAI
* @version 1.0
* Create by 2020/04/21 16:25
*/
public class ClientUI extends JFrame {
public static void main(String[] args) {
// TODO 在窗口标题里写上自己的学号
JFrame frame = new JFrame("Client: 1707");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MyPanel());
// 设置窗口宽度和高度
frame.setPreferredSize(new Dimension(600, 300));
frame.pack();
frame.setVisible(true);
}
}
MyListError.java
package client;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
/**
* @author KonBAI
* @version 1.0
* Create by 2020/04/21 21:03
*/
class MyListError implements ActionListener {
MyPanel myPanel;
public MyListError(MyPanel myPanel) {
this.myPanel = myPanel;
}
@Override
public void actionPerformed(ActionEvent e) {
// 从文本框中读取用户输入
String sendStr = myPanel.tf.getText();
// 将文本框中的字符清空
myPanel.tf.setText("");
// TODO 向服务器发送字符串
myPanel.out.println(sendStr);
myPanel.out.flush();
myPanel.ta.append("To server: " + sendStr + "\n");// 将字符串显示在文本区域
// TODO 接收服务器回复的字符串,并显示在文本区域
try {
myPanel.ta.append(myPanel.in.readLine() + '\n');
} catch (IOException ex) {
System.out.println("异常信息:" + ex.getMessage());
}
}
}
MyPanel.java
package client;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
/**
* @author KonBAI
* @version 1.0
* Create by 2020/04/21 21:02
*/
class MyPanel extends JPanel {
JTextField tf;
JTextArea ta;
/**
* 声明客户机套接字
*/
Socket clientSocket = null;
/**
* 声明网络输入流
*/
BufferedReader in;
/**
* 声明网络输出流
*/
PrintWriter out;
public MyPanel() {
this.setLayout(new BorderLayout());
JPanel p1 = new JPanel();
JLabel lb1 = new JLabel();
lb1.setText("消息:");
p1.add(lb1);
tf = new JTextField(25);
p1.add(tf);
JButton btn = new JButton("发送");
btn.addActionListener(new MyListError(this));
p1.add(btn);
this.add(p1, BorderLayout.NORTH);
JPanel p2 = new JPanel();
p2.setLayout(new BorderLayout());
JScrollPane sp1 = new JScrollPane();
sp1.setPreferredSize(new Dimension(8, 250));
ta = new JTextArea(5, 10);
ta.setEditable(false);
sp1.setViewportView(ta);
p2.add(sp1, BorderLayout.CENTER);
this.add(p2, BorderLayout.CENTER);
try {
clientSocket = new Socket();
// TODO 新建SocketAddress的对象,指定要连接的服务器;
// 2)调用Socket的方法连接服务器
SocketAddress address = new InetSocketAddress("localhost", 1024);
// 连接服务器
clientSocket.connect(address);
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
// TODO 上面已给出输入流的代码,请补充输出流的
out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
} catch (IOException ex) {
System.out.println("异常信息:" + ex.getMessage());
}
}
}
Layout.java
package server;
import javax.swing.*;
/**
* 布局类
*
* @author KonBAI
* @version 1.0
* Create by 2020/04/21 22:50
*/
public class Layout {
private JButton btnStart;
private JLabel jLabel1;
private JLabel jLabel2;
private JScrollPane jScrollPane1;
/**
* 创建内容面板容器
*/
private JPanel midPanel;
private JPanel topPanel;
public static JTextArea txtArea;
private JTextField txtHostName;
private JTextField txtHostPort;
public Layout() {
this.btnStart = new JButton();
this.jLabel1 = new JLabel();
this.jLabel2 = new JLabel();
this.jScrollPane1 = new JScrollPane();
this.midPanel = new JPanel();
this.topPanel = new JPanel();
this.txtHostName = new JTextField();
this.txtHostPort = new JTextField();
}
public JButton getBtnStart() {
return btnStart;
}
public void setBtnStart(JButton btnStart) {
this.btnStart = btnStart;
}
public JLabel getjLabel1() {
return jLabel1;
}
public void setjLabel1(JLabel jLabel1) {
this.jLabel1 = jLabel1;
}
public JLabel getjLabel2() {
return jLabel2;
}
public void setjLabel2(JLabel jLabel2) {
this.jLabel2 = jLabel2;
}
public JScrollPane getjScrollPane1() {
return jScrollPane1;
}
public void setjScrollPane1(JScrollPane jScrollPane1) {
this.jScrollPane1 = jScrollPane1;
}
public JPanel getMidPanel() {
return midPanel;
}
public void setMidPanel(JPanel midPanel) {
this.midPanel = midPanel;
}
public JPanel getTopPanel() {
return topPanel;
}
public void setTopPanel(JPanel topPanel) {
this.topPanel = topPanel;
}
public static JTextArea getTxtArea() {
return txtArea;
}
public static void setTxtArea(JTextArea txtArea) {
Layout.txtArea = txtArea;
}
public JTextField getTxtHostName() {
return txtHostName;
}
public void setTxtHostName(JTextField txtHostName) {
this.txtHostName = txtHostName;
}
public JTextField getTxtHostPort() {
return txtHostPort;
}
public void setTxtHostPort(JTextField txtHostPort) {
this.txtHostPort = txtHostPort;
}
}
ServerUI.java
package server;
import utils.ClientThread;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.*;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Echo服务器:根据Echo协议,接收来自客户机消息,并立即回送
*
* @author KonBAI
* @version 1.0
* Create by 2020/04/21 17:12
*/
public class ServerUI extends JFrame {
/**
* 侦听套接字
*/
private ServerSocket listenSocket = null;
/**
* 与客户机对话的套接字
*/
private Socket toClientSocket = null;
/**
* 客户数量编号
*/
public static Integer clientCounts = 0;
/**
* JLabel
*/
private Layout layout;
/**
* 用户编号
*/
public static JTextArea txtArea;
public ServerUI() {
initComponents();
}
@SuppressWarnings("unchecked")
private void initComponents() {
layout = new Layout();
txtArea = new JTextArea();
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setTitle("My Server: 1707");
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent evt) {
formWindowClosing(evt);
}
});
setPreferredSize(new Dimension(600, 500));
layout.getTopPanel().setBorder(BorderFactory.createTitledBorder(null, "Server Info Border", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION));
layout.getjLabel1().setText("Host Name:");
layout.getTxtHostName().setText("localhost");
layout.getjLabel2().setText("Port:");
layout.getTxtHostPort().setText("1024");
layout.getBtnStart().setText("Start");
layout.getBtnStart().addActionListener(evt -> btnStartActionPerformed(evt));
// 创建分组布局
GroupLayout topPanelLayout = new GroupLayout(layout.getTopPanel());
layout.getTopPanel().setLayout(topPanelLayout);
/**
* 水平组(仅确定 X 轴方向的坐标/排列方式)
*/
topPanelLayout.setHorizontalGroup(
topPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(topPanelLayout.createSequentialGroup()
.addContainerGap()
.addComponent(layout.getjLabel1())
.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(layout.getTxtHostName(), GroupLayout.DEFAULT_SIZE, 131, Short.MAX_VALUE)
.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(layout.getjLabel2())
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addComponent(layout.getTxtHostPort(), GroupLayout.PREFERRED_SIZE, 39, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(layout.getBtnStart())
.addContainerGap())
);
/**
* 垂直组(仅确定 Y 轴方向的坐标/排列方式)
*/
topPanelLayout.setVerticalGroup(
topPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addGroup(topPanelLayout.createSequentialGroup()
.addContainerGap()
.addGroup(topPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(layout.getjLabel1())
.addComponent(layout.getTxtHostName(), GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addComponent(layout.getjLabel2())
.addComponent(layout.getTxtHostPort(), GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addComponent(layout.getBtnStart()))
.addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
getContentPane().add(layout.getTopPanel(), BorderLayout.PAGE_START);
layout.getMidPanel().setBorder(BorderFactory.createTitledBorder(null, "Information Border", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION));
layout.getMidPanel().setLayout(new BorderLayout());
txtArea.setEditable(false);
txtArea.setColumns(20);
txtArea.setRows(5);
layout.getjScrollPane1().setViewportView(txtArea);
layout.getMidPanel().add(layout.getjScrollPane1(), BorderLayout.CENTER);
getContentPane().add(layout.getMidPanel(), BorderLayout.CENTER);
// 调整此窗口的大小,以适合其子组件的首选大小和布局
pack();
}
//启动服务器
private void btnStartActionPerformed(ActionEvent evt) {
try {
// 禁用按钮,避免重复启动
layout.getBtnStart().setEnabled(false);
String hostName = layout.getTxtHostName().getText();//主机名
Integer hostPort = Integer.parseInt(layout.getTxtHostPort().getText());//端口
//构建服务器的SocketAddress格式地址
SocketAddress serverAddr = new InetSocketAddress(InetAddress.getByName(hostName), hostPort);
listenSocket = new ServerSocket(); //创建侦听套接字
listenSocket.bind(serverAddr); //绑定到工作地址
txtArea.append("服务器开始等待客户机连接...\n");
} catch (IOException ex) {
}
//创建一个匿名线程,用于侦听和接受客户机连接,并创建响应客户机的会话线程
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) { //处理客户机连接
toClientSocket = listenSocket.accept();//侦听并接受客户机连接
clientCounts ++;//客户机数量加1
txtArea.append(toClientSocket.getRemoteSocketAddress() + " 客户机编号: " + clientCounts + " 会话开始...\n");
//创建客户线程clientThread,实现一客户一线程
Thread clientThread = new ClientThread(toClientSocket, clientCounts);
clientThread.start(); //启动任务线程
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex.getMessage(), "错误提示", JOptionPane.ERROR_MESSAGE);
}
}
}).start();
}
//关闭服务器之前
private void formWindowClosing(WindowEvent evt) {
//关闭服务器之前释放套接字
if (!Objects.isNull(listenSocket)) {
listenSocket = null;
}
if (!Objects.isNull(toClientSocket)) {
toClientSocket = null;
}
}
public static void main(String[] args) {
try {
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception ex) {
Logger.getLogger(ServerUI.class.getName()).log(Level.SEVERE, null, ex);
}
EventQueue.invokeLater(() -> new ServerUI().setVisible(true));
}
}
ClientThread.java
package utils;
import server.ServerUI;
import java.io.*;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 创建客户线程实现类,已实现服务器与客户机会话
*
* @author KonBAI
* @version 1.0
* Create by 2020/04/21 17:13
*/
public class ClientThread extends Thread {
/**
* 会话套接字
*/
private Socket toClientSocket;
/**
* 网络输入流
*/
private BufferedReader in;
/**
* 网络输出流
*/
private PrintWriter out;
/**
* 在线客户机总数
*/
private int clientCounts = 0;
public ClientThread(Socket toClientSocket, int clientCounts) {
this.toClientSocket = toClientSocket;
this.clientCounts = clientCounts;
}
@Override
public void run() {
try {
// 创建绑定到套接字toClientSocket上的网络输入流与输出流
in = new BufferedReader(new InputStreamReader(toClientSocket.getInputStream(), "UTF-8"));
out = new PrintWriter(new OutputStreamWriter(toClientSocket.getOutputStream(), "UTF-8"), true);
//5. 根据服务器协议,在网络流上进行读写操作
String recvStr;
//只要客户机不关闭,则反复等待和接收客户机消息
while ((recvStr = in.readLine()) != null) {
DateTimeFormatter df = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss");
LocalDateTime time = LocalDateTime.now();
String localTime = df.format(time);
//解析并显示收到的消息
ServerUI.txtArea.append(localTime + " [" + toClientSocket.getRemoteSocketAddress() + "] - [客户机编号:" + clientCounts + "] 消息:" + recvStr + "\n");
//按照echo协议原封不动回送消息
out.println(localTime + " [" + toClientSocket.getLocalSocketAddress() + "] - [客户机编号:" + clientCounts + "] Echo消息:" + recvStr);
}
// 客户机总数减1
ServerUI.clientCounts --;
// 远程客户机断开连接,线程释放资源
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
if (toClientSocket != null) {
toClientSocket.close();
}
} catch (IOException ex) {
}
}
}
5、启动方法
开启多个客户端
### 5、启动可视化界面编写程序
具体怎么使用,自己琢磨吧~