网络编程相关 API 学习

news2024/12/5 2:44:27

目录

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

2. UDP 的 socket api 的使用

(1) DatagramSocket API

(2) DatagramPacket API

(3) InetSocketAddress API

(4) 使用 UDP 的 socket api 

3. TCP 的 socket api 的使用

(1) ServerSocket 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 服务器,然后就是按照刚刚说的方式,分不同的角色,服务器就是先从客户端读取数据报,也就得到了请求字符串,然后再根据请求字符串解析成响应字符串,然后再将响应字符串构造成一个数据报,最后返回给客户端。

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

写完之后是这样的:

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);
    }
}
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 方法冲刷缓冲区)。

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

写完后是这样的:

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

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

分析原因:

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

这样就没问题了。

完整代码:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2253364.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Android】View工作原理

View 是Android在视觉上的呈现在界面上Android提供了一套GUI库,里面有很多控件,但是很多时候我们并不满足于系统提供的控件,因为这样就意味这应用界面的同类化比较严重。那么怎么才能做出与众不同的效果呢?答案是自定义View&#…

burp2

声明! 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下,如涉及侵权马上删除文章,笔记只是方便各位师傅的学习和探讨,文章所提到的网站以及内容,只做学习交流,其他均与本人以及泷羽sec团队无关&a…

【阅读记录-章节5】Build a Large Language Model (From Scratch)

目录 5. Pretraining on unlabeled data5.1 Evaluating generative text models5.1.1 Evaluating generative text models5.1.2 Calculating the text generation loss评估模型生成文本的质量 5.1.3 Calculating the training and validation set losses 5.2 Training an LLM5.…

Qt Qtablewidget 标题 QHeaderView 增加可选框 QcheckBox

创建自定义QHeaderView #pragma once#include <QObject> #include <QHeaderView> #include <QPainter> #include <QMouseEvent>class SSHeaderView : public QHeaderView {Q_OBJECTprivate:bool isChecked;int m_checkColIdx; public:SSHeaderView(i…

DDD架构设计

今天的应用架构&#xff0c;意指软件系统中固定不变的代码结构、设计模式、规范和组件间的通信方式。在应用开发中架构之所以是最重要的第一步&#xff0c;因为一个好的架构能让系统安全、稳定、快速迭代。在一个团队内通过规定一个固定的架构设计&#xff0c;可以让团队内能力…

再来聊聊总线机制

背景 之前写过一篇《KafkaPostgreSql&#xff0c;构建一个总线服务》&#xff0c;近期在实践过程中又踩了一些坑&#xff0c;有了一些新的体验&#xff0c;拿出来再说道说道。 我们说EventBus 是一种设计模式和编程工具&#xff0c;它简化了应用程序组件之间的通信。通过使用…

怎么做DNS污染检测

DNS污染是指通过恶意手段篡改DNS解析结果&#xff0c;导致用户访问错误或恶意网站的行为。这种行为不仅影响用户体验&#xff0c;还可能带来安全风险。以下是几种检测DNS污染的方法&#xff1a; 1. 使用在线DNS检查工具 可以使用在线工具如帝恩思旗下的拨测在线DNS检测工具等…

视频融合×室内定位×数字孪生

随着物联网技术的迅猛发展&#xff0c;室内定位与视频融合技术在各行各业中得到了广泛应用。不仅能够提供精确的位置信息&#xff0c;还能通过实时视频监控实现全方位数据的可视化。 与此同时&#xff0c;数字孪生等技术的兴起为智慧城市、智慧工厂等应用提供了强大支持&#…

合规性要求对漏洞管理策略的影响

讨论漏洞管理中持续面临的挑战&#xff0c;包括确定漏洞的优先级和解决修补延迟问题。 介绍合规性要求以及自动化如何简化漏洞管理流程。 您认为为什么尽管技术不断进步&#xff0c;但优先考虑漏洞和修补延迟等挑战仍然存在&#xff1f; 企业基础设施日益复杂&#xff0c;攻…

基于Java Springboot诗词学习APP且微信小程序

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse微信开…

常见问题QA的前端代码

这个的后端代码参见此文 使用语言向量建立常见问题的模糊搜索-CSDN博客https://blog.csdn.net/chenchihwen/article/details/144207262?spm1001.2014.3001.5501 这段代码实现了一个简单的问答页面&#xff0c;页面分为左右两部分&#xff0c;左侧用于展示对话记录&#xff0c…

CSS学习记录03

CSS背景 CSS 背景属性用于定义元素的背景效果。 CSS background-color background-color属性指定元素的背景色。 页面的背景色设置如下&#xff1a; body {background-color: lightblue; } 通过CSS&#xff0c;颜色通常由以下方式指定&#xff1a; 有效的颜色名称-比如“…

【k8s】kubelet 的相关证书

在 Kubernetes 集群中&#xff0c;kubelet 使用的证书通常存放在节点上的特定目录。这些证书用于 kubelet 与 API 服务器之间的安全通信。具体的位置可能会根据你的 Kubernetes 安装方式和配置有所不同&#xff0c;下图是我自己环境【通过 kubeadm 安装的集群】中的kubelet的证…

JavaWeb:Servlet (学习笔记)【1】

目录 一&#xff0c;Servlet介绍 1&#xff0c;简介 2&#xff0c;Servlet技术特点 3&#xff0c;Servlet在应用程序中的位置 4&#xff0c;Servlet在程序中到底处于一个什么地位? 二&#xff0c;servlet运行过程&#xff1a; 三&#xff0c;servlet路径配置 四&#x…

STM32-C语言基础知识

C语言基础知识 stdint.h简介 给寄存器某个位赋值 给位6赋值为1流程&#xff1a;先清0&#xff0c;再赋值 带参数的宏定义 建议使用do {…}while(0)来构造宏定义 条件编译 条件编译后面必须跟宏语句&#xff0c;如#if _LED_H 指针使用常见的2大问题 1、未初始化 2、越界使…

Android 应用单元测试涉及 Telephony 环境初始化问题

Telephony 相关类注入问题 SubscriptionManager Cannot invoke "android.telephony.SubscriptionManager.getActiveSubscriptionInfoList()" because "this.mSubscriptionManager" is nulljava.lang.NullPointerException: Cannot invoke "android.t…

mysql 存储结构的进化之路

文章目录 前言一、线性结构二、二叉树&#xff08;BST&#xff09;三、平衡二叉树&#xff08;AVL&#xff09;四、多路平衡查找树&#xff08;B Tree&#xff09;五、加强版多路平衡查找树&#xff08;B Tree&#xff09;总结 前言 树形结构是一种具有层次关系的数据结构&…

高速定向广播声光预警系统赋能高速安全管控

近年来&#xff0c;高速重大交通事故屡见不鲜&#xff0c;安全管控一直是高速运营的重中之重。如何利用现代化技术和信息化手段&#xff0c;创新、智能、高效的压降交通事故的发生概率&#xff0c;优化交通安全管控质量&#xff0c;是近年来交管部门的主要工作&#xff0c;也是…

【机器学习】CatBoost 模型实践:回归与分类的全流程解析

一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS&#xff1a;转载自己的文章也算原创吧。 在机器学习领域&#xff0c;CatBoost 是一款强大的梯度提升框架&#xff0c;特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…

游戏引擎学习第25天

Git: https://gitee.com/mrxiao_com/2d_game 今天的计划 总结和复述&#xff1a; 这段时间的工作已经接近尾声&#xff0c;虽然每次编程的时间只有一个小时&#xff0c;但每一天的进展都带来不少收获。尽管看起来似乎花费了很多时间&#xff0c;实际上这些日积月累的时间并未…