天天看点

【Java基础】网络编程:Socket、ServerSocket、Echo程序

《第一行代码:Java》第12章、Java网络编程 读书笔记。有关计算机网络的基础知识建议去看计算机网络相关的文章,本章中主要以“C/S”开发中的TCP程序实现为主。

文章目录

    • Java网络编程
      • 12.1 网络编程
      • 12.2开发第一个网络程序
      • 12.3 网络开发的经典模型——Echo程序
      • 本章小结:

Java网络编程

12.1 网络编程

网络编程的核心意义在于不同的电脑主机之间的数据交互,但是在Java中这一概念会进一步简化。在Java中是以JVM进程划分网络的,即不同的JVM代表不同的主机。Java中同一台主机的不同JVM之间的数据访问也属于远程访问。

【Java基础】网络编程:Socket、ServerSocket、Echo程序
  • 网络编程的是指意义在于数据的交互,而在交互的过程中一定会将交互的双方分为服务器端和客户端,而这两端的开发会存在以下两种模式:
    • 模式一:C/S结构(Client/Server),此类模式的开发一般要编写两套程序,一套是客户端代码,另外一套属于服务器端代码。由于需要有编写程序,所以对于开发以及维护的成本较高。但是由于其使用的是自己的连接端口与交换协议,所以安全性比较高。而C/S结构程序的开发分为两种:
      • TCP(传输控制协议,一种面向连接的通信协议,可靠的传输)
      • UDP(数据报协议,一种面向无连接的通讯协议)。
    • 形式二:B/S结构(Browser /Server ),不再单独开发客户端代码,只开发一套服务器端程序,客户端将利用浏览器进行访问,这种模式只需要开发一套程序,但是安全性不高,因为使用的是公共的HTTP 协议以及公共的80端口。
    本章主要以“C/S”开发中的TCP程序实现为主。

12.2开发第一个网络程序

  • 在java.net包中,提供了网络编程相关的开发工具类,在此包中有以下两个主要的核心操作类。
    • ServerSocket类:是一个封装支持TCP协议的操作类,主要工作是在服务器端,用于接收客户端请求。
    • Socket类:也是一个封装了TCP协议的操作类,每个Socket对象都是一个客户端
  • java.net.ServerSocket类常用方法:
    public ServerSocket(int port) throws IOException	// 构造,开辟一个指定的端口监听,一般使用5000以上的端口
    public Socket accept() throws IOException			// 普通,服务器端接收客户端请求,通过Socket返回
    public void close() throws IOException				// 普通,返回服务器端
               
  • java.net.Socket类常用方法:
    public Socket(String host, int port) throws UnknowHostException, IOException	// 构造,指定要连接的主机(IP地址)和端口
    public OutputStream getOutputStream() throws IOException	// 普通,取得指定客户端的输出对象,使用PrintStream操作
    public InputStream getInputStream() throws IOException		// 普通,从指定的客户端读取数据,使用Scanner操作
               
  • 在客户端,程序可以通过Socket类的getInputStream()方法,取得服务器的输出信息,在服务器端可以通过getOutputStream()方法取得客户端的输出信息,具体如下图所示:
    【Java基础】网络编程:Socket、ServerSocket、Echo程序
  • 定义服务器端——主要使用ServerSocket类:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    public class HelloServer { 
        public static void main(String[] args) throws Exception {
            ServerSocket server = new ServerSocket(9999) ;	// 所有的服务器必须有端口
            System.out.println("等待客户端连接.....");			// 提示信息
            Socket client = server.accept() ;				// 等待客户端连接
            // OutputStream并不方便进行内容的输出,所以利用打印流完成输出
            PrintStream out = new PrintStream(client.getOutputStream()) ;
            out.println("Hello World !");					// 输出数据
            out.println("你好世界 !");						 // 输出数据
            out.close(); 
            client.close();
            server.close();
        }
    }
    // 程序执行结果:	等待客户端连接.....
               
    本程序在本机的9999端口上设置了一个服务器的监听操作(accept()方法表示打开服务器监听,并且会一直等待,直到有客户端连接后才会执行下一步),当有客户端通过TCP连接方式连接到服务器端后,服务器端将会利用PrintSream输出数据,当数据输出完毕后,该服务器将会关闭,也就是说本次定义的服务器只能处理一次客户端的请求。
  • 编写客户端——主要使用Socket类:
    package com.yootk.demo;
    import java.net.Socket;
    import java.util.Scanner;
    public class HelloClient {
        public static void main(String[] args) throws Exception {
            Socket client = new Socket("localhost",9999) ;			// 连接服务器端
            // 取得客户端的输入数据流对象,表示接收服务器端的输出信息
            Scanner scan = new Scanner(client.getInputStream()) ;	// 接收服务器端回应数据
            scan.useDelimiter("\n") ;								// 设置分隔符
            while (scan.hasNext()) {								// 是否有数据
               System.out.println("【回应数据】" + scan.next());		// 取出数据
            }
            scan.close();
            client.close();
        }
    }
    /*
    程序执行结果:
        Hello World !
        你好世界 !
    */
               
    在TCP程序中,每一个Socket对象都表示一个客户端的信息,所以客户端程序要连接也必须依靠Socket对象操作。在实例化Socket对象时必须设置连接的主机名称(本机为“localhost“,或者填写IP地址)以及连接端口号,当连接成功后就可以利用Scanner进行输入流数据的读取,这样就可以接收服务器端的回应信息了。

12.3 网络开发的经典模型——Echo程序

在网络编程中 Echo是一个经典的程序开发模型,本程序的意义在于:客户端随意输入信息并且将信息发送给服务器端,服务器端接收后前面加上一个”ECHO:”的前缀标记后将数据返还给客户端。在本程序中服务器端既要接收客户端发送来的数据,又要向客户端输出数据,同时考虑到需要进行多次数据交换,所以每次连接后不应该立刻关闭服务器,而当用户输入了一些特定字符串(例如:“byebye”)后才表示可以结束本次的Echo操作。

  • 服务器端实现:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    public class EchoServer {
        public static void main(String[] args) throws Exception {
            ServerSocket server = new ServerSocket(9999) ;	// 定义连接端口
            Socket client = server.accept() ;				// 等待客户端连接
            // 得到客户端输入数据以及向客户端输出数据的对象,利用扫描流接收,打印流输出
            Scanner scan = new Scanner(client.getInputStream()) ;
            PrintStream out = new PrintStream(client.getOutputStream()) ;
            boolean flag = true ;							// 设置循环标记
            while(flag) {
                if (scan.hasNext()) {						// 是否有内容输入
                   String str = scan.next().trim() ;		// 得到客户端发送的内容,并删除空格
                   if (str.equalsIgnoreCase("byebye")) {	// 程序结束标记
                      out.println("拜拜,下次再会!");		   // 输出结束信息
                      flag = false ;						// 退出循环
                   } else {									// 回应输入信息
                      out.println("ECHO : " + str);			// 加“ECHO :”前缀返回
                   }
                }
            }
            scan.close();
            out.close();
            client.close(); 
            server.close();
        }
    }
               
  • 客户端实现:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.Socket;
    import java.util.Scanner;
    public class EchoClient {
        public static void main(String[] args) throws Exception {
            Socket client = new Socket("localhost", 9999);		// 服务器地址与端口
            Scanner input = new Scanner(System.in); 			// 键盘输入数据
            // 利用Scanner包装客户端输入数据(服务器端输出),PrintStream包装客户端输出数据;
            Scanner scan = new Scanner(client.getInputStream());
            PrintStream out = new PrintStream(client.getOutputStream());
            input.useDelimiter("\n");						// 设置键盘输入分隔符
            scan.useDelimiter("\n");						// 设置回应数据分隔符
            boolean flag = true;							// 循环标志
            while (flag) {
                System.out.print("请输入要发送数据:");
                if (input.hasNext()) {						// 键盘是否输入数据
                   String str = input.next().trim();		// 取得键盘输入数据
                   out.println(str); 						// 发送数据到服务器端
                   if (str.equalsIgnoreCase("byebye")) {	// 结束标记
                      flag = false; 						// 结束循环
                   }
                   if (scan.hasNext()) {					// 服务器端有回应
                      System.out.println(scan.next()); 		// 输出回应数据
                   }
                }
            }
            input.close();
            scan.close();
            out.close();
            client.close();
        }
    }
    /*
    程序执行结果:
        请输入您需要发送的数据:aewcdsacsa5545
        Echo:aewcdsacsa5545
        请输入您需要发送的数据:54d5f4ad
        Echo:54d5f4ad
        请输入您需要发送的数据:sdafsa4
        Echo:sdafsa4
        请输入您需要发送的数据:byebye
        拜拜,下次再见!
    */
               
  • 上面的程序实现了一个最简单的服务器端与客户端通讯,但是该程序只能连接一个客户端,不能连接其他客户端,因为所有的操作都是在主线程上进行的开发,也就是说该程序属于单线程的网络应用。而在实际的开发中一个服务器需要同时处理多个客户端的请求操作,在这样的情况下就可以利用多线程来进行操作,把每一个连接到服务器端的客户都作为一个独立的线程对象保留,如图12-3所示:
    【Java基础】网络编程:Socket、ServerSocket、Echo程序
  • 多线程服务器端实现:
    package com.yootk.demo;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.Scanner;
    class EchoThread implements Runnable {					// 建立线程类
        private Socket client;								// 每个线程处理一个客户端
        public EchoThread(Socket client) {					// 创建线程对象时传递Socket
            this.client = client;
        }
        @Override
        public void run() {	
            try {	// 每个线程对象取得各自Socket的输入流与输出流
                Scanner scan = new Scanner(client.getInputStream());
                PrintStream out = new PrintStream(client.getOutputStream());
                boolean flag = true; 							// 控制多次接收操作
                while (flag) {
                    if (scan.hasNext()) {						// 是否有内容
                       String str = scan.next().trim(); 		// 得到客户端发送的内容
                       if (str.equalsIgnoreCase("byebye")) { 	// 程序结束
                          out.println("拜拜,下次再会!");
                          flag = false; 						// 退出循环
                       } else { 								// 应该回应输入信息
                          out.println("ECHO : " + str);			// 回应信息
                       }
                    }
                }
                scan.close();
                out.close();
                client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public class EchoServer {
        public static void main(String[] args) throws Exception {
            ServerSocket server = new ServerSocket(9999);	// 在9999端口上监听
            boolean flag = true;							// 循环标记
            while (flag) {									// 接收多个客户端请求
                Socket client = server.accept(); 			// 客户端连接
                new Thread(new EchoThread(client)).start();	// 创建并启动新线程
            }
            server.close();
        }
    }
               

本章小结:

  • ServerSocket主要用于TCP协议的服务器程序开发上,使用accept()方法等待客户端连接,每个连接的客户端都是用一个SOcket表示。
  • 服务器端加入多线程机制后,就可以同时为多个用户提供服务。