【Java基础教程】(四十七)网络编程篇:网络通讯概念,TCP、UDP协议,Socket与ServerSocket类使用实践与应用场景~-LMLPHP

🔹本节学习目标

  • 了解多线程与网络编程的操作关系;
  • 了解网络程序开发的主要模式;
  • 了解 TCP 程序的基本实现;

1️⃣ 网络编程的概念

在Java中,网络编程的核心意义是实现不同电脑主机之间的数据交互。Java采用了一种简化的概念,将这个过程进一步抽象为JVM(Java虚拟机)进程之间的通信。可以在同一台电脑上同时运行多个JVM进程,而这些不同的JVM进程能够相互通信,它们在网络编程中被视为不同的主机。


每个JVM进程都有自己的内存空间和资源,并且可以在不同的物理机或同一台物理机上运行。通过使用Java提供的网络编程API,我们可以在这些JVM进程之间建立连接、发送和接收数据。

这种以JVM进程划分网络的方式带来了一些优势。首先,与传统的网络编程相比,它提供了更高层次的抽象,使得开发人员可以更方便地处理网络通信。其次,由于JVM进程可以在同一台物理机上运行,它们之间的通信速度更快,并且可以共享某些资源,从而提高了效率。

此外,我们前面介绍过的多线程篇中,也提供了一些并发编程的机制,如线程和锁,使得多个JVM进程之间的数据交互更加安全可靠。

而不同JVM进程间,彼此的数据访问也属于远程访问。在Java中存在的远程方法调用(Remote Method Invocation,RMI) 技术或企业 JavaBean(Enterprise JavaBean, EJB) 也都是依靠此概念进行使用的。

网络编程的实质意义在于数据的交互,而在交互过程中一定就会分为服务器端与客户端,而这两端的开发就会存在以下两种模式。

  • C/S 结构 (Client / Server), 此类模式的开发一般要编写两套程序,一套是客户端代码,另外一套属于服务器端代码。由于需要有编写程序,所以对于开发以及维护的成本较高。但是由于其使用的是自己的连接端口与交换协议,所以安全性比较高。而 C/S 结构程序的开发分为两种:
    • TCP (传输控制协议,可靠的传输);
    • UDP (数据报协议)。
  • B/S 结构 (Browser /Server), 不再单独开发客户端代码,只开发一套服务器端程序,客户端将利用浏览器进行访问,这种模式只需要开发一套程序,但是安全性不高,因为使用的是公共的 HTTP 协议以及公共的80端口。

ASPPHPJSP 等都属于 B/S 的常见开发技术,这些都需要单独的服务器支持。而这些 B/S 技术要想实现互相访问,则需要Web Service技术支持。

🔍 TCP和 UDP协议

在网络编程中,开发人员可以根据具体的业务需求选择使用TCPUDP。如果需要确保可靠的数据传输和有序性,则选择TCP;如果追求更低的延迟并能容忍一些数据丢失或乱序的情况,则可以选择UDP。在某些情况下,也可以同时使用两种协议来处理不同类型的数据。

2️⃣ Socket 与ServerSocket 类

java.net 包提供了网络编程有关的开发工具类,在此包中有以下两个主要的核心操作类。

  • ServerSocket 类:是一个封装支持 TCP 协议的操作类,主要工作在服务器端,用于接收客户端请求;
  • Socket 类:也是一个封装了 TCP 协议的操作类,每一个Socket 对象都表示一个客户端。

下面列出了ServerSocket 类的常用操作方法:

下面列出了Socket 类的常用操作方法:

在客户端,程序可以通过 Socket 类的 getInputStream()方法,取得服务器的输出信息,在服务器端可以通过 getOutputStream() 方法取得客户端的输出信息,如下所示。


在进行网络程序的开发中,最为重要的就是服务器端的功能。下边范例操作定义的服务器端将针对连接的客户端发出一个 “Hello World” 的字符串信息。

//	范例 1: 定义服务器端——主要使用 ServerSocket
package com.xiaoshan.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.close();
		client.close();
		server.close();
	}
}

程序执行结果:

等待客户端连接…

从程序执行结果可以看到,程序将出现阻塞情况, 一直到客户端连接后才会继续执行。

此程序在本机的9999端口上设置了一个服务器的监听操作,accept()方法表示打开服务器监听,这样当有客户端通过 TCP 连接方式连接到服务器端后,服务器端将利用 PrintStream 输出数据,当数据输出完毕后该服务器端就将关闭,所以本次定义的服务器只能处理一次客户端的请求。

//	范例 2: 编写客户端——Socket
package com.xiaoshan.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");	//设置分隔符
		if (scan.hasNext()){	//是否有数据
			System.out.println("【回应数据】"+ scan.next());	//取出数据
		}
		scan.close();
		client.close();
	}
}

程序执行结果:

【回应数据】Hello World !

在 TCP 程序中,每一个 Socket 对象都表示一个客户端的信息,所以客户端程序要连接也必须依靠 Socket 对象操作。在实例化 Socket 类对象时必须设置要连接的主机名称(本机为 localhost,或者填写 IP 地址)以及连接端口号,当连接成功后就可以利用 Scanner 进行输入流数据的读取,这样就可以接收服务器端的回应信息。

3️⃣ 网络编程实战——Echo 程序

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

//	范例 3: 实现服务器端
package	com.xiaoshan.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.equalslgnoreCase("byebye")){ 	//程序结束标记
					out.println("拜拜,再见!"); 		//输出结束信息
					flag = false;					//退出循环
				}else{
					out.println("ECHO:" + str);		//回应输入信息,加“ECHO:” 前缀返回
				}
			}
		}
		scan.close();
		out.close();
		client.close();
		server.close();
	}
}

由于服务器端需要接收以及回应客户端的请求,所以在程序开始就首先取得了客户端的输入流与输出流,同时为了方便数据的读取与输出,分别使用了 ScannerPrintStream 进行 IO 的操作包装。考虑到该服务器端需要与客户端进行重复的数据交互,所以使用了一个 while 循环来不断实现数据的接收与输出。

//	范例 4: 定义客户端
package com.xiaoshan.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();
	}
}

程序实现了键盘数据的输入与发送操作,每当用户输入完信息后会将该信息发送到服务器端,只要发送的数据不是 “byebye” ,服务器端都会将发送的数据处理后再发送回客户端。由于需要重复输入, 所以在客户端上也使用了一个 while 循环进行控制。

范例4 就实现了一个最简单的服务器端与客户端通讯,但是该程序只能连接一个客户端,不能连接其他客户端,因为所有的操作都是在主线程上进行的开发,也就是说该程序属于单线程的网络应用。而在实际的开发中一个服务器需要同时处理多个客户端的请求操作,在这样的情况下就可以利用多线程来进行操作,把每一个连接到服务器端的客户都作为一个独立的线程对象保留,如下所示。


//	范例 5:修改服务器端
package com.xiaoshan.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();
	}
}

程序使用了多线程的概念来处理每一个客户端的请求,这样服务器就可以同时处理多个客户端的连接操作。当有新的客户端连接到服务器端后,会启动一个新的线程,这样在此线程中就会各自处理每 一个客户端的输入与输出操作。

4️⃣ 应用场景

Java网络编程具有广泛的应用场景,包括但不限于以下几个方面:

  1. 客户端-服务器通信:Java网络编程可以用于构建基于C/S架构的分布式系统。通过建立连接、传输数据和响应请求,可以实现客户端与服务器之间的通信,例如网站服务器与浏览器之间的HTTP通信、即时通讯程序等。

  2. 文件传输和共享:Java网络编程可以用于文件传输和共享。通过建立TCP连接,可以在客户端和服务器之间传输文件,如FTP(文件传输协议)或SFTP(SSH文件传输协议)。

  3. 远程过程调用(RPC):Java网络编程可以支持远程过程调用,使得应用程序可以在不同的主机上相互调用函数或方法。这种方式可以实现分布式计算和服务架构,常见的例子是使用Java RMI(远程方法调用)、Apache Thrift、gRPC等。

  4. Socket编程:Java网络编程中的Socket API允许开发者直接控制网络连接和数据传输。可以创建基于TCP或UDP的Socket,实现点对点的通信,例如实时游戏、聊天应用等。

  5. 网络爬虫和数据采集:Java网络编程提供了强大的能力来进行网页抓取和数据采集。通过使用网络库和相关API,可以模拟浏览器行为,发送HTTP请求、解析HTML、提取数据并进行存储和分析。

总之,Java网络编程广泛应用于各种场景,包括网络服务端开发、网页爬取、远程过程调用、文件传输和Socket通信等。通过利用Java提供的网络API和库,开发人员可以构建高效、可靠的网络应用程序和分布式系统。

🌾 总结

本文介绍了网络编程的概念以及与之相关的TCPUDP协议。我们探讨了Java中的 SocketServerSocket类,这些重要的API用于实现网络通信。通过一个简单的实战示例——Echo程序,我们展示了如何使用Java进行网络编程,并解释了其工作原理。

网络编程在现代应用开发中扮演着至关重要的角色。它允许不同主机之间的数据交互,使得分布式系统成为可能。同时,网络编程也适用于各种场景,包括客户端-服务器通信、网络爬虫和数据采集、文件传输和共享、远程过程调用等。

无论是构建一个Web应用程序还是设计一个分布式系统,掌握网络编程技能都是至关重要的。Java提供了强大而丰富的网络编程库和API,支持多种协议和通信方式。通过理解并灵活运用这些概念和工具,开发人员可以轻松构建高性能、可靠的网络应用程序,并满足不同应用场景的需求。


【Java基础教程】(四十七)网络编程篇:网络通讯概念,TCP、UDP协议,Socket与ServerSocket类使用实践与应用场景~-LMLPHP
07-27 00:37