使用Java Socket 和 ServerSocket 实现客户端服务端网络通信(合肥工业大学java实验二)

一、实验前言

        在这次的实验里,我们总体上巩固了一遍Java的基础知识,包括:继承、重载、GUI设计、监听机制、IO、多线程、网络编程等等。其中大多数都是已经学习的内容,只有Socket和ServerSocket属于陌生知识,而且此次实验的关键在于这两个类,所以我也将会简要介绍。

二、实验标题

        基于GUI的网络通信程序设计

三、实验目的      

                1.掌握Java中GUI程序的编写,包括事件监听机制。

                2.掌握Java的网络通信编程,ServerSocket,Socket类的使用。

                3.掌握Java中多线程的编程,Thread类,Runnable接口的使用。

                4.掌握用面向对象的方法分析和解决复杂问题。

四、实验内容

编写程序完成以下功能:

  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连接后,编程实现服务端、客户端之间的“单向通信”:在客户端的输入界面发送消息,在服务端接收该消息,并将接收到对方的数据追加显示在多行文本框中。

        思考题

        1. 对整个实验进行总结,写出实验心得。

        2. 在完成上述实验内容的基础上,尝试实现“双向通信”功能,即服务端、客户端之间可以相互发送、接收消息,并以此作为实验成绩评优的加分依据

五、任务分析

        对整个实验进行分析,发现可以将整个任务分为以下几个部分

        图形界面的设计(GUI)

        监听器的绑定

        Socket和ServerSocket的实现

        多线程的实现

六、详细设计

                对第五点的几个部分进行分析,我们可以逐步进行设计

        1、图形界面

        观察组件较为复杂的客户端窗口

         发现大概可以由以下几个部分组成:三个文本框JTextfield、三个按钮JButton、四个标签JLabel、一个文本展示的区域JTextArea、最后再加上一个panel画布。看上去好像是这样,真的吗?

        可以发现,在我们所认为的“标签”客户机设置上旁,明显的有一个边框,这个边框将两个标签,两个文本框和一个按钮统统包含在内。这不是偶然,其实这是panel的一个效果。创建一个JPanel对象,可以发现有一个setBorder方法

        这是它从java.swing.Component继承来的方法,点开,发现

         它需要的参数是Boder对象,而有一个类叫BoderFactory,它的定义是这样的

         提供标准Boder对象的工厂类,它也有一个方法

         快看!它描述的是不是感觉跟我们客户机设置一模一样?所以我们只需要调用

panel1.setBorder(BorderFactory.createTitledBorder("客户机设置"));

 就可以形成了

        所以我们的界面其实包含的是这样的信息:首先,两个表示IP和端口的标签和两个文本框和一个按钮放到一个画布panel1上,然后底下是一个显示区域JTextArea,最后是由一个标签、一个文本框和两个按钮组成的第二个panel2。之后将两个panel和一个area放入JFrame完成了界面的设置。

        在这个部分中容易出错的部分有

        因为JFrame的默认布局管理器是:BorderLayout,JPanel的默认布局管理器是:FlowLayout,因此如果你什么也不说,直接将panel和JTextArea加入到JFrame去,是得不到最终结果的。所以我选择的是迁就JFrame,将panel1和panel2位置设置为上和下,就可以了。

        add(panel1,BorderLayout.NORTH);
        add(new JScrollPane(area));
        add(panel2,BorderLayout.SOUTH);

        2、建立监听器

        很容易理解,我们需要对三个按钮建立监听机制以让用户在点击时可以产生相应效果,建立监听器的第一步就是

        button2.addActionListener(this);
        button1.addActionListener(this);
        button3.addActionListener(this);

        接下来在重载的public void actionPerformed(ActionEvent e)方法中我们需要分别进行判断。

我们通过e.getSource是否等于button1、button2、button3来分别判断用户点击的是哪个键。如果是button1的话,所需要做的操作是将用户在文本框输入的域名和端口号保存并使用,启动服务器和客户端的连接;如果是button2,所需要进行的操作是对say后面的文本框的内容进行输出,如果没有连接就给予提示;如果用户点击的button3,所需要进行的操作是清空展示区域的内容。

        到此为止,我们已经建立好了一个完整的图形界面和监听机制,只不过还没有往里面填充内容。

        3、Socket和ServerSocket的实现

         上面这张图是我从别的博客上找来的关于服务器端和客户端工作原理的解释

         就跟打电话需要两个人一样,网络间的通信也需要两个端口建立连接,通过连接形成的管道进行信息交流。而Socket类代表一个客户端套接字(套接字就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象),当你想要和一个远程的服务端建立连接,就需要这样一个socket。要建立一个套接字,你需要指定IP地址和服务器套接字将要进行监听的端口号,通常,IP地址将会是127.0.0.1,也就是说,服务器套接字将会监听本地机器

        在客户端上我们只需要一个Socket,但是在服务端上,我们需要用一个Socket来监视某端口,然后根据来访的客户端来建立新的Socket负责数据通信。也就是说,建立socket需要端口号和域名,建立ServerSocket需要Socket对象和ServerSocket对象,但只需要端口号。

        所以,在定义那些GUI组件时,不妨顺带写上

    Socket socket=null;
    ServerSocket server=null;
    DataInputStream in=null;
    DataOutputStream out=null;
    Thread thread;

        接下来,我们对刚刚按钮中没有做完的进行补充。对于客户端来说,如果用户点击了button1,也就是连接按钮,area中需要立马显示“客户端连接中”,当连接没有成功,也就是!socket.isConnected()的时候,需要给socket赋上端口号和IP地址,然后等待服务器连接。如果点击的是button2,那么依然是先判断是否连接,如果连接之后在取文本框内的内容,通过DataOutputStream写入,传递给另一端。如果用户点击的button3,所需要进行的操作是清空展示区域的内容。

        这个部分需要注意的有:

        在对服务器和客户端处理的时候,一定要进行异常处理!!!

        别忘了对输入输出流的对象in和out进行初始化,这是两端进行交流的桥梁

        这部分还没有进行线程的处理

4、多线程

        在button2被按下的时候,做完操作之后,我们还需要有这样的操作

      if(!thread.isAlive())
          thread=new Thread(this);
      thread.start();

        因为单线程时,一条消息发完就停止工作了,所以我们需要在每次一个线程结束之后都创建一个新线程并且启动它,就可以实现多次对话了。

        那线程中需要啥操作呢?刚刚说了,就是获取对方端口发来的信息,所以第一点:需要异常处理的操作,第二点,通过in,读取对方端口的信息并且用一个String类型的变量保存后展现到area区域。

        

        到这里为止,我们就已经实现了一个可以实现客户端和服务器多次通信的、具有图形化界面的程序。接下来再说点需要完善的地方

        在每次输入一句想发给对方端口的话时,发完之后信息依然在文本框,我们可以通过setText函数让它消失。

        为了区分客户端和服务器端,我们可以设置颜色区分,可以使用 setForeground(Color.red);来设置。

        如果一个端口迟迟没有连接到另外一个端口,可以判断连接失败。

        可以通过设置JMenuBar和JMenu让窗口更加完善...以后再说.

七、完整代码

        客户端

import java.net.*;
import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Client {
    public static void main(String args[]) {
        ClientWindow c=new ClientWindow();
    }
}
class ClientWindow extends JFrame implements ActionListener,Runnable{
    JLabel label1,label2,label3;
    JButton button1,button2,button3;
    JTextField field1,field2,field3;
    JTextArea area;
    Socket socket=null;
    //ServerSocket server=null;
    DataInputStream in=null;
    DataOutputStream out=null;
    Thread thread;
    public ClientWindow()
    {
        init();
        setBounds(100,500,600,400);
        setVisible(true);
        setTitle("客户端");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public void init()
    {
        socket=new Socket();
        label1=new JLabel("Server IP: ");
        label2=new JLabel("Server Port: ");
        label3=new JLabel("Say");
        field1=new JTextField(15);
        field2=new JTextField(15);
        field3=new JTextField(25);
        button1=new JButton("Connect");
        button2=new JButton("Say: ");
        button3=new JButton("clear");
        area=new JTextArea();
        area.setEditable(false);//设置展示区域不可更改

        JPanel panel1=new JPanel();
        panel1.setBorder(BorderFactory.createTitledBorder("客户机设置"));//设置边框
        panel1.add(label1);
        panel1.add(field1);
        panel1.add(label2);
        panel1.add(field2);
        panel1.add(button1);


        JPanel panel2=new JPanel();
        panel2.add(label3);
        panel2.add(field3);
        panel2.add(button2);
        panel2.add(button3);
        //设置排版
        add(panel1,BorderLayout.NORTH);
        add(new JScrollPane(area),BorderLayout.CENTER);
        add(panel2,BorderLayout.SOUTH);
        //添加监听器并初始化线程
        thread=new Thread();
        button2.addActionListener(this);
        button1.addActionListener(this);
        button3.addActionListener(this);
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getSource()==button1)
        {
            area.append("客户端连接中...\n");
            try {
                //判断是否连接
                if(!socket.isConnected())
                {
                    //获取端口号和IP地址
                    socket=new Socket(field1.getText(),Integer.parseInt(field2.getText()));
                    in=new DataInputStream(socket.getInputStream());
                    out=new DataOutputStream(socket.getOutputStream());
                    if(!thread.isAlive()) {
                        thread = new Thread(this);
                    }
                        thread.start();
                }
                else{
                    area.append("客户端连接成功\n");
                }
            }catch (IOException ee)
            {
                System.out.println(ee);
                socket=new Socket();
            }
        }
        if(e.getSource()==button2)
        {
            if(socket.isConnected()) {
                String s = "";
                s += "客户端说:";
                s += field3.getText();
                area.append(s + "\n");
                field3.setText("");
                try {
                    out.writeUTF(s);
                } catch (IOException ee) {
                }
            }
            else {
                area.append("服务器尚未连接..\n");
                field3.setText("");
            }
        }
        if(e.getSource()==button3){
            area.setText("清除成功");
        }
    }

    @Override
    public void run() {
        String s=null;
        while(true)
        {
            try {
                //读取服务器端的数据
                s=in.readUTF();
                area.append("服务器说:"+s+"\n");
            }catch (IOException ee)
            {
                area.append("与服务器断开连接...");
                //socket=new Socket();
                break;
            }
        }
    }
}


    服务器端  

import java.net.*;
import java.io.*;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Server{
    public static void main(String args[]) {
        new ServerWindow();
    }
}
class ServerWindow extends JFrame implements Runnable,ActionListener{

    JLabel label1,label2;
    JTextField field1,field2;
    JButton button1,button2,button3;
    JTextArea area;
    ServerSocket serversocket=null;
    Socket socket=null;
    DataOutputStream out=null;
    DataInputStream in=null;
    Thread thread;

    public ServerWindow()
    {
        init();
        setVisible(true);
        setTitle("服务器");
        setBounds(200,200,600,400);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public void init()
    {

        socket=new Socket();
        label1=new JLabel("Port: ");
        label2=new JLabel("Say: ");
        field1=new JTextField(30);
        field2=new JTextField(25);
        button1=new JButton("Start");
        button2=new JButton("Say");
        button3=new JButton("clear");
        area=new JTextArea();
        area.setEditable(false);

        JPanel panel1=new JPanel();
        panel1.setBorder(BorderFactory.createTitledBorder("服务器设置"));
        panel1.add(label1);
        panel1.add(field1);
        panel1.add(button1);

        JPanel panel2=new JPanel();
        panel2.add(label2);
        panel2.add(field2);
        panel2.add(button2);
        panel2.add(button3);
        
        add(panel1,BorderLayout.NORTH);
        add(new JScrollPane(area));
        add(panel2,BorderLayout.SOUTH);

        button1.addActionListener(this);
        button2.addActionListener(this);
        button3.addActionListener(this);
        thread=new Thread();
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getSource()==button1)
        {
            area.append("服务器连接中....\n");
            try{
                if(!socket.isConnected())
                {
                    serversocket=new ServerSocket(Integer.parseInt(field1.getText()));
                    socket=serversocket.accept();

                    in =new DataInputStream(socket.getInputStream());
                    out=new DataOutputStream(socket.getOutputStream());
                    if(!thread.isAlive())
                        thread=new Thread(this);
                    thread.start();
                }
                else
                {
                    area.append("服务器连接成功\n");
                }
            }
            catch (IOException ioException) {
                System.out.println(ioException);
                socket=new Socket();
            }
        }
        if(e.getSource()==button2)
        {
            if(socket.isConnected()){
            String s=field2.getText();
            area.append("服务器说:"+s+"\n");
            field2.setText("");
            try {
                out.writeUTF(s);
            }catch (IOException ee){

            }
            }else {
                area.append("客户端未连接...\n");
                field2.setText("");
            }

        }
        if(e.getSource()==button3)
        {
            area.setText("清除成功");
        }
    }
    @Override
    public void run() {
        String s=null;

        while(true)
        {
            try {
                s=in.readUTF();
                area.append(s+"\n");
            }catch (IOException ee)
            {
                area.append("客户端下线");
                //socket=new Socket();
                break;
            }
        }
    }
}

        运行效果:

 

九、未解决的bug 

        我发现每次输入好两边的数据后,必须要服务器先点连接,客户端后点才可以正确连接。不然服务器就会崩掉,不知道为什么..

十、结尾

        如果这篇文章对您有帮助的话,希望可以留下一个宝贵的赞

  • 28
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少๑渊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值