JavaEE-- 网络编程 Socket套接字

Socket套接字

概念

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。 基于Socket套接字的网络程序开发就是网络编程。

分类

Socket套接字主要针对传输层协议划分为如下三类:

流套接字

流套接字:使用传输层TCP协议 TCP,即TransmissionControlProtocol(传输控制协议),传输层协议。

以下为TCP的特点(细节后续再学习):

• 有连接

• 可靠传输

• ⾯向字节流

• 有接收缓冲区,也有发送缓冲区

• 大小不限 对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的 情况下,是无边界的数据,可以多次发送,也可以分开多次接收。

数据报套接字

数据报套接字:使用传输层UDP协议 UDP,即UserDatagramProtocol(用户数据报协议),传输层协议。 以下为UDP的特点(细节后续再学习):

• 无连接

• 不可靠传输

• 面向数据报

• 有接收缓冲区,无发送缓冲区

• 大小受限:⼀次最多传输64k 对于数据报来说,可以简单的理解为,传输数据是⼀块⼀块的,发送⼀块数据假如100个字节,必须⼀ 次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

原始套接字

原始套接字:原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。

我们不学习原始套接字,简单了解即可。

Java数据报套接字通信模型

对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且⼀次发送全部 数据报,⼀次接收全部的数据报。

java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用 DatagramPacket 作为发送或接收的UDP数据报。对于⼀次发送及接收UDP数据报的流程如下:

以上只是⼀次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请 求,没有响应。对于⼀个服务端来说,重要的是提供多个客⼾端的请求处理及响应,流程如下:

Java流套接字通信模型

Socket编程注意事项

  1. 客户端和服务端:开发时,经常是基于⼀个主机开启两个进程作为客户端和服务端,但真实的场 景,⼀般都是不同主机。

  2. 注意目的IP和目的端口号,标识了⼀次数据传输时要发送数据的终点主机和进程

  3. Socket编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应⽤层协议, 也需要考虑,这块我们在后续来说明如何设计应⽤层协议。

  4. 关于端口被占用的问题

  5. 如果⼀个进程A已经绑定了⼀个端口,再启动⼀个进程B绑定该端端口,就会报错,这种情况也叫端口 被占用。对于java进程来说,端口被占用的常见报错信息如下:

计算机中 "文件" 的广义概念与 Socket 对网卡的抽象管理

  • 广义文件概念:计算机中的 "文件" 是抽象化管理概念,不仅包含普通存储文件,还可代指硬件设备。操作系统将硬件设备抽象为文件,实现统一管理。
  • 网卡与 Socket 文件的关联 :网卡被抽象为socket文件,操作网卡的流程与操作普通文件一致,遵循打开→读写→关闭的步骤,且会在文件描述符表中分配表项。
  • Socket 文件的作用:直接操作网卡难度较大,Socket 文件相当于 "网卡的遥控器",将对网卡的操作转换为对 Socket 文件的操作,降低了硬件操作的复杂度,让开发者能以熟悉的文件操作逻辑处理网络通信。

解决端口被占用的问题:

◦ 如果占用端口的进程A不需要运行,就可以关闭A后,再启动需要绑定该端口的进程B

◦ 如果需要运行A进程,则可以修改进程B的绑定端口,换为其他没有使用的端口。

UDP数据报套接字编程

API 介绍

DatagramSocket:

DatagramSocket 是UDP 的Socket,用于发送和接收UDP数据报。

DatagramSocket 构造方法: 就是 打开 '文件'

DatagramSocket 方法:receive 就是读取 ,send 就是写

DatagramPacket

DatagramPacket是UDP Socket发送和接收的数据报。

DatagramPacket表示UDP完整的数据报

DatagramPacket 构造方法:

UDP数据包的载荷数据,就饿可以通过构造方法来指定

DatagramPacket 方法:

构造UDP发送的数据报时,需要传入 SocketAddress ,该对象可以使用 InetSocketAddress 来创建。

InetSocketAddress

InetSocketAddress ( SocketAddress 的子类)构造方法:

注意:真实的服务器响应与请求是不一样的,这个是回显服务器

updSevrve示例:

核心思路:

具体实现:

  • DatagramPacket是 Java 中用于表示 UDP 数据报的类。
  • new byte[4096]:创建一个长度为 4096 字节的字节数组,用来存储接收到的 UDP 数据报的载荷部分(即实际传输的有效数据)。
  • length: 4096:指定接收数据的最大长度为 4096 字节,确保接收到的数据不会超出字节数组的容量。
将 UDP 数据报中接收到的二进制数据转换为字符串
  • requestPacket.getData():获取存储 UDP 数据报载荷的字节数组(即接收到的二进制数据)。
  • offset: 0:指定从字节数组的起始位置(第 0 个字节)开始转换。
  • requestPacket.getLength():获取实际接收到的数据长度,确保只转换有效数据部分。
打印 UDP 通信请求与响应日志
receive() 方法的 DatagramPacket 参数是一个典型的"输出型参数":

输出型参数:调用方提供一个"空容器",方法执行后这个容器被填充了数据。

为什么不用手动close 关闭socket?

UDP 服务器中Socket对象的生命周期和资源释放:

  • Socket 对象的生命周期 :在 UDP 服务器中,Socket对象会伴随服务器的整个运行过程,从服务器启动到停止,始终用于处理客户端的 UDP 数据报通信。
  • 资源释放机制 :当服务器进程结束(如关闭服务器程序)时,操作系统会自动释放该进程在 PCB(进程控制块)文件描述符表中占用的所有资源,因此不需要手动调用close方法来释放Socket资源。

PCB 是 进程控制块(Process Control Block) 的缩写,是操作系统用于管理进程的核心数据结构

服务器启动后,客户端还没请求时,服务器逻辑在做什么?

服务器会在 socket.receive(requestPacket) 处阻塞,直到客户端发送 UDP 请求才会继续执行。

java 复制代码
package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        // 指定了一个固定端口号, 让服务器来使用.
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        // 启动服务器
        System.out.println("服务器启动");

        while (true) {
            // 循环一次, 就相当于处理一次请求.
            // 处理请求的过程, 典型的服务器都是分成三个步骤的.
            // 1. 读取请求并解析.
            //    DatagramPacket 表示一个 UDP 数据报. 此处传入的字节数组, 就保存 UDP 的载荷部分.
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(requestPacket);
            //    把读取到的二进制数据, 转成字符串. 只是构造有效的部分.
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());

            // 2. 根据请求, 计算响应. (服务器最关键的逻辑)
            //    但是此处写的是回显服务器. 这个环节相当于省略了.
            String response = process(request);

            // 3. 把响应返回给客户端
            //    根据 response 构造 DatagramPacket, 发送给客户端.
            //    此处不能使用 response.length()
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());
            //    此处还不能直接发送. UDP 协议自身没有保存对方的信息(不知道发给谁)
            //    需要指定 目的 ip 和 目的端口.
            socket.send(responsePacket);

            // 4. 打印一个日志
            System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(),
                    request, response);
        }
    }

    // 后续如果要写别的服务器, 只修改这个地方就好了.
    // 不要忘记, private 方法不能被重写. 需要改成 public
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

updClient示例:

我们的数据包中,只存储了他自己的IP和端口号,所以客户端这里要记一下服务器的IP和端口号

客户端访问服务器时,IP 和端口是如何分配的?

-目的 IP:服务器的 IP(serverIP)。

-目的端口:服务器的端口(serverport)。

-源 IP:客户端所在主机的 IP。

-源端口:操作系统随机分配的空闲端口。

getByName(): 解析服务器 IP 地址
127.0.0.1(环回 IP)
服务器与客户端之间要联网么?
java 复制代码
package network;

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

public class UdpEchoClient {
    private DatagramSocket socket = null;

    // UDP 本身不保存对端的信息, 就自己的代码中保存一下
    private String serverIp;
    private int serverPort;

    // 和服务器不同, 此处的构造方法是要指定访问的服务器的地址.
    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1. 从控制台读取用户输入的内容.
            System.out.println("请输入要发送的内容:");
            if (!scanner.hasNext()) {
                break;
            }
            String request = scanner.next();
            // 2. 把请求发送给服务器, 需要构造 DatagramPacket 对象.
            //    构造过程中, 不光要构造载荷, 还要设置服务器的 IP 和端口号
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            // 3. 发送数据报
            socket.send(requestPacket);
            // 4. 接收服务器的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            // 5. 从服务器读取的数据进行解析, 打印出来.
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}
相关推荐
leonardee5 小时前
Spring Security安全框架原理与实战
java·后端
q***5185 小时前
Spring Cloud gateway 路由规则
java
空空kkk6 小时前
SpringMVC框架——入门
java·spring
liyi_hz20086 小时前
云原生 + 国产化适配:O2OA (翱途)开发平台后端技术栈深度解析
java·后端·开源软件
⑩-6 小时前
缓存穿透,击穿,雪崩
java·redis
合作小小程序员小小店6 小时前
web网页开发,在线%考试管理%系统,基于Idea,html,css,jQuery,java,jsp,servlet,mysql。
java·前端·intellij-idea
程序媛_MISS_zhang_01106 小时前
vant-ui中List 组件可以与 PullRefresh 组件结合使用,实现下拉刷新的效果
java·linux·ui
曹牧6 小时前
Java中处理URL转义并下载PDF文件
java·开发语言·pdf
White_Can6 小时前
《计算机网络:物理层》
计算机网络
雾山大叔7 小时前
Python学习 - 面向对象学习-文件分类小测试
java·前端·spring