【Java网络编程】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

Java是一种广泛应用于网络编程的编程语言。通过Java的网络编程能力,我们可以构建强大的网络应用程序。本文将介绍Java网络编程的基础知识、常用API和一些实践技巧,帮助读者更好地理解和应用Java网络编程。


一、Java网络编程基础

在开始探讨Java网络编程之前,我们需要了解一些基本概念和术语。网络编程是指通过计算机网络进行数据交换和通信的过程。Java提供了一套完善的网络编程API,涵盖了各种协议和服务,如TCP/IP、HTTP、UDP等。
1.网络应用模型

  1. **C/S:**这里的C是指Client,即客户端。而S是指Server,即服务端。网络上的的应用本质上就是两台计算机上的软件进行交互。而客户端和服务端就是对应的两个应用程序,即客户端应用程序和服务端应用程序

  2. **B/S:**这里的B是Browser,即浏览器,而S是指Server。浏览器是一个通用的客户端,可以与不同的服务端进行交互。但是本质上B/S还是C/S结构,只不过浏览器是一个通用的客户端而已。
    2.可靠传输与不可靠传输

    TCP协议与UDP协议都是传输协议,客户端程序与服务端程序基于这些协议完成网络间的数据交互。

  3. TCP是可靠传输协议,是面向连接的协议,保证数据传输中的可靠性和完整性。

    TCP保证可靠传输,但是传输效率低,占用带宽高。

  4. UDP是不可靠传输协议,不保证数据传输的完整性。

    UDP不保证可靠传输,但是传输速度块,占用带宽小
    3.Socket与ServerSocket
    - java.net.Socket

    Socket(套接字)封装了TCP协议的通讯细节,是的我们使用它可以与服务端建立网络链接,并通过 它获取两个流(一个输入一个输出),然后使用这两个流的读写操作完成与服务端的数据交互
    java.net.ServerSocket

  5. ServerSocket运行在服务端,作用有两个:

    • 向系统申请服务端口,客户端的Socket就是通过这个端口与服务端建立连接的。
    • 监听服务端口,一旦一个客户端通过该端口建立连接则会自动创建一个Socket,并通过该Socket与客户端进行数据交互。

二、Java网络编程常用API

1 .Socket类:Socket类是Java网络编程中最常用的类之一,它提供了创建客户端套接字的方法和与服务器进行通信的能力。通过Socket,可以建立与远程服务器的连接,并进行数据传输。
Socket提供了两个重要的方法:

  • OutputStream getOutputStream()
    该方法会获取一个字节输出流,通过这个输出流写出的字节数据会通过网络发送给对方。
  • InputStream getInputStream()
    通过该方法获取的字节输入流读取的是远端计算机发送过来的数据。
    原理图

例:

java 复制代码
String host = "127.0.0.1";
int port = 8080;

// 创建Socket对象并连接服务器
Socket socket = new Socket(host, port);

// 获取输入流和输出流进行数据传输
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();

// 向服务器发送数据
String message = "Hello, Server!";
outputStream.write(message.getBytes());

// 从服务器接收数据
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
String response = new String(buffer, 0, length);

// 关闭资源
outputStream.close();
inputStream.close();
socket.close();

2.ServerSocket类:ServerSocket类用于创建服务器套接字,接受来自客户端的连接请求,并创建对应的Socket对象进行通信。通过ServerSocket,可以监听指定的端口,等待客户端的连接。

java 复制代码
int port = 8080;

// 创建ServerSocket对象并监听指定端口
ServerSocket serverSocket = new ServerSocket(port);

// 接受客户端的连接请求
Socket socket = serverSocket.accept();

// 获取输入流和输出流进行数据传输
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();

// 接收客户端发送的数据
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
String request = new String(buffer, 0, length);

// 处理请求并返回响应数据
String response = "Hello, Client!";
outputStream.write(response.getBytes());

// 关闭资源
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();

3.URL类:URL类提供了对统一资源定位符(URL)的解析和处理能力,它可以用于打开和读取网络资源。通过URL,可以创建一个表示网络资源的对象,并获取资源的各种属性信息。

java 复制代码
String urlString = "https://www.example.com";

// 创建URL对象
URL url = new URL(urlString);

// 打开连接并获取输入流
InputStream inputStream = url.openStream();

// 读取数据
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
StringBuilder responseBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
    responseBuilder.append(line);
}

// 关闭资源
reader.close();
inputStream.close();

4.HttpURLConnection类:HttpURLConnection类是一种常用的建立HTTP连接的方式,它继承自URLConnection类,在Java中用于发送HTTP请求和接收HTTP响应。通过HttpURLConnection,可以设置请求头、发送请求参数,以及获取服务器返回的响应数据。

java 复制代码
String urlString = "https://www.example.com";

// 创建URL对象
URL url = new URL(urlString);

// 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

// 设置请求方法
connection.setRequestMethod("GET");

// 发送请求并获取响应码
int responseCode = connection.getResponseCode();

// 读取响应数据
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder responseBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
    responseBuilder.append(line);
}

// 关闭资源
reader.close();
connection.disconnect();

5 .DatagramSocket类:DatagramSocket类用于进行UDP数据包的发送和接收,适用于一些实时性要求较高的应用场景。通过DatagramSocket,可以实现基于UDP协议的数据传输。

1.发送数据报文

java 复制代码
String host = "127.0.0.1";
int port = 9999;

// 创建DatagramSocket对象
DatagramSocket socket = new DatagramSocket();

// 准备数据
String message = "Hello, Server!";
byte[] data = message.getBytes();

// 创建DatagramPacket对象并设置目标地址和端口号
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(host), port);

// 发送数据报文
socket.send(packet);

// 关闭资源
socket.close();

2.接收数据报文

java 复制代码
int port = 9999;

// 创建DatagramSocket对象并监听指定端口
DatagramSocket socket = new DatagramSocket(port);

// 准备接收数据
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

// 接收数据报文
socket.receive(packet);

// 处理接收到的数据
String request = new String(packet.getData(), 0, packet.getLength());

// 关闭资源
socket.close();

三、Java网络编程实践技巧

与服务端建立连接案例

例:聊天室

java 复制代码
package socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * 聊天室客户端
 */
public class Client {
    /*
java.net.Socket 套接字
Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
与远端计算机的数据交互工作。
我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
风(输出流),通过它们就可以与对方交流了。
*/
    private Socket socket;

    /**
     * 构造方法,用来初始化客户端
     */
    public Client() {
        try {
            System.out.println("正在连接服务器......");
            /*
        实例化Socket时要传入两个参数
        参数1:服务端的地址信息
        可以是IP地址,如果链接本机可以写"localhost"
        参数2:服务端开启的服务端口
        我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
        的服务端应用程序。
        实例化的过程就是链接的过程,如果链接失败会抛出异常:
        java.net.ConnectException: Connection refused: connect
        */
            socket = new Socket("localhost",8088);
            System.out.println("与服务器建立连接了!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端开始工作的方法
     */
    public void start() {
        try {
            /*
            Socket提供了一个方法:
            OutputStream getOutputStream()
            该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
            */
            //低级流,将字节通过网络发送给对方
            OutputStream out = socket.getOutputStream();
            //高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
            OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
            //高级流,负责块写文本数据加速
            BufferedWriter bw = new BufferedWriter(osw);
            //高级流,负责按行写出字符串,自动行刷新
            PrintWriter pw = new PrintWriter(bw,true);

            Scanner scanner = new Scanner(System.in);
            while(true) {
                String line = scanner.nextLine();
                if("exit".equalsIgnoreCase(line)) {
                    break;
                }
                pw.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                /*
                通讯完毕后调用socket的close方法。
                该方法会给对方发送断开信号。
                */
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Client client = new Client();
        client.start();
    }
}
java 复制代码
package socket;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * 聊天室服务器
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     * 就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server() {
        try {
            /*
            ServerSocket实例化的同时指定服务端口
            如果该端口被其他程序占用则会抛出异常:
            java.net.BindException:address already in use
            此时我们需要更换端口,或者杀死占用该端口的进程。
            端口号范围:0-65535
            */
            System.out.println("正在启动服务......");
            /*
            实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
            应用程序占用的端口相同,否则会抛出异常:
            java.net.BindException:address already in use
            端口是一个数字,取值范围:0-65535之间。
            6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
            */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务器启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 服务端开始工作的方法
     */
    public void start() {

        try {
            /*
            ServerSocket的一个重要方法:
            Socket accept()
            该方法用于接受客户端的连接。这是一个阻塞方法,调用后会"卡住",直到
            一个客户端与ServerSocket连接,此时该方法会立即返回一个Socket实例
            通过这个Socket实例与该客户端对等连接并进行通讯。
            相当于"接电话"的动作
            */
            while(true){
                System.out.println("等待客户端连接......");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了!");
                //启动一个线程负责与客户端交互
                ClientHandler handler = new ClientHandler(socket);
                //创建线程
                Thread t = new Thread(handler);
                //启动线程
                t.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 创建线程的方式:实现Runnable接口单独定义线程任务
     * 这个线程任务就是让一个线程与指定的客户端进行交互
     */
    private class ClientHandler implements Runnable{
        private Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }
        @Override
        public void run() {

            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                String line;
                while((line = br.readLine()) != null){
                    System.out.println("客户端说:" + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

需要注意的几个点:

  • 当客户端不再与服务端通讯时,需要调用socket.close()断开链接,此时会发送断开链接的信号给服
    务端。这时服务端的br.readLine()方法会返回null,表示客户端断开了链接。
  • 当客户端链接后不输入信息发送给服务端时,服务端的br.readLine()方法是出于阻塞状态的,直到
    读取了一行来自客户端发送的字符串。

总结

通过深入理解Java网络编程,我们可以构建强大的网络应用程序,满足不同的业务需求。希望读者通过本文的学习,能够掌握Java网络编程的核心概念和技术,提升自己的开发能力。

相关推荐
chuanauc20 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴36 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao43 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc7871 小时前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
靡樊1 小时前
NAT、代理服务、内网穿透
网络·内网穿透·nat·代理服务·内网打洞
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
一只栖枝7 小时前
网络安全 vs 信息安全的本质解析:数据盾牌与网络防线的辩证关系关系
网络·网络安全·信息安全·it·信息安全认证
云泽野7 小时前
【Java|集合类】list遍历的6种方式
java·python·list