网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))

文章目录

  • 1. 什么是网络编程
  • 2. 网络编程中的基本概念
    • 1)发送端和接收端
    • 2)请求和响应
    • 3)客户端和服务端
    • 4)常见的客户端服务端模型
  • 3. Socket 套接字
    • 1)Socket 的分类
    • 2)Java 数据报套接字通信模型
    • 3)Java 流套接字通信模型
  • 4. UDP 数据报套接字编程
    • 1)DatagramSocket API
    • 2)DatagramPacket API
    • 3)示例
  • 5. TCP 流套接字编程
    • 1)ServerSocket API
    • 2)Socket API
    • 3)示例
      • a. 短连接版本
      • b. 长连接并发版本

1. 什么是网络编程

网络编程是指网络上的主机,通过不同的进程,以编程的方式实现 网络通信(或称为网络数据传输)

请添加图片描述

只要满足不同的进程就可以进行通信,所以即便是在同一个主机,只要不同的进程,基于网络传输数据,也属于网络编程

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

1)发送端和接收端

在一次网络传输中:

发送端: 数据的 发送方进程,称为发送端。发送端主机即网络通信中的源主机

接收端: 数据的 接收方进程,称为接收端。接收端主机即网络通信中的目的主机

收发端: 发送端和接收端两端,简称为收发端

注意: 发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念

请添加图片描述

2)请求和响应

一般来说,获取一个网络资源时,涉及到两次网络数据传输

  • 第一次:请求数据的发送
  • 第二次:响应数据的发送

就好比在饭店里点了一碗面

顾客先发起请求:来一碗面

饭店再做出响应:提供一碗面

请添加图片描述

3)客户端和服务端

服务端: 在常见的网络传输场景下,把 提供服务 的一方进程,称为服务端,可以对外提供服务

客户端: 获取服务 的一方进程,称为客户端

对于服务来说,一般是提供:

  • 客户端获取服务资源

请添加图片描述

  • 客户端保存资源到服务端

请添加图片描述

4)常见的客户端服务端模型

在常见的场景中,客户端是指给用户使用的程序,服务端是指提供用户服务的程序,具体流程如下:

  1. 客户端发送请求到服务端
  2. 服务端根据请求数据,执行相应的业务处理
  3. 服务端返回响应:发送业务处理结果
  4. 客户端根据响应数据,展示处理结果

请添加图片描述

3. Socket 套接字

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

1)Socket 的分类

常用的 Socket 套接字有以下两类:

  • 流套接字: 使用 TCP 协议

    传输数据基于 IO 流,流式数据的特征就是在 IO 流没有关闭的情况下,是无边界的数据,可以多次发送数据,也可以分开多次接收

  • 数据报套接字: 使用 UDP 协议

    传输数据是一块一块的,每一块数据必须一次性发送,接受也必须一次性接受,不能分开发送或接收

2)Java 数据报套接字通信模型

UDP 协议具有无连接、面向数据报的特征,即每次传输都是没有建立连接,并且一次发送全部数据,一次接收全部数据

Java 中使用 UDP 协议通信时,主要基于 DatagramSocket 类来创建数据报套接字,并使用 DatagramPacket 作为发送或者接受的数据报。

具体流程如下:

请添加图片描述

3)Java 流套接字通信模型

TCP 协议具有有连接、面向字节流的特征,即传输数据之前要先建立连接,并且数据的传输是基于 IO 流的

具体流程如下:

请添加图片描述

4. UDP 数据报套接字编程

1)DatagramSocket API

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

构造方法:

方法签名方法说明
DatagramSocket()创建一个 UDP 数据报套接字的 Socket,绑定到本机任意一个随机端口
DatagramSocket(int port)创建一个 UDP 数据报套接字的 Socket,绑定到本机的指定端口上

实例方法:

方法签名方法说明
void receive(DatagramPacket p)接收数据报到提前构造的 DatagramPacket 对象中(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)发送提前构造的 DatagramPacket 数据报
void close()关闭套接字

2)DatagramPacket API

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

构造方法:

方法签名方法说明
DatagramPacket(byte[] buf, int length)构造一个 DatagramPacket 对象用来接收数据报,接收的数据保存到字节数组中
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)构造 DatagramPacket 用来发送数据报,发送的数据为字节数组中的数据。指定目的主机的 IP 和端口号

实例方法:

方法签名方法说明
InetAddress getAddress()从数据报中,获取 IP 地址
int getPort()从数据报中,获取端口号
byte[] getData()获取数据报中的数据

3)示例

使用 UDP Socket 套接字完成一个简单的翻译小程序

客户端向服务端发起翻译请求,服务端接收到请求后,对请求进行处理,再向客户端做出响应

服务端

服务端接收到请求之后,在我们人为规定的 map 中进行查询,再对客户端做出响应

public class Server {
    private final static HashMap<String, String> map = new HashMap<>();
    static {
        map.put("苹果", "apple");
        map.put("香蕉", "banana");
        map.put("梨", "pear");
        map.put("电脑", "computer");
        map.put("手机", "phone");
    }

    public static void main(String[] args) throws IOException {
        // 得到 socket 对象,并对其绑定端口号
        Log.println("服务器开启并且绑定了 8080 端口");
        DatagramSocket socket = new DatagramSocket(8080);

        // 循环接受和处理请求
        while (true) {
            // 准备接收数据的容器
            byte[] buf = new byte[1024];
            // 包装数据包
            DatagramPacket received = new DatagramPacket(buf, 0, 1024);
            // 接收请求
            Log.println("服务器准备接受请求");
            socket.receive(received);
            Log.println("服务器接受到了请求");

            // 处理请求
            InetAddress address = received.getAddress();    // 得到发起请求方的 IP
            Log.println("对方的地址:" + address);
            int port = received.getPort();      // 得到发起请求方的端口号
            Log.println("对方的端口:" + port);
            int length = received.getLength();      // 得到请求中真实有效的数据长度
            Log.println("真实的数据长度:" + length);

            byte[] realData = Arrays.copyOf(buf, length);   // 得到请求中真实有效的数据
            // 将请求数据转换成 String
            String request = new String(realData, 0, length, StandardCharsets.UTF_8);
            Log.println("请求中的数据:\r\n" + request);
            // 人为规定请求和相应的格式
            // 请求格式必须以 “我是请求:\r\n” 开始,以 “\r\n” 结束
            if (!request.startsWith("我是请求:\r\n")) {
                Log.println("请求格式出错");
                // 请求出错
                continue;
            }

            if (!request.endsWith("\r\n")) {
                Log.println("请求格式出错");
                // 请求出错
                continue;
            }
            // 得到请求中的英文单词
            String EnglishWord = request.substring("我是请求:\r\n".length(), request.length() - 2);
            Log.println("请求中的英文单词:" + EnglishWord);
            // 进行翻译
            String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
            Log.println("翻译后的中文:" + ChineseWord);
            // 将翻译后的结果进行包装
            String response = String.format(ChineseWord, "%s\r\n");
            byte[] bytes = response.getBytes(StandardCharsets.UTF_8);

            // 包装数据包
            DatagramPacket send = new DatagramPacket(bytes, 0, bytes.length, address, port);
            // 发起响应
            Log.println("准备发送响应");
            socket.send(send);
            Log.println("相应发送成功");
        }
    }
}

客户端

public class Client {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        DatagramSocket socket = new DatagramSocket(9999);
        while (sc.hasNextLine()) {
            String str = sc.nextLine();
            String send = "我是请求:\r\n" + str + "\r\n";
            byte[] bytes = send.getBytes();
            DatagramPacket request = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getLoopbackAddress(), 8080);
            socket.send(request);

            // 接收响应
            byte[] buf = new byte[1024];
            DatagramPacket datagramPacket = new DatagramPacket(buf, 0, 1024);
            socket.receive(datagramPacket);
            int n = datagramPacket.getLength();
            String response = new String(buf, 0, n, StandardCharsets.UTF_8);
            Log.println(response);
        }
    }
}

请添加图片描述

5. TCP 流套接字编程

1)ServerSocket API

用于创建 TCP 服务端 Socket 的 API

构造方法:

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字 Socket,并绑定到指定端口

实例方法:

方法签名方法说明
Socket accept()等待客户端发起连接,连接成功后返回一个 Socket 对象
void close()关闭 Socket

2)Socket API

这里的 Socket 是客户端 Socket,或服务端中接收到客户端连接请求后,accept 方法返回的服务端 Socket

不管是客户端还是服务端 Socket,都是双方建立连接后,保存对端信息,以及用来与对方收发数据的

构造方法:

方法签名方法说明
Socket(String host, int port)创建一个客户端流套接字,并与对应 IP 的主机,对应端口的进程建立连接

实例方法:

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的 IP 地址
InputStream getInputStream()返回套接字的输入流
OutputStream getOutputStream()返回套接字的输出流

3)示例

使用 TCP Socket 套接字完成一个简单的翻译小程序

完成的效果和上文 UDP 的小程序效果一样

不过在 TCP 这里有长短连接的区分,所以有两个版本的代码,在长连接版本中加入了并发编程,使得同一个服务端可以被多个客户端所连接

a. 短连接版本

客户端:

public class 短连接版本Client {
    public static void main(String[] args) throws IOException {
        for (int i = 0; i < 3; i++) {
            Socket socket = new Socket("127.0.0.1", 8080);      // 拨号

            OutputStream os = socket.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(os, "UTF-8");
            PrintWriter printWriter = new PrintWriter(writer);

            printWriter.printf("我是请求\r\n%s\r\n", "苹果");
            printWriter.flush();

            InputStream is = socket.getInputStream();
            Scanner scanner = new Scanner(is, "UTF-8");
            String header = scanner.nextLine();
            String word = scanner.nextLine();
            System.out.println(word);

            socket.close();     // 挂电话
        }
    }
}

服务端:

public class 短连接版本Server {
    private final static HashMap<String, String> map = new HashMap<>();
    static {
        map.put("苹果", "apple");
        map.put("香蕉", "banana");
        map.put("梨", "pear");
        map.put("电脑", "computer");
        map.put("手机", "phone");
    }

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);

        while(true) {
            Log.println("等待客户端建立连接");
            Socket socket = serverSocket.accept();
            Log.println("客户端连接成功");

            InetAddress address = socket.getInetAddress();
            Log.println("客户端 IP 地址:" + address);
            int port = socket.getPort();
            Log.println("客户端端口号:" + port);

            InputStream is = socket.getInputStream();
            Scanner sc = new Scanner(is, "UTF-8");
            String header = sc.nextLine();
            String EnglishWord = sc.nextLine();
            Log.println("请求中的英文单词为:" + EnglishWord);

            String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
            Log.println("翻译后的中文为:" + ChineseWord);

            Log.println("准备响应");
            OutputStream os = socket.getOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(os);
            PrintWriter printWriter = new PrintWriter(writer);
            printWriter.printf("OK!\r\n%s\t\n", ChineseWord);
            printWriter.flush();
            Log.println("响应成功");

            socket.close();
            Log.println("断开连接");
        }
    }
}

请添加图片描述

b. 长连接并发版本

在长连接版本中,必须服务端和客户端同时支持长连接才可以进行通信

如果不清楚长连接和短连接,可以去看看我上篇文章,里面有介绍长连接和短连接,HTTP 和 HTTPS,有介绍 HTTP 的长短连接,实质上就是 TCP 长短连接

客户端:

public class Client {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        Socket socket = new Socket("127.0.0.1", 8080);

        OutputStream os = socket.getOutputStream();
        OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
        PrintWriter printWriter = new PrintWriter(writer);

        InputStream is = socket.getInputStream();
        Scanner scanner = new Scanner(is, "UTF-8");

        while (sc.hasNextLine()) {
            String str = sc.nextLine();
            printWriter.printf("我是请求:\r\n%s\r\n", str);
            printWriter.flush();

            // 接收响应
            String header = scanner.nextLine();
            String response = scanner.nextLine();
            Log.println(header + "\r\n" + response);
        }
        socket.close();
    }
}

服务端:

public class Server {
    private final static HashMap<String, String> map = new HashMap<>();
    static {
        map.put("苹果", "apple");
        map.put("香蕉", "banana");
        map.put("梨", "pear");
        map.put("电脑", "computer");
        map.put("手机", "phone");
    }

    private final static class ServerTask implements Runnable {
        private final Socket socket;

        private ServerTask(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            InetAddress address = socket.getInetAddress();
            Log.println("客户端 IP 地址为:" + address);
            int port = socket.getPort();
            Log.println("客户端端口号为:" + port);

            try {
                InputStream is = socket.getInputStream();
                // 请求格式必须以 “我是请求:\r\n” 开始,以 “\r\n” 结束
                Scanner sc = new Scanner(is, "UTF-8");

                // 进行响应
                OutputStream os = socket.getOutputStream();
                OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8);
                PrintWriter printWriter = new PrintWriter(writer);


                while (sc.hasNextLine()) {
                    String header = sc.nextLine();
                    String EnglishWord = sc.nextLine();
                    Log.println("请求中的词为:" + EnglishWord);

                    // 对请求进行处理
                    String ChineseWord = map.getOrDefault(EnglishWord, "我不知道");
                    Log.println("翻译后的词:" + ChineseWord);

                    Log.println("准备发起响应");
                    printWriter.printf("OK!\r\n%s\r\n", ChineseWord);
                    printWriter.flush();
                    Log.println("响应成功");
                }

                socket.close();
                Log.println("连接已关闭");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        // 使用多线程完成多用户同时在线的功能
        ExecutorService threadPool = Executors.newFixedThreadPool(3);


        ServerSocket serverSocket = new ServerSocket(8080);
        Log.println("绑定端口到 8080");

        while (true) {
            Log.println("等待客户端连接");
            Socket socket = serverSocket.accept();
            Log.println("客户端连接成功");

            ServerTask serverTask = new ServerTask(socket);
            threadPool.execute(serverTask);
        }
    }
}

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Open3D的点云处理2-Open3D的IO与数据转换

三维数据类型 点云 某个坐标系下的点数据集&#xff0c;每个点包括三维坐标X&#xff0c;Y&#xff0c;Z、颜色、分类值、强度值、时间等信息&#xff1b; 储存格式&#xff1a;pts、LAS、PCD、xyz、asc、ply等&#xff1b;Mesh 多边形网格&#xff0c;常见的是三角网格&#…

有研究员公开了一个解析并提取 Dell PFS BIOS 固件的工具(上)

导语&#xff1a;研究员公开了一个解析并提取 Dell PFS BIOS 固件的工具。 Dell PFS BIOS提取器 介绍 解析 Dell PFS BIOS 映像并提取其 SPI/BIOS/UEFI 固件组件。它支持所有Dell PFS 修订版和格式&#xff0c;包括最初在 ThinOS 包中LZMA压缩、ZLIB压缩或拆分成块的格式。输出…

Kafka上的优化经验

1. 平滑扩容 Motivation kafka扩容⼀台新机器的流程 假如集群有 3 个 broker &#xff0c;⼀共有 4 个 TP &#xff0c;每个 3 副本&#xff0c;均匀分布。现在要扩容⼀台机器&#xff0c; 新 broker 加⼊集群后需要通过⼯具进⾏ TP 的迁移。⼀共迁移 3 个 TP 的副…

Spring Boot集成ShardingSphere实现按月数据分片及创建自定义分片算法 | Spring Cloud 44

一、前言 在前面我们通过以下章节对数据分片有了基础的了解&#xff1a; Spring Boot集成ShardingSphere实现数据分片&#xff08;一&#xff09; | Spring Cloud 40 Spring Boot集成ShardingSphere实现数据分片&#xff08;二&#xff09; | Spring Cloud 41 Spring Boot集…

权限提升:信息收集 .(Linux系统)

权限提升&#xff1a;信息收集. 权限提升简称提权&#xff0c;由于操作系统都是多用户操作系统&#xff0c;用户之间都有权限控制&#xff0c;比如通过 Web 漏洞拿到的是 Web 进程的权限&#xff0c;往往 Web 服务都是以一个权限很低的账号启动的&#xff0c;因此通过 Webshel…

1.1 基于B/S 结构的 Web 应用

文章目录 1.1 基于B/S 结构的 Web 应用1.2 JDK安装与配置1.3 服务器Tomcat下载与安装1.4 Eclipse安装与使用1.4.1 Eclipse 下载及创建Dynamic Web Project1.4.2 Eclipse 中的编码问题1.4.3 将Tomcat和Eclipse相关联1.4.4 Eclipse 自动部署项目到 Tomcat 的 webapps 目录 1.5 My…

【AWS入门】AWS Lamda

目录 创建一个Lamda函数用Lamda函数控制启停EC2实例创建一台EC2实例创建角色创建lamda函数 使用Amazon EventBridge计划启停实例创建EventBridge 用户往S3存储桶上传图片文件&#xff0c;触发Lambda函数&#xff0c;将图片压缩并上传至另一个存储桶创建两个存储桶通过Cloudform…

【SpringMVC】| SpringMVC执行流程原理 | 常用注解 剥析

MVC目录 一. &#x1f981; MVC模型二. &#x1f981; SpringMVC1. SpringMVC执行流程&#xff08;重点&#xff09;Ⅰ. SpringMVC四大组件Ⅱ. 执行流程 2. RequestMapping3. RequestParam4. ReuqestHeader & CookieValue5. RESTful风格支持Ⅰ. 传统 vs restfulⅡ. PathVar…

【网络技术】什么是CNI

序言 你只管努力&#xff0c;其他交给时间&#xff0c;时间会证明一切。 Never look back unless you are planning to go that way. 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记一级论点蓝色&#xff1a;用…

【应急响应】日志自动提取分析项目ELKLogkitLogonTracerAnolog等

日志自动提取-七牛Logkit&观星应急工具 1、七牛Logkit&#xff1a;(Windows&Linux&Mac等) https://github.com/qiniu/logkit/ 支持的数据源&#xff08;各类日志&#xff0c;各个系统&#xff0c;各个应用等&#xff09; File: 读取文件中的日志数据&#xff0c;包…

面了一个4年经验的测试工程师,自动化都不会也要15k,我也是醉了····

在深圳这家金融公司也待了几年&#xff0c;被别人面试过也面试过别人&#xff0c;大大小小的事情也见识不少&#xff0c;今天又是团面的一天&#xff0c; 一百多个人都聚集在一起&#xff0c;因为公司最近在谈项目出来面试就2个人&#xff0c;无奈又被叫到面试房间。 整个过程…

数说热点 | 跟着《长月烬明》起飞,今年各地文旅主打的就是一个听劝

近日&#xff0c;随着热播剧《长月烬明》的爆火&#xff0c;蚌埠、宣城、敦煌等多个与剧情梦幻联动的宝藏城市被带飞&#xff0c;各地热心网友也纷纷催促自家文旅局赶紧“蹭飞”&#xff0c;《长月烬明》以一己之力打造了影视文旅融合的新样板。 仙偶剧特效天花板&#xff0c;…

《互联网安全产品漏洞管理规定》

《网络产品安全漏洞管理规定》由工业和信息化部、国家互联网信息办公室、公安部联合印发&#xff0c;自2021年9月1日起施行。 该《规定》明确&#xff0c;任何组织或者个人不得利用网络产品安全漏洞从事危害网络安全的活动&#xff0c;不得非法收集、出售、发布网络产品安全漏洞…

Redis高频面试题,使用场景

一、缓存 1、什么是缓存穿透 ? 怎么解决 ? 缓存穿透 查询一个不存在的数据&#xff0c;mysql查询不到数据也不会直接写入缓存&#xff0c;就会导致每次请求都查数据库。 解决 方案一&#xff1a;缓存空数据&#xff0c;查询返回的数据为空&#xff0c;仍把这个空结果进行…

【JavaEE】认识线程

目录 1、什么是线程 2、为什么引入线程 2.1、线程的优缺点 3、CPU的工作原理 4、线程和进程的关系 4.1、线程和进程的入口函数 4.2、线程独享的资源 1、什么是线程 一个进程中可以有一个或者多个线程&#xff0c;每个线程都是一个独立的执行流。多个线程之间&#xff0c;也…

3.rabbitMQ之发布确认高级和整合springboot(重要)找了很多博客整理出来的

1.极端情况下 rabbitMQ需要重启,导致消息投递失败(生产者发消息全部丢失)(交换机或者队列出问题) 生产者需要把数据放到缓存,用定时任务重新发送 解决方法: 0.必须配置文件写 spring.rabbitmq.publisher-confirm-typecorrelatedspring.rabbitmq.publisher-returnstruecorrelati…

Word Embedding

One-hot-encoding 缺点 1.向量维度和向量个数很大&#xff0c;假设有1w个token的话&#xff0c;向量个数和维度就都是1w 2. 语义相近的词的向量并不相似 Word Embedding 核心思想&#xff1a;可以通过上下文理解单词的语义 predection-based方法 使用前一个单词预测下一个…

【机器学习】信息量、香农熵、信息增益

这节可以搭配 【机器学习】Logistic回归&#xff08;重新整理&#xff09;信息量&#xff08;信息&#xff09;信息量公式的推理过程 香农熵信息增益 【机器学习】Logistic回归&#xff08;重新整理&#xff09; B站视频&#xff1a;“交叉熵”如何做损失函数&#xff1f;打包…

Linux一学就会——编写自己的shell

编写自己的shell 进程程序替换 替换原理 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行…

视觉震撼的数据可视化示例

众所周知&#xff0c;数据可以非常强大——当你真正理解它告诉你什么时。 数据和信息可视化(数据可视化或信息可视化)是对大量复杂的定量、定性数据、信息进行设计和创建易于沟通、易于理解的图形或视觉表示的实践&#xff0c;在静态、动态或交互式视觉项目的帮助下&#xff0…