Java基础教程之网络编程
- 🔹本节学习目标
- 1️⃣ 网络编程的概念
- 🔍 TCP和 UDP协议
- 2️⃣ Socket 与ServerSocket 类
- 3️⃣ 网络编程实战——Echo 程序
- 4️⃣ 应用场景
- 🌾 总结
🔹本节学习目标
- 了解多线程与网络编程的操作关系;
- 了解网络程序开发的主要模式;
- 了解 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端口。
像 ASP
、PHP
、JSP
等都属于 B/S
的常见开发技术,这些都需要单独的服务器支持。而这些 B/S
技术要想实现互相访问,则需要Web Service
技术支持。
🔍 TCP和 UDP协议
- TCP(传输控制协议):
TCP
提供了可靠的、面向连接的通信。它通过建立一个持久的连接,在发送数据之前,会先进行握手过程来确保双方的通信正常。这种可靠性是通过使用确认机制和重传机制来实现的,因此在传输过程中,既可以保证数据的顺序不变,也可以确保数据不被丢失或损坏。TCP
适合于需要确保数据完整、按照顺序到达的应用场景,例如文件传输、HTTP请求等。
- UDP(用户数据报协议):
UDP
是一种无连接的协议,它不需要先建立连接就能直接发送数据。这意味着它没有像TCP
那样的握手和断开连接的开销,具有较低的延迟和网络负载。UDP
提供了一种快速而简单的数据传输方式,但不保证数据的可靠性和顺序性。由于缺乏确认机制和重传机制,并且数据包可能在传输过程中丢失或乱序,所以需要应用层来处理可靠性和有序性的问题。UDP
适用于那些对实时性要求较高,但对数据完整性和顺序性要求相对较低的应用场景,例如音视频传输、实时游戏等。
在网络编程中,开发人员可以根据具体的业务需求选择使用TCP
或UDP
。如果需要确保可靠的数据传输和有序性,则选择TCP
;如果追求更低的延迟并能容忍一些数据丢失或乱序的情况,则可以选择UDP
。在某些情况下,也可以同时使用两种协议来处理不同类型的数据。
2️⃣ Socket 与ServerSocket 类
java.net
包提供了网络编程有关的开发工具类,在此包中有以下两个主要的核心操作类。
ServerSocket
类:是一个封装支持 TCP 协议的操作类,主要工作在服务器端,用于接收客户端请求;Socket
类:也是一个封装了 TCP 协议的操作类,每一个Socket
对象都表示一个客户端。
下面列出了ServerSocket
类的常用操作方法:
方法名称 | 类型 | 描述 |
---|---|---|
public ServerSocket(int port) throws IOException | 构造 | 开辟一个指定的端口监听, 一般使用 5000以上的端口 |
public Socket accept() throws IOException | 普通 | 服务器端接收客户端请求,通过Socket 返回 |
public void close() throws IOException | 普通 | 关闭服务器端 |
下面列出了Socket
类的常用操作方法:
方法名称 | 类型 | 描述 |
---|---|---|
public Socket(String host, int port) throws UnknownHostException, IOException | 构造 | 指定要连接的主机(IP地址)和端口 |
public OutputStream getOutputStream() throws IOException | 普通 | 取得指定客户端的输出对象,使用 PrintStream操作 |
public InputStream getInputStream() throws IOException | 普通 | 从指定的客户端读取数据,使用 Scanner操作 |
在客户端,程序可以通过 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();
}
}
由于服务器端需要接收以及回应客户端的请求,所以在程序开始就首先取得了客户端的输入流与输出流,同时为了方便数据的读取与输出,分别使用了 Scanner
与 PrintStream
进行 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网络编程具有广泛的应用场景,包括但不限于以下几个方面:
-
客户端-服务器通信:Java网络编程可以用于构建基于
C/S
架构的分布式系统。通过建立连接、传输数据和响应请求,可以实现客户端与服务器之间的通信,例如网站服务器与浏览器之间的HTTP通信、即时通讯程序等。 -
文件传输和共享:Java网络编程可以用于文件传输和共享。通过建立TCP连接,可以在客户端和服务器之间传输文件,如FTP(文件传输协议)或SFTP(SSH文件传输协议)。
-
远程过程调用(RPC):Java网络编程可以支持远程过程调用,使得应用程序可以在不同的主机上相互调用函数或方法。这种方式可以实现分布式计算和服务架构,常见的例子是使用Java RMI(远程方法调用)、Apache Thrift、gRPC等。
-
Socket编程:Java网络编程中的Socket API允许开发者直接控制网络连接和数据传输。可以创建基于TCP或UDP的Socket,实现点对点的通信,例如实时游戏、聊天应用等。
-
网络爬虫和数据采集:Java网络编程提供了强大的能力来进行网页抓取和数据采集。通过使用网络库和相关API,可以模拟浏览器行为,发送HTTP请求、解析HTML、提取数据并进行存储和分析。
总之,Java网络编程广泛应用于各种场景,包括网络服务端开发、网页爬取、远程过程调用、文件传输和Socket通信等。通过利用Java提供的网络API和库,开发人员可以构建高效、可靠的网络应用程序和分布式系统。
🌾 总结
本文介绍了网络编程的概念以及与之相关的TCP
和UDP
协议。我们探讨了Java中的 Socket
和 ServerSocket
类,这些重要的API用于实现网络通信。通过一个简单的实战示例——Echo程序,我们展示了如何使用Java进行网络编程,并解释了其工作原理。
网络编程在现代应用开发中扮演着至关重要的角色。它允许不同主机之间的数据交互,使得分布式系统成为可能。同时,网络编程也适用于各种场景,包括客户端-服务器通信、网络爬虫和数据采集、文件传输和共享、远程过程调用等。
无论是构建一个Web应用程序还是设计一个分布式系统,掌握网络编程技能都是至关重要的。Java提供了强大而丰富的网络编程库和API,支持多种协议和通信方式。通过理解并灵活运用这些概念和工具,开发人员可以轻松构建高性能、可靠的网络应用程序,并满足不同应用场景的需求。
《【Java基础教程】(四十六)IO篇 · 下:System类对IO的支持:错误输出、信息输出、系统输入,字符缓冲流、扫描流和对象序列化流~》