网络编程相关 API 学习

目录

[1. 网络编程中的基本概念](#1. 网络编程中的基本概念)

[2. UDP 的 socket api 的使用](#2. UDP 的 socket api 的使用)

[(1) DatagramSocket API](#(1) DatagramSocket API)

[(2) DatagramPacket API](#(2) DatagramPacket API)

[(3) InetSocketAddress API](#(3) InetSocketAddress API)

[(4) 使用 UDP 的 socket api](#(4) 使用 UDP 的 socket api)

[3. TCP 的 socket api 的使用](#3. TCP 的 socket api 的使用)

[(1) ServerSocket API](#(1) ServerSocket API)

[(2) Socket API](#(2) Socket API)


1. 网络编程中的基本概念

客户端:主动发起请求的一方。

服务器:被动接受请求的一方。

客户端给服务器发起请求,服务器就会给客户端返回响应。

比如说,我去吃板面,那我一进门店,我就会对着老板说:给我来一份小碗板面(发请求),那老板就会立马去后厨,给我煮板面,等老板做好板面后,她就会给我端上来(返回响应)。

网络编程,通过网络,让两个主机之间能够进行通信,基于这样的通信来完成一定功能。

进行网络编程的时候,需要操作系统给我们提供一组 API,通过这组 API 才能完成编程。

Socket API 可以认为是应用层和传输层之间交互的路径,通过这一套 Socket API 可以完成不同主机之间,不同系统之间的网络通信。

传输层提供的网络协议主要就是两个:1. TCP 2. UDP

因为这两个协议的特性差异很大,操作起来也不同,所以系统就分别为它们各自提供了一套 API。

那么 TCP 和 UDP 有什么区别呢?

2. UDP 的 socket api 的使用

(1) DatagramSocket API

socket 其实也是操作系统的一个概念,本质上是一种特殊的文件。

Socket 就属于是把 "网卡" 这个设备,给抽象成文件了。

往 socket 文件中写数据,就相当于通过网卡发送数据,

往 socket 文件中读数据,就相当于通过网卡接收数据。

这样就把网络通信和文件操作统一了。

而 Java 中就是通过使用 DatagramSocket,来表示系统内部的 socket 文件了。

DatagramSocket 构造方法:

DatagramSocket 提供了以下方法:

因为 socket 是个文件,所以使用完后就需要关闭,要是一直频繁打开文件,而不去关闭,文件描述符表就可能会被吃满,就有可能导致文件资源泄露。

(2) DatagramPacket API

DatagramPacket 就是 UDP 数据报,UDP 会以数据报为单位发送或者接收数据。

(3) InetSocketAddress API

构造发送的数据报的时候,需要传 SocketAddress 对象,这个对象可以使用 InetSocketAddress 来创建,InetSocketAddress 是 SocketAddress 的子类。

(4) 使用 UDP 的 socket api

我们可以写一个简单的 UDP 客户端/服务器 通信的程序。

回显服务器:

这个程序没有什么业务逻辑,就是单纯调用 socket api。

让客户端发送一个请求,请求就是一个从控制台输入的字符串。

服务器收到字符串后,也会把这个字符串原封不动还给客户端,客户端再显示出来。

那就直接先创建两个类来表示 UDP 客户端 和 UDP 服务器,然后就是按照刚刚说的方式,分不同的角色,服务器就是先从客户端读取数据报,也就得到了请求字符串,然后再根据请求字符串解析成响应字符串,然后再将响应字符串构造成一个数据报,最后返回给客户端。

而客户端就是先从控制台读取请求字符串,然后将请求字符串构造成数据报发送给服务器,然后从服务器那里获取响应数据报,从而获取到响应字符串,最后将响应字符串打印在控制台上即可。

写完之后是这样的:

java 复制代码
public class UdpEchoClient {

    // 首先创建 UDP socket
    private DatagramSocket socket;

    // 这里不需要给客户端指定端口,而是让系统自动分配
    // 防止程序员自己指定的端口号跟用户主机的其他程序产生冲突
    // 需要传服务器的 ip 和端口,因为 udp 不会记录对端信息
    public UdpEchoClient(String serverIp, int serverPort) throws IOException {
        // 创建这个对象,不能手动指定端口
        socket = new DatagramSocket();
        Scanner scan = new Scanner(System.in);
        // 1. 从控制台输入请求字符串
        // 2. 构造数据报发送给服务器
        // 3. 从服务器获取响应
        // 4. 将获取到的响应字符串打印
        while (true) {
            // 读取请求
            System.out.print("->");
            String requestString = scan.next();
            // 构造请求数据包并发送
            DatagramPacket requestPacket = new DatagramPacket(requestString.getBytes(), 0, requestString.getBytes().length,
                    new InetSocketAddress(InetAddress.getByName(serverIp), serverPort));
            socket.send(requestPacket);
            // 获取响应数据报
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            String responseString = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println(responseString);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 8888);
    }
}
java 复制代码
public class UdpEchoServer {
    // 创建一个 DatagramSocket 对象,后续操作网卡的基础
    private DatagramSocket socket;

    // 构造方法, 需要传个端口号来绑定
    public UdpEchoServer(int port) throws IOException {
        // 这么写就是手动指定服务器绑定的端口
        socket = new DatagramSocket(port);
        // 1. 从客户端那里读取数据报
        // 2. 处理读到的数据
        // 3. 并构造一个数据报返回响应
        while (true) {
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            // 读取请求
            socket.receive(requestPacket);
            // 当前完成 receive 之后,数据是以 二进制 的形式存储到 DatagramPacket 中了
            // 要想把这里的数据给显示出来,还需要把这个二进制数据给转成字符串
            // 处理请求, 将二进制数据转化成字符串
            String requestString = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 根据请求计算响应(一般的服务器都会经历的过程)
            // 由于是回显服务器,请求是啥样,响应就是啥样
            String responseString = process(requestString);
            // 将响应返回给客户端
            // 往 DatagramPacket 里构造刚才的数据,再通过 send 返回
            DatagramPacket responsePacket = new DatagramPacket(responseString.getBytes(), 0, responseString.getBytes().length,
                    requestPacket.getAddress(), requestPacket.getPort());
            socket.send(responsePacket);
            // 打印一个日志,把这次数据交互的详情给打印出来
            System.out.printf("[%s:%d] req=%s,resp=%s\n", requestPacket.getAddress().toString(),
                    requestPacket.getPort(), requestString, responseString);
        }
    }

    private String process(String data) {
        return new String(data);
    }

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

运行下程序看看,首先启动服务器,再来启动客户端。

没问题。

其中需要注意的地方就是服务器需要绑定指定的端口,而客户端的端口是由系统自动分配,不需要手动绑定。

服务器绑定端口是为了方便用户来找到它,而如果客户端也绑定端口的话,这个端口号就可能会和用户主机上的程序的端口号发送冲突,所以客户端的端口号就交给系统去分配比较好,系统分配的话,就一定会分配一个空闲的端口号。

写 while true 是因为服务器不可能运行一下子就结束了,通常情况下,服务器是要 7*24 小时运行的。

没调用 close 关闭 socket 也不会出现文件资源泄露,刚刚也说过,文件资源泄露的原因是一直频繁打开文件,而不去关闭文件,这里我们的 socket 不需要 close,因为我们没有频繁的打开文件,而且不能把 socket 提前释放掉,因为客户端还需要发送请求,当我们程序结束的时候,进程就会销毁,文件描述符表也会被回收,所以自然不用担心文件资源泄露,客户端的话,使用周期比较短,进程很快就会结束掉。

核心网络编程流程:

1. 读取请求并解析

2. 根据请求计算响应

3. 把响应写回到客户端

接下来我们再来看看 TCP 的 socket api

3. TCP 的 socket api 的使用

ServerSocket 和 Socket 都是用来表示 socket 文件的(抽象了网卡这样的硬件设备)

(1) ServerSocket API

socket 的 api 差异又很大,但是和文件操作,是有密切联系的。

TCP 面向字节流,传输基本单位是 byte .

ServerSocket 是给服务器使用的类,使用这个类来绑定端口号。

前面也提到过,TCP 的特点是有连接,可靠传输,面向字节流,全双工。

那这个连接,就是通信双方会记录对方的信息,

所以使用 TCP 的方式来网络通信的话,通信双方就必须得先建立连接。

建立连接这件事操作系统内核帮我们做了,我们需要做的就是:

客户端发起建立连接的动作,然后让服务器把建立好的连接从内核中拿到应用程序里。

这个 ServerSocket 就只是用来取连接的。

然后我们再来看看 ServerSocket 的构造方法:

(2) Socket API

socket 既会给服务器用,又会给客户端用。

我们来看看 Socket 的方法:

了解了 TCP 的 socket api 后,我们就可以去写一个回显服务器啦。

还是那个核心逻辑:1. 读取请求 2. 根据请求计算响应 3. 将响应返回给客户端。

但是想要通信,首先得建立连接才行,用 ServerSocket 调用 accept 方法就能拿到连接,然后使用 Socket 来进行与客户端的通信。其实思路跟 UDP 的差不多,服务器的话,就是循环从系统内核的队列中拿连接,拿到连接后就可以通过连接得到 Socket 对象,然后就是通过 Socket 对象完成与客户端的通信,可能有多次请求,那就写个循环,然后还是先获取请求,然后根据请求计算响应,最后将响应返回给客户端(ps: 写文件的时候不要忘记调用 flush 方法冲刷缓冲区)。

然后客户端的话,还是写个循环,先从控制台输入请求,然后将请求发送给服务器,然后从服务器拿到响应,最后将响应打印在控制台上。

写完后是这样的:

先启动服务器,再来启动客户端。

看起来好像没有问题,但是不要忘记存在多个客户端的情况。

分析原因:

稍微修改下服务器的代码:

这样就没问题了。

完整代码:

java 复制代码
public class TcpEchoServer {
    // 首先创建个 ServerSocket 对象
    private ServerSocket serverSocket;

    // 写构造方法,需要指定端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }


    // 写个 start 方法,通过 start 方法来完成主要逻辑
    public void start() throws IOException {
        // 创建线程池,保证 processConnection 和 循环获取连接 能并发执行
        ExecutorService executor = Executors.newCachedThreadPool();
        // 用 serverSocket 来取连接,连接可能有多个,用循环
        while (true) {
            Socket clientSocket = serverSocket.accept();
            // 通过 processConnection 来完成服务器与客户端的交互
            executor.submit(() -> {
                processConnection(clientSocket);
            });
        }
    }

    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
        // 1. 获取请求
        // 2. 根据请求计算响应
        // 3. 返回响应
        // 通过流对象来完成,但是要记得使用完后关闭流对象
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            while (true) {
                Scanner scan = new Scanner(inputStream);
                // 首先判断是否有请求
                if (!scan.hasNext()) {
                    // 没请求的话,说明客户端下线了,那这个连接就关闭了,循环直接跳出即可
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                // 获取请求, 此处用 \n 来表示一个数据包的结束
                String request = scan.next();
                // 根据请求,计算响应
                String response = process(request);
                // 将响应返回
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                // 千万不要忘记冲刷缓冲区!!!!
                printWriter.flush();

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(8888);
        tcpEchoServer.start();
    }
}
java 复制代码
public class TcpEchoClient {
    // 先创建一个 Socket 对象
    private Socket socket;

    // 提供构造方法,传服务器的 ip 以及端口号
    public TcpEchoClient(String serverIp, int port) throws IOException {
        // 此时就相当于发送连接请求
        socket = new Socket(serverIp, port);
    }

    // 通过 start 方法来完成主逻辑
    public void start() {
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream();
             Scanner scan = new Scanner(System.in);
             Scanner scanRead = new Scanner(inputStream)) {
            while (true) {
                System.out.println("->");
                // 1. 读取输入的请求
                String request = scan.next();
                // 2. 将请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                // 不要忘记冲刷缓冲区!!!
                printWriter.flush();
                // 3. 接收响应
                String response = scanRead.next();
                // 4. 打印响应
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 8888);
        tcpEchoClient.start();
    }
}
相关推荐
tjjingpan22 分钟前
HCIA-Access V2.5_4_2_静态路由介绍
网络
这题怎么做?!?2 小时前
ARP协议及其具体过程
运维·服务器·网络
无线认证x英利检测2 小时前
进网许可认证、交换路由设备检测项目更新25年1月起
网络·智能路由器
卡卡大怪兽2 小时前
fastAPI接口的请求与响应——基础
服务器·网络·fastapi
昌sit!2 小时前
监控IP频繁登录服务器脚本
服务器·网络·tcp/ip
代码洁癖症患者3 小时前
HTTP请求的奇幻旅程:从发起至响应的全方位探索
网络·网络协议·http
凹凸撒man3 小时前
AUTOSAR TCP中的MSS和MTU的关系
网络·tcp/ip·autosar
岳不谢3 小时前
华为DHCP高级配置学习笔记
网络·笔记·网络协议·学习·华为
m0_748255024 小时前
新手参加2025年CTF大赛——Web题目的基本解题流程
前端·网络·安全
龙少95435 小时前
【深入理解网络协议】
网络·网络协议