【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网络编程】_第1张图片

二、Java网络编程常用API

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

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

例:

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,可以监听指定的端口,等待客户端的连接。

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,可以创建一个表示网络资源的对象,并获取资源的各种属性信息。

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,可以设置请求头、发送请求参数,以及获取服务器返回的响应数据。

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.发送数据报文

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.接收数据报文

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网络编程实践技巧

与服务端建立连接案例
例:聊天室

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();
    }
}

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网络编程】_第3张图片

总结

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

你可能感兴趣的:(网络,java,intellij,idea)