在学习Java的socket通信时,老师布置的一道作业题,借此机会也对相关的知识进行梳理,题目如下:

编写客户服务器端程序,使用Socket技术实现通信,双方约定通信端口为6789。服务器端功能:当收到客户端信息后,首先判断是否是“BYE”,若是,则立即向对方发送“BYE”,然后关闭监听,结束程序。若不是,则在屏幕上输出收到的信息,并由键盘上输入发送到对方的应答信息。客户端功能:当收到服务器发来的是“BYE”时,立即向对方发送“BYE”,然后关闭连接,否则,继续向服务器发送信息。

首先我们需要明白,socket是基于应用服务与TCP/IP通信之间的一个抽象,它将TCP/IP协议里面复杂的通信逻辑进行分装,对用户来说,只要通过一组简单的API就可以实现网络的连接。

进入代码的编写,因为只是针对该题目,所以将main函数启动时加入对参数的判断,如果启动参数不为0,则启动服务器端,否则启动客户端,代码如下:

public static void main(String[] args) {
        if(args.length > 0) {
            // 启动服务器端
            server();
        } else {
            // 启动客户端
            client();
        }
}

服务器端和客户端通过键盘的输入在控制台显示对方的消息和自己的输入,这里我通过使用两个类分别命名为Reader和Writer继承Thread类来实现读写的操作,类Reader代码如下:

class Reader extends Thread {
    private DataInputStream dis;
    private DataOutputStream dos;

    public Reader(DataInputStream dis, DataOutputStream dos) {
        this.dis = dis;
        this.dos = dos;
    }

    @Override
    public void run() {
        String str;
        while(true) {
            // 读
            try {
                str = dis.readUTF();
                System.out.println("对方说:" + str);
                // 收到“BYE”退出
                if("BYE".equals(str)) {
                    new Writer(dis, dos).start();
                    System.exit(0); // 程序退出
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

类Writer代码如下:

class Writer extends Thread {
    private DataInputStream dis;
    private DataOutputStream dos;

    public Writer(DataInputStream dis, DataOutputStream dos) {
        this.dis = dis;
        this.dos = dos;
    }

    @Override
    public void run() {
        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader br = new BufferedReader(isr);
        String str;
        while(true) {
            // 写
            try {
                str = br.readLine();
                dos.writeUTF(str);
                dos.flush();
                if("BYE".equals(str)) {
                    // 收到“BYE”退出
                    new Reader(dis, dos).start();
                    System.exit(0);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

此时编写服务器端和客户端的代码就变得简单,服务器端代码如下:

//  服务器端
    public static void server() {
        System.out.println("=================server===================");
        try {
            // 设置端口号
            ServerSocket ss = new ServerSocket(6789);
            Socket s = ss.accept();
            // 包装输入输出流
            OutputStream os = s.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(os);
            DataOutputStream dos = new DataOutputStream(bos);
            InputStream is = s.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);
            DataInputStream dis = new DataInputStream(bis);

            // 读,启动一个线程实现读功能
            new Reader(dis, dos).start();
            // 写,启动一个线程实现写功能
            new Writer(dis, dos).start();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

客户端代码如下:

//  客户端
    public static void client() {
        System.out.println("=================client===================");
        try {
            Socket s = new Socket("localhost", 6789); // localhost 127.0.0.1 本机地址
            // 包装输入输出流
            OutputStream os = s.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(os);
            DataOutputStream dos = new DataOutputStream(bos);
            InputStream is = s.getInputStream();
            BufferedInputStream bis = new BufferedInputStream(is);
            DataInputStream dis = new DataInputStream(bis);

            // 读,启动一个线程实现读功能
            new Reader(dis, dos).start();
            // 写,启动一个线程实现写功能
            new Writer(dis, dos).start();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

至此,代码部分就已经完成,运行时需要注意的是如果使用IDEA运行的话,需要先点击Edit Configurations,

java 服务器端 客户端 java客户端和服务端的socket编程_http

 需要修改两项,首先是Allow parallel run,因为需要同时启动客户端和服务器端,如果不勾选的话只能启动其中一个,program arguments也需要修改是因为启动服务器端还是启动客户端的判断条件是依据program arguments的字符长度,首先需要启动服务器端所以需要同时修改,内容可以随便填写,这里我填入的是“123”,截图如下:

java 服务器端 客户端 java客户端和服务端的socket编程_java_02

 此时点击运行,服务器端启动成功截图:

java 服务器端 客户端 java客户端和服务端的socket编程_http_03

 再次点击Edit Configurations,需要将program arguments后的内容删除,记得点击apply和ok按钮,截图如下:

java 服务器端 客户端 java客户端和服务端的socket编程_http_04

 点击运行,会发现客户端也成功运行,截图如下:

java 服务器端 客户端 java客户端和服务端的socket编程_socket_05

 在服务器端或者客户端输入,便可发现能够成功对话:

java 服务器端 客户端 java客户端和服务端的socket编程_http_06

java 服务器端 客户端 java客户端和服务端的socket编程_socket_07

 输入“BYE”结束对话,同时也会发现程序结束运行状态:

java 服务器端 客户端 java客户端和服务端的socket编程_socket_08

 服务器端也自动结束了运行:

java 服务器端 客户端 java客户端和服务端的socket编程_http_09

 至此这道题也就成功实现,但是同时留下的思考题为如何实现多客户端通信,并没有想到特别好的解决方法,希望在今后的学习中可以得到好的解决方案。