写在前面:这次实验代码的构建主要是更加熟练的运用socket网络编程、文件输入输出流、GUI设计、容器的使用、多线程的运用等等多方面的知识,是综合类题型,做完受益身心的类型。
题目如下:
编写程序完成以下功能:
1.设计一个基于GUI的客户-服务器的通信应用程序,如图1,图2所示。
图1 Socket通信服务器端界面
图2 Socket通信客户端界面
2.图1为Socket通信服务器端界面,点击该界面中的【Start】按钮,启动服务器监听服务(在图1界面中间的多行文本区域显示“Server starting…”字样)。图2为Socket通信客户端界面,点击该界面中的【Connect】按钮与服务器建立链接,并在图2所示界面中间的多行文本区域显示“Connect to server…”字样,当服务器端监听到客户端的连接后,在图1界面中间的多行文本区域追加一行“Client connected…”字样,并与客户端建立Socket连接。
3.当图1所示的服务器端和图2所示的客户机端建立Socket连接后,编程实现服务端、客户端之间的“单向通信”:在客户端的输入界面发送消息,在服务端接收该消息,并将接收到对方的数据追加显示在多行文本框中。
下面开始做题:
一、学习基础知识
(如果想要学到什么的话,可以根据清单去学,不花很多时间
首先主要需要了解socket的知识,包括但不限于下列:(以下为最主要、基础的知识
1. ServerSocket、Socket的构造函数,需要什么东西来构造
2. 了解服务器与客户端之间依靠中间Socket连接,获取输入输出流
3. 为什么需要多线程,以及多线程的两种基本操作方法
1) implements Runnable
2) extends Thread
4. 在服务器与客户端通信的过程中,会引起阻塞的操作有哪几种。
了解完一些基本知识,发车,冲!!~
二、代码逻辑构建与代码包细节
之后,做出如下代码构建逻辑 (无GUI版 先设计这版的更容易理解基础知识。
然后用如下代码包体现上面的逻辑构建
(无GUI版) 具体代码如下:(很多很多,我自己都不爱看 不过也不需要看,毕竟是我写的 :) 但是看一遍逻辑会清楚很多
因为比较大,所以分开服务端、客户端截图。
只是看起来很复杂,因为具体代码怎么写的都基本写出来,所以会显得很多。
但是毕竟我们的作业是有GUI版,所以再次进行代码逻辑构建:
(有了上面一版的理解,有GUI版仅仅是多了GUI的代码设计而已
上图知识点解释来啦:
1. 因为代码为了区分功能开了很多的类和文件 所以main函数主要用来组织这些类的发生。
2. 输出不需要等待scanner类的键盘输入了,只需要判断文本框内有没有东西然后直接输出就行,然后就直接send就可以了。
3. 因为服务器的启动时间由start组件来监听开启,所以相比于无GUI版代码直接使用start组件的类兼职accept(),意思是服务器默认永久等待客户端连接。
4. 和3一样,connect类相当于客户端的main函数,而且因为输出仅仅需要send来等待,所以这个时候使用connect兼职一下监听。
5. 为什么start和connect的里面的actionperformed不可以直接进行accept和监听呢?
因为在actionPerformed中体现点击事件,如果actionPerformed过长会一直假卡死,即该UI界面无法发生其他事件,无法进入下个动作,所以类似于accept和监听这样长时间的会阻塞线程的动作我们单独开启一个工作线程就不放在actionperformed里面了。
代码包以及代码作用如下:(可以放大查看,代码思路都交代了
注意:本来这里可以不使用接口类,其实没有必要,但是当时自己想实践一下面向接口的思想,并且让代码更有逻辑有利于后期维护,所以采用了很多接口类。
其实如果为了让actionperformed函数分开功能的话只需要直接写一个类进行了。不需要再写接口了。
有GUI版代码相比于无GUI版改进了很多用户交互的内容,做了很多优化包括但不限于:
1. 双向通信 2.选择不同客户发送消息 3.按钮事件的控制以及很多事件源监视器的委派 4.默认本地服务器等等。
三、具体成果显示
1. 可以连接多个客户
细节的优化:按钮事件的控制,不可调整窗口大小,默认本机地址、不同客户有自己的名字、且存在于下拉菜单中等等
2. 双向通信
细节的优化:区分客户发送的消息、分别发送给不同客户、 按钮点击文本框的清空等等
3. 客户断开连接
附完整可运行代码:
代码逻辑如图:
C_MyCommandSurveillanceForConnect接口
package Client;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.net.Socket;
public interface C_MyCommandSurveillanceForConnect extends ActionListener {
void setJTextField_IP(JTextField jTextField);
void setJTextField_Port(JTextField jTextField);
void setJTextArea(JTextArea jTextArea);
void setSocket(Socket socket);
void setBtn(JButton jButton);
}
C_MyCommandSurveillanceForSend接口
package Client;
import javax.swing.*;
import java.awt.event.ActionListener;
import java.net.Socket;
public interface C_MyCommandSurveillanceForSend extends ActionListener {
void setJTextField(JTextField jTextField);
void setSocket(Socket socket);
}
C_MySurveillanceForConnect类
package Client;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
//根据输入的IP地址与端口号进行连接、监听信息线程 Client、ClientListen
public class C_MySurveillanceForConnect extends Thread implements C_MyCommandSurveillanceForConnect{
JTextField jTextField_IP;
JTextField jTextField_Port;
JTextArea jTextArea;
JButton btn;
Socket client;
DataInputStream in;
String str;
public void setJTextField_IP(JTextField jTextField){
this.jTextField_IP = jTextField;
}
public void setJTextField_Port(JTextField jTextField){
this.jTextField_Port = jTextField;
}
public void setBtn(JButton jButton){
this.btn = jButton;
}
public void setJTextArea(JTextArea jTextArea){
this.jTextArea = jTextArea;
}
public void setSocket(Socket socket){
this.client = socket;
}
public void actionPerformed(ActionEvent e){
btn.setEnabled(false);
//判断IP与Port都填写完整,进行连接
String IP, Port;
if((Port = jTextField_Port.getText()) != null){
InetAddress address;
//开始进行连接
try {
if((IP = jTextField_IP.getText()) == null){
address = InetAddress.getLocalHost();
}
else{
address = InetAddress.getByName(IP);
}
}
catch (UnknownHostException ex) {
throw new RuntimeException(ex);
}
InetSocketAddress socketAddress = new InetSocketAddress(address, Integer.parseInt(Port));
try {
client.connect(socketAddress);
jTextArea.append("Connecting to server...\n");
jTextArea.paintImmediately(jTextArea.getBounds());
in = new DataInputStream(client.getInputStream());
Thread thread = this;
thread.start();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
else{
jTextArea.append("Please enter right IP and Port...\n");
jTextArea.paintImmediately(jTextArea.getBounds());
}
}
public void run(){
while(true){
try {
str = in.readUTF();
jTextArea.append("From server: " + str + "\n");
} catch (IOException ex) {
jTextArea.append("Disconnected to server...");
jTextArea.paintImmediately(jTextArea.getBounds());
break;
}
}
}
}
C_MySurveillanceForSend类
package Client;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
//发送信息 ClientTx
public class C_MySurveillanceForSend implements C_MyCommandSurveillanceForSend {
JTextField jTextField;
Socket client;
DataOutputStream out;
public void setJTextField(JTextField jTextField){
this.jTextField = jTextField;
}
public void setSocket(Socket socket){
this.client = socket;
}
public void actionPerformed(ActionEvent e){
try {
out = new DataOutputStream(client.getOutputStream());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
try {
out.writeUTF(jTextField.getText());
jTextField.setText(null);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
C_MyGUI类
package Client;
import javax.swing.*;
import java.awt.*;
import java.net.Socket;
import static java.awt.Font.ITALIC;
//设置页面 添加监视器
public class C_MyGUI extends JFrame {
JFrame jFrame = new JFrame();
JTextField textF_IP, textF_port, textF_send;
JButton btn_Connect, btn_send;
JTextArea textArea;
JPanel jPanel01;
JPanel jPanel02;
JPanel jPanel03;
JPanel jPanel;
//监视器接口
C_MyCommandSurveillanceForSend myCommandSurveillanceForSend;
C_MyCommandSurveillanceForConnect myCommandSurveillanceForConnect;
//一个对象
Socket client;
//构造函数完成窗口的主流操作
public C_MyGUI(){
client = new Socket();
init();
jFrame.setResizable(false);
jFrame.setBounds(100,100,460,360);
jFrame.setTitle("客户端");
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void init(){
//服务器设置框
jPanel = new JPanel();
jPanel01 = new JPanel();
jPanel02 = new JPanel();
jPanel03 = new JPanel();
textF_IP = new JTextField(10);
textF_IP.setFont(new Font(null, Font.BOLD, 13));
textF_port = new JTextField(8);
textF_port.setFont(new Font(null, Font.BOLD, 13));
btn_Connect = new JButton("Connect");
jPanel01.add(new JLabel("Server IP: "));
jPanel01.add(textF_IP);
jPanel01.add(new JLabel("Port: "));
jPanel01.add(textF_port);
jPanel01.add(btn_Connect);
jPanel01.setBorder(BorderFactory.createTitledBorder("客户机设置:"));
//中间文本框
textArea = new JTextArea(9,30);
textArea.setLineWrap(true);
textArea.setFont(new Font(null, Font.BOLD+ITALIC, 15));
JScrollPane jScrollPane = new JScrollPane();
jScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
jScrollPane.setViewportView(textArea);
jPanel02.add(jScrollPane);
//send发送信息栏
textF_send = new JTextField(23);
textF_send.setFont(new Font(null, Font.BOLD, 13));
btn_send = new JButton("Send");
jPanel03.add(new JLabel("Send: "));
jPanel03.add(textF_send);
jPanel03.add(btn_send);
//将上中下三个组件存放如jPanel
jPanel.add(jPanel01,BorderLayout.NORTH);
jPanel.add(jPanel02,BorderLayout.CENTER);
jPanel.add(jPanel03,BorderLayout.SOUTH);
jFrame.setContentPane(jPanel);
}
//设置监视器
void setMyCommandSurveillanceForConnect(C_MyCommandSurveillanceForConnect myCommandSurveillanceForConnect){
//利用组合设置对象
this.myCommandSurveillanceForConnect = myCommandSurveillanceForConnect;
myCommandSurveillanceForConnect.setJTextField_Port(textF_port);
myCommandSurveillanceForConnect.setJTextField_IP(textF_IP);
myCommandSurveillanceForConnect.setJTextArea(textArea);
myCommandSurveillanceForConnect.setSocket(client);
myCommandSurveillanceForConnect.setBtn(btn_Connect);
//为事件源添加监视器 用户点击按钮才能够触发事件
textF_port.addActionListener(myCommandSurveillanceForConnect);
textF_IP.addActionListener(myCommandSurveillanceForConnect);
btn_Connect.addActionListener(myCommandSurveillanceForConnect);
}
void setMyCommandSurveillanceForSend(C_MyCommandSurveillanceForSend myCommandSurveillanceForSend){
this.myCommandSurveillanceForSend = myCommandSurveillanceForSend;
myCommandSurveillanceForSend.setJTextField(textF_send);
myCommandSurveillanceForSend.setSocket(client);
//为事件源添加监视器
textF_send.addActionListener(myCommandSurveillanceForSend);
btn_send.addActionListener(myCommandSurveillanceForSend);
}
}
Client_main类(可以设置多个相同的main类想起来连接了多个客户
package Client;
//192.168.197.121
public class Client_main {
public static void main(String[] args){
//设置GUI界面
C_MyGUI myGui = new C_MyGUI();
//设置监视器
C_MySurveillanceForConnect MySurveillanceForConnect = new C_MySurveillanceForConnect();
C_MySurveillanceForSend mySurveillanceForSend = new C_MySurveillanceForSend();
myGui.setMyCommandSurveillanceForConnect(MySurveillanceForConnect);
myGui.setMyCommandSurveillanceForSend(mySurveillanceForSend);
}
}
S_MyCommandSurveillanceForStart接口
package Server;
import javax.swing.*;
import java.awt.event.ActionListener;
public interface S_MyCommandSurveillanceForStart extends ActionListener {
void setJTextField(JTextField jTextField);
void setJTextArea(JTextArea jTextArea);
void setJBtn(JButton jButton);
}
S_MyCommandSurveillanceForSend接口
package Server;
import javax.swing.*;
import java.awt.event.ActionListener;
public interface S_MyCommandSurveillanceForSend extends ActionListener {
void setJTextField(JTextField jTextField);
void setJComboBox(JComboBox<String> jComboBox);
}
S_MySurveillanceForStart类
package Server;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Objects;
import static Server.S_MyGUI.socket_name;
import static Server.S_MyGUI.scs;
//accept工作线程 作用:基于GUI,读取port框内内容进行连接操作
//为客户取名,并且为每一位用户开启监听线程
public class S_MySurveillanceForStart extends Thread implements S_MyCommandSurveillanceForStart{
JTextField Port;
JTextArea jTextArea;
JButton jButton;
ServerSocket serverSocket;
Socket sc;
String client = "Client";
String Client_name = null;
static int client_num = 1;
public void setJTextField(JTextField jTextField){
this.Port = jTextField;
}
public void setJTextArea(JTextArea TextArea){
this.jTextArea = TextArea;
}
public void setJBtn(JButton jButton){ this.jButton = jButton;}
public void actionPerformed(ActionEvent e){
Thread thread = this;
thread.start();
}
public void run(){
while(true){
try{
if(Port.getText() != null){
serverSocket = new ServerSocket(Integer.parseInt(Port.getText()));
}
else{
jTextArea.append("Please enter right Port...");
jTextArea.paintImmediately(jTextArea.getBounds());
break;
}
}
catch (IOException exception){
jTextArea.append("Server listening..."+ "\n");
jTextArea.paintImmediately(jTextArea.getBounds());
}
try {
if(client_num == 1){
jTextArea.append("Server starting..."+ "\n");
jTextArea.paintImmediately(jTextArea.getBounds());
}
sc = serverSocket.accept();
scs.add(sc);
if(Objects.equals(socket_name.get(0), "null")){
socket_name.remove(0);
}
Client_name = client + client_num;
jTextArea.append(Client_name+" connected..."+ "\n");
socket_name.add(Client_name);
jTextArea.paintImmediately(jTextArea.getBounds());
} catch (IOException even) {
jTextArea.append("Server waiting..."+ "\n");
jTextArea.paintImmediately(jTextArea.getBounds());
}
if(sc != null){
client_num++;
new ServerListenThread(sc, jTextArea, Client_name).start();
}
}
}
}
S_MySurveillanceForSend类
package Server;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import static Server.S_MyGUI.scs;
public class S_MySurveillanceForSend implements S_MyCommandSurveillanceForSend{
JTextField jTextField;
JComboBox<String> jComboBox;
Socket sc;
DataOutputStream out;
public void setJTextField(JTextField jTextField){
this.jTextField = jTextField;
}
public void setJComboBox(JComboBox<String> jComboBox){
this.jComboBox = jComboBox;
}
public void actionPerformed(ActionEvent e){
sc = scs.elementAt(jComboBox.getSelectedIndex());
try {
out = new DataOutputStream(sc.getOutputStream());
out.writeUTF(jTextField.getText());
jTextField.setText(null);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
}
S_MyGUI类
package Server;
import javax.swing.*;
import java.awt.*;
import java.net.Socket;
import java.util.Vector;
import static java.awt.Font.ITALIC;
//设置GUI界面 添加监视器 + 建立通信
public class S_MyGUI extends JFrame {
//组件
static Vector<String> socket_name;
static Vector<Socket> scs;
JFrame jFrame = new JFrame();
JTextField textF_port, textF_send;
JButton btn_start, btn_send;
JTextArea textArea;
JPanel jPanel01;
JPanel jPanel02;
JPanel jPanel03;
JPanel jPanel;
JComboBox<String> comboBox;
//监视器接口
S_MyCommandSurveillanceForStart myCommandSurveillanceForStart;
S_MyCommandSurveillanceForSend myCommandSurveillanceForSend;
//对象
//构造函数完成窗口的主流操作
public S_MyGUI(){
socket_name = new Vector<>();
scs = new Vector<>();
socket_name.add("null");
init();
jFrame.setResizable(false);
jFrame.setBounds(100,100,460,360);
jFrame.setTitle("服务器");
jFrame.setVisible(true);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void init(){
//服务器设置框
jPanel = new JPanel();
jPanel01 = new JPanel();
jPanel02 = new JPanel();
jPanel03 = new JPanel();
textF_port = new JTextField(23);
textF_port.setFont(new Font(null, Font.BOLD, 13));
btn_start = new JButton("Start");
jPanel01.add(new JLabel("Port: "));
jPanel01.add(textF_port);
jPanel01.add(btn_start);
jPanel01.setBorder(BorderFactory.createTitledBorder("服务器设置:"));
//中间文本框
textArea = new JTextArea(9,30);
textArea.setLineWrap(true);
textArea.setFont(new Font(null, Font.BOLD+ITALIC, 15));
JScrollPane jScrollPane = new JScrollPane();
JScrollBar jscrollBar = jScrollPane.getVerticalScrollBar();
if (jscrollBar != null) {
// 必须先获取一次jScrollBar.getMaximum(),否则滚动不到最底部, swing bug
jscrollBar.setValue(jscrollBar.getMaximum());
jScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
}
jScrollPane.setViewportView(textArea);
jPanel02.add(jScrollPane);
//send发送信息栏
textF_send = new JTextField(18);
comboBox = new JComboBox<>(socket_name);
textF_send.setFont(new Font(null, Font.BOLD, 13));
btn_send = new JButton("Send");
jPanel03.add(new JLabel("SendTo: "));
jPanel03.add(comboBox);
jPanel03.add(textF_send);
jPanel03.add(btn_send);
//将上中下三个组件存放如jPanel
jPanel.add(jPanel01,BorderLayout.NORTH);
jPanel.add(jPanel02,BorderLayout.CENTER);
jPanel.add(jPanel03,BorderLayout.SOUTH);
jFrame.setContentPane(jPanel);
}
//设置监视器
void setMyCommandSurveillanceForStart(S_MyCommandSurveillanceForStart myCommandSurveillanceForStart){
//利用组合设置对象
this.myCommandSurveillanceForStart = myCommandSurveillanceForStart;
myCommandSurveillanceForStart.setJTextField(textF_port);
myCommandSurveillanceForStart.setJTextArea(textArea);
myCommandSurveillanceForStart.setJBtn(btn_start);
//为事件源添加监视器 用户按回车键、按钮都能够触发事件
textF_port.addActionListener(myCommandSurveillanceForStart);
btn_start.addActionListener(myCommandSurveillanceForStart);
}
void setMyCommandSurveillanceForSend(S_MyCommandSurveillanceForSend myCommandSurveillanceForSend){
this.myCommandSurveillanceForSend = myCommandSurveillanceForSend;
myCommandSurveillanceForSend.setJTextField(textF_send);
myCommandSurveillanceForSend.setJComboBox(comboBox);
//为事件源添加监视器
textF_send.addActionListener(myCommandSurveillanceForSend);
btn_send.addActionListener(myCommandSurveillanceForSend);
}
}
ServerListenThread类
package Server;
import javax.swing.*;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import static Server.S_MyGUI.scs;
import static Server.S_MyGUI.socket_name;
//服务器输入流监听线程 作用:进行输入流的监听
public class ServerListenThread extends Thread{
Socket sc;
JTextArea jTextArea;
DataInputStream in;
String str;
ServerListenThread(Socket socket, JTextArea jTextArea, String string){
super(string);
this.sc = socket;
this.jTextArea = jTextArea;
try {
in = new DataInputStream(sc.getInputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
//后台死循环进行监听
public void run(){
while(true){
try {
str = in.readUTF();
jTextArea.append(getName() + ": " + str + "\n");
jTextArea.paintImmediately(jTextArea.getBounds());
} catch (IOException e) {
int index = scs.indexOf(sc);
jTextArea.append(socket_name.get(index) + "disconnected...\n");
scs.remove(index);
socket_name.remove(index);
if(socket_name.isEmpty()){
socket_name.add("null");
}
break;
}
}
}
}
Server_main类
package Server;
public class Server_main {
public static void main(String[] args){
//设置GUI界面
S_MyGUI myGui = new S_MyGUI();
//添加监视器,利用组合调用方法
S_MySurveillanceForStart mySurveillanceForStart = new S_MySurveillanceForStart();
S_MySurveillanceForSend mySurveillanceForSend = new S_MySurveillanceForSend();
myGui.setMyCommandSurveillanceForStart(mySurveillanceForStart);
myGui.setMyCommandSurveillanceForSend(mySurveillanceForSend);
}
}
至此,所有代码就完成了哈哈哈。
源代码和教程附上,说是教程其实主要是讲出构建整个代码的知识与思路,希望在完成作业的同时能够对你有帮助来学习思路。
如果觉得还不错的话...... :)