网络编程相关 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();
    }
}
相关推荐
我也要当昏君42 分钟前
6.3 文件传输协议 (答案见原书 P277)
网络
Greedy Alg1 小时前
Socket编程学习记录
网络·websocket·学习
刘逸潇20052 小时前
FastAPI(二)——请求与响应
网络·python·fastapi
Mongnewer2 小时前
通过虚拟串口和网络UDP进行数据收发的Delphi7, Lazarus, VB6和VisualFreeBasic实践
网络
我也要当昏君3 小时前
6.5 万维网(答案见原书P294)
网络
嶔某3 小时前
网络:传输层协议UDP和TCP
网络·tcp/ip·udp
文火冰糖的硅基工坊4 小时前
[嵌入式系统-154]:各种工业现场总线比较
网络·自动驾驶·硬件架构
以己之4 小时前
详解TCP(详细版)
java·网络·tcp/ip
Ronin3054 小时前
【Linux网络】封装Socket
linux·网络·socket·网络通信
Brianna Home4 小时前
博客安全攻防演练:从攻击者视角构筑铜墙铁壁
网络·数据库·安全·oracle