网络编程-TCP套接字

文章目录

  • 初始TCP套接字
  • TCP的Socket API
    • Socket
    • ServerSocket
  • 使用TCP模拟通信
    • 服务器端
    • 客户端
  • 上述测试代码的问题分析
    • IO的输入缓冲区的问题
    • 关于TCP协议中的粘包的问题
    • 不能进行多线程通信的问题
  • 处理问题之后的完整代码
    • 启动多个实例
    • 完整代码
    • 测试结果
  • 关于IO多路复用机制的引入

初始TCP套接字

我们在上一节简单的介绍了一下, 套接字的相关特性, 现在回顾一下
TCP的特性

  • 有连接: TCP套接字内部天然保存了对端的一些地址信息
  • 可靠传输: 尽最大的能力保障数据可以传输到指定位置
  • 面向字节流: 使用字节流的方式进行数据的传输, 大小没有限制, 但是可能存在一定的问题, 比如有可能出现粘包问题, 所以我们在传输的时候, 通常会使用一些特定的分隔符对数据的内容进行分割, 我们下面的测试代码也可以体现这一点
  • 全双工: 可以同时进行请求和响应

TCP的Socket API

和UDP相同, TCP也属于传输层的范畴, 所以在一台计算机内部, 就属于操作系统的管辖范围, 所以我们处在应用层的Java程序员, 就需要使用JVM提供的API接口进行编程(JVM也是封装了操作系统提供的一些API)


Socket

这个API主要是提供给客户端使用的, 当然服务器端也会使用, 是在服务器的ServerSocket对象调用accpet方法的时候作为返回值出现


常用的构造方法
在这里插入图片描述

  • 构造方法1: 创建一个套接字(未绑定对端地址)
  • 构造方法2: 创建一个套接字(绑定了对端的IP和端口号)
    注意, 该构造方法的IP可以直接传入字符串类型, 不用先转化为InetAddress类型然后传入(其实源码进行了一步转化操作)

常用方法

在这里插入图片描述

这个获取InputStream和OutputStream对象的方法, 可以说是最重要的方法, 因为我们的TCP是面向字节流传输的, 这个方法相当于提供了客户端和服务器端进行访问的通道…

在这里插入图片描述

和UDP类似, TCP的Socket操作的网卡资源, 也可以抽象为一种文件资源, 也占用文件操作符表的内存资源, 所以如果我们不用的话, 要及时的调用close方法进行资源的关闭…


ServerSocket

其实根据名字也不难看出, 这个ServerSocketAPI是专门给服务器端使用的, 客户端用的是另一套API, 等会再说


常用的两个构造方法

在这里插入图片描述

  • 构造方法1: 绑定一个随机的端口号(服务器不常用)
  • 构造方法2: 绑定一个固定的端口号(服务器常用的)

常用方法
在这里插入图片描述
上文我们说了, 在服务器的ServerSocketAPI的使用过程中, 也有Socket出现的时候, 正是当ServerSocket想要和客户端的Socket对象建立通信的时候, 本质上是调用accpet方法, 返回一个Socket对象, 然后使用该对象和客户端的Socket对象, 通过打开的IO通道进行通信

在这里插入图片描述
不再多说, 因为这也是一种文件的资源, 所以当我们不用的时候, 要进行及时的关闭, 避免占用文件操作符表的资源, 但是在真实的服务器的场景中, 使用这个方法的场景是有限的, 因为一般都是 7 * 24 小时持续运行

使用TCP模拟通信

下面我们简单写一个翻译的服务器, 来模拟一下使用TCP协议进行网络通信


服务器端

创建网卡以及构造方法

// 创建一个网卡Socket对象
    ServerSocket serverSocket = null;
    
    // 构造方法(传入一个固定的端口号作为服务器固定端口号)
    public TcpServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    

start方法进行和客户端通信的主流程

// start方法, 与客户端进行通信的主流程
    public void start() throws IOException {
        // 输出日志信息, 服务器上线
        System.out.println("服务器已上线...");
        // 使用while循环不断处理客户端发来的连接
        while(true){
            Socket connection = serverSocket.accept();
            // 处理这个连接
            processConnection(connection);
        }
    }

注意accept方法, 仅仅相当于客户端和服务器端之间建立了连接, 还没有进行请求和响应的内容…(类比)其实就相当于客户端和服务器端进行打电话, 只是拨通了, 还没开始说话

关于长短连接

上面我们说了, 建立连接之后, 才可以进行请求和响应, 那么这个连接中, 是包含一次请求响应还是多次请求响应的 ? 是客户端给服务器端发请求还是服务器端给客户端发请求 ? 这个都是说不准的, 要看具体的使用场景, 所以分为长短连接

  • 短连接: 一次连接只有一次请求响应, 比较消耗资源, 通常应用在查看网页, 内容展示等场景
  • 长连接: 一次连接中有多次请求响应, 比较节约资源, 且不仅仅可能是服务器端给客户端发请求, 服务器端也可能给客户端发请求, 通常应用在游戏, 在线聊天等场景

处理每一个连接的代码逻辑
关键内容都在注释中了

// 处理连接的方法, 这才是真正的进行请求与响应
    private void processConnection(Socket connection){
        // 输出日志, 表示客户端上线
        System.out.printf("客户端上线[%s:%d]", connection.getInetAddress().toString(), connection.getPort());
        // 打开connection的IO通道和客户端的联通(使用try-with-resource机制自动释放资源)
        try(InputStream inputStream = connection.getInputStream(); OutputStream outputStream = connection.getOutputStream()) {
            // 为了便于我们使用 IO, 我们对上面的输入输出流进行套壳处理(也可以不用)
            Scanner in = new Scanner(inputStream);
            PrintWriter out = new PrintWriter(outputStream);
            // 使用while循环不断尝试读取请求和响应
            while(true){
                // 1. 读取请求
                if(!in.hasNext()){
                    // 如果没有下一条请求了, 输出日志, 直接退出
                    System.out.printf("客户端下线[%s:%d]", connection.getInetAddress().toString(), connection.getPort());
                    break;
                }
                String req = in.next();
                
                // 2. 处理请求
                String resp = process(req);
                
                // 3. 发送请求
                out.println(resp);
                
                // 4. 记录日志
                System.out.printf("[req: %s, resp: %s]", req, resp);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

对请求进行处理的主函数, 和上次UDP一致, 都是汉译英的功能

// 处理请求的主方法(翻译)
    private static Map<String, String> chineseToEnglish = new HashMap<>();

    static {
        chineseToEnglish.put("小猫", "cat");
        chineseToEnglish.put("小狗", "dog");
        chineseToEnglish.put("小鹿", "fawn");
        chineseToEnglish.put("小鸟", "bird");
    }

    private String process(String req) {
        return chineseToEnglish.getOrDefault(req, "未收录该词条");
    }


客户端

创建网卡, 构造方法

// 创建网卡
    private Socket clientSocket = null;
    
    // 构造方法, 由于是Tcp协议, 所以直接绑定对端的地址信息
    public TcpClient(String host, int port) throws IOException {
        clientSocket = new Socket(host, port);
    }

start方法, 和服务器端的一些内容是相似的

// start方法, 与服务器建立通信, 请求与响应
    public void start(){
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream(); Scanner sc = new Scanner(System.in)) {
            // 把输入输出流进行包装
            Scanner in = new Scanner(inputStream);
            PrintWriter out = new PrintWriter(outputStream);
            
            // 使用while循环不断读取用户的请求, 发送请求并接收响应
            while(sc.hasNext()){
                // 1. 读取请求
                String req = sc.next();
                
                // 2. 发送请求
                out.println(req);
                
                // 3. 接收响应
                String resp = in.next();
                
                // 4. 输出响应内容
                System.out.println(resp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

上述测试代码的问题分析

如果我们直接运行上面的代码, 我们就会发现, 是无法直接运行的, 说明上面的代码存在一些问题, 我们现在处理一下这些问题

IO的输入缓冲区的问题

有一些 IO 工具, 在输出的时候, 并不会是真正的输出, 而是将输出的内容放到一个缓冲区的地方, 必须调用flush()方法才能够真正的进行数据的发送, 所以我们在 IO 那个章节的时候, 建议是所有输出的流, 我们都进行flush()方法进行推送, 这是一个非常好的习惯, 所以上面的测试代码, 我们把所有使用out.println()的位置后面, 都加上flush()方法进行消息的推送

改进代码如下

	// 2. 发送请求
	out.println(req);
    out.flush();

关于TCP协议中的粘包的问题

由于TCP协议传输的时候, 是通过字节流的方式进行传输的, 所以不同的消息之间, 并没有一个非常明显的界限, 所以我们一般手动进行消息边界的指定, 避免消息的"粘连问题"
上述测试代码的逻辑中, 使用

	out.println(req);

因为println天然的就带有一个换行, 所以这就成为了一个天然的分割条件
关于如果解决粘包问题, 我们之后会仔细说, 这里只是简单介绍一下…


不能进行多线程通信的问题

分析下面的代码片段
在这里插入图片描述
在这里插入图片描述
假设有一台服务器, 此时客户端A尝试与服务器建立连接, 然后服务器进行连接的处理, 这时, 服务器就要阻塞等待in.hasNext()这里, 如果另一个客户端B也尝试和服务器建立连接, 那此时就不会有任何的反应(因为代码阻塞无法进行连接), 那岂不是一台服务器只能给一台客户端提供服务 ?
显然这样是不合理的, 所以此时我们就引入了多线程来解决这个问题, 通过不同的线程把处理连接的内容和接收连接的内容分隔开, 实质上这也就是早期发明多线程的原因(为了解决服务器开发的问题)

为了不频繁的创建销毁线程导致资源开销太大, 我们又引入了线程池来解决这个问题


处理问题之后的完整代码

启动多个实例

首先要设置启动的时候运行启动多个实例这一项, 来模拟同时启动多个客户端
在这里插入图片描述
勾选Allow multiple instances


完整代码

客户端代码

package net_demo1.net_demo04;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * 关于使用Tcp协议的客户端程序的模拟
 */

public class TcpClient {

    // 创建网卡
    private Socket clientSocket = null;

    // 构造方法, 由于是Tcp协议, 所以直接绑定对端的地址信息
    public TcpClient(String host, int port) throws IOException {
        clientSocket = new Socket(host, port);
    }

    // start方法, 与服务器建立通信, 请求与响应
    public void start(){
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream(); Scanner sc = new Scanner(System.in)) {
            // 把输入输出流进行包装
            Scanner in = new Scanner(inputStream);
            PrintWriter out = new PrintWriter(outputStream);

            // 使用while循环不断读取用户的请求, 发送请求并接收响应
            while(sc.hasNext()){
                // 1. 读取请求
                String req = sc.next();

                // 2. 发送请求
                out.println(req);
                out.flush();

                // 3. 接收响应
                String resp = in.next();

                // 4. 输出响应内容
                System.out.println(resp);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) throws IOException {
        TcpClient tcpClient = new TcpClient("127.0.0.1", 9090);
        tcpClient.start();
    }
}

服务器端代码

package net_demo1.net_demo04;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 使用TCP协议模拟的服务器
 */

public class TcpServer {

    // 创建一个网卡Socket对象
    ServerSocket serverSocket = null;

    // 构造方法(传入一个固定的端口号作为服务器固定端口号)
    public TcpServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    // start方法, 与客户端进行通信的主流程
    public void start() throws IOException {
        // 创建一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 输出日志信息, 服务器上线
        System.out.println("服务器已上线...");
        // 使用while循环不断处理客户端发来的连接
        while (true) {

            Socket connection = serverSocket.accept();

            executorService.execute(() -> {
                // 处理这个连接
                processConnection(connection);
            });
        }
    }


    // 处理连接的方法, 这才是真正的进行请求与响应
    private void processConnection(Socket connection) {
        // 输出日志, 表示客户端上线
        System.out.printf("客户端上线[%s:%d]\n", connection.getInetAddress().toString(), connection.getPort());
        // 打开connection的IO通道和客户端的联通(使用try-with-resource机制自动释放资源)
        try (InputStream inputStream = connection.getInputStream(); OutputStream outputStream = connection.getOutputStream()) {
            // 为了便于我们使用 IO, 我们对上面的输入输出流进行套壳处理(也可以不用)
            Scanner in = new Scanner(inputStream);
            PrintWriter out = new PrintWriter(outputStream);
            // 使用while循环不断尝试读取请求和响应
            while (true) {
                // 1. 读取请求
                if (!in.hasNext()) {
                    // 如果没有下一条请求了, 输出日志, 直接退出
                    System.out.printf("客户端下线[%s:%d]\n", connection.getInetAddress().toString(), connection.getPort());
                    break;
                }
                String req = in.next();

                // 2. 处理请求
                String resp = process(req);

                // 3. 发送请求
                out.println(resp);
                out.flush();

                // 4. 记录日志
                System.out.printf("[req: %s, resp: %s]\n", req, resp);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    // 处理请求的主方法(翻译)
    private static Map<String, String> chineseToEnglish = new HashMap<>();

    static {
        chineseToEnglish.put("小猫", "cat");
        chineseToEnglish.put("小狗", "dog");
        chineseToEnglish.put("小鹿", "fawn");
        chineseToEnglish.put("小鸟", "bird");
    }

    private String process(String req) {
        return chineseToEnglish.getOrDefault(req, "未收录该词条");
    }

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


测试结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


关于IO多路复用机制的引入

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

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

相关文章

flutter开发-figma交互设计图可以转换为flutter源代码-如何将设计图转换为flutter源代码-优雅草央千澈

flutter开发-figma交互设计图可以转换为flutter源代码-如何将设计图转换为flutter源代码-优雅草央千澈 开发背景 可能大家听过过蓝湖可以转ui设计图为vue.js&#xff0c;react native代码&#xff0c;那么请问听说过将figma的设计图转换为flutter源代码吗?本文优雅草央千澈带…

重拾Python学习,先从把python删除开始。。。

自己折腾就是不行啊&#xff0c;屡战屡败&#xff0c;最近终于找到前辈教我 第一步 删除Python 先把前阵子折腾的WSL和VScode删掉。还是得用spyder&#xff0c;跟matlab最像&#xff0c;也最容易入手。 从VScode上搞python&#xff0c;最后安装到appdata上&#xff0c;安装插…

【机器学习实战中阶】音乐流派分类-自动化分类不同音乐风格

音乐流派分类 – 自动化分类不同音乐风格 在本教程中,我们将开发一个深度学习项目,用于自动化地从音频文件中分类不同的音乐流派。我们将使用音频文件的频率域和时间域低级特征来分类这些音频文件。 对于这个项目,我们需要一个具有相似大小和相似频率范围的音频曲目数据集…

[Qt]事件-鼠标事件、键盘事件、定时器事件、窗口改变事件、事件分发器与事件过滤器

目录 前言&#xff1a;Qt与操作系统的关系 一、Qt事件 1.事件介绍 2.事件的表现形式 常见的Qt事件&#xff1a; 常见的事件描述: 3.事件的处理方式 处理鼠标进入和离开事件案例 控件添加到对象树底层原理 二、鼠标事件 1.鼠标按下和释放事件&#xff08;单击&#x…

后盾人JS -- 好用的 JavaScript Symbol 类型

Symbol使用场景介绍 举个例子&#xff0c;当leader让你去机房取某个电脑的时候&#xff0c;机房那么多电脑&#xff0c;你怎么知道取哪个 所以这个时候symbol的作用就显现出来了&#xff08;上面有什么贴纸的&#xff0c;什么型号的电脑&#xff09; 声明定义Symbol的几种方…

社区版Dify实现文生视频 LLM+ComfyUI+混元视频

社区版Dify实现文生视频 LLMComfyUI混元视频 一、 社区版Dify实现私有化混元视频效果二、为什么社区版Dify可以在对话框实现文生视频&#xff1f;LLMComfyUI混元视频 实现流程图&#xff08;重点&#xff09;1. 文生视频模型支持ComfyUI2. ComfyUI可以轻松导出API实现封装3. Di…

数智化转型 | 星环科技Defensor 助力某银行数据分类分级

在数据驱动的金融时代&#xff0c;数据安全和隐私保护的重要性日益凸显。某银行作为数字化转型的先行者&#xff0c;面临着一项艰巨的任务&#xff1a;如何高效、准确地对分布在多个业务系统、业务库与数仓数湖中的约80万个字段进行数据分类和分级。该银行借助星环科技数据安全…

Spring boot启动原理及相关组件

优质博文&#xff1a;IT-BLOG-CN 一、Spring Boot应用启动 一个Spring Boot应用的启动通常如下&#xff1a; SpringBootApplication Slf4j public class ApplicationMain {public static void main(String[] args) {ConfigurableApplicationContext ctx SpringApplication.…

中国石油大学(华东)自动评教工具(涵盖爬虫的基础知识,适合练手)

我开发了一个用于自动评教的工具&#xff0c;大家可以试着用用&#xff0c;下面是链接。 https://github.com/restrain11/auto_teachingEvaluate 可以点个星吗&#xff0c;感谢&#xff01;&#x1fae1; 以下是我在开发过程中学到的知识 以及 碰到的部分问题 目录 动态爬虫和静…

PyTorch使用教程(2)-torch包

1、简介 torch包是PyTorch框架最外层的包&#xff0c;主要是包含了张量的创建和基本操作、随机数生成器、序列化、局部梯度操作的上下文管理器等等&#xff0c;内容很多。我们基础学习的时候&#xff0c;只有关注张量的创建、序列化&#xff0c;随机数、张量的数学数学计算等常…

机器学习-距离的度量方法

文章目录 一. 欧式距离二. 曼哈顿距离三. 切比雪夫距离四. 闵式距离1. p不同取值,表示不同距离2. 当 ( p → ∞ ) ( p \to \infty ) (p→∞) 时&#xff0c;为什么闵式距离变为切比雪夫距离 五. 总结 一. 欧式距离 欧式距离&#xff08;Euclidean distance&#xff09;:多维空…

ComfyUI 矩阵测试指南:用三种方法,速优项目效果

在ComfyUI中&#xff0c;矩阵测试也叫xyz图表测试&#xff0c;作用是通过控制变量的方式来对Lora模型以及各种参数开展测试&#xff0c;并进行有效区分。其中测试方法有很多种&#xff0c;可以通过借助插件也可以自行搭建工作流实现&#xff0c;下面介绍3种方式&#xff1a; 1…

内存与缓存:保姆级图文详解

文章目录 前言1、计算机存储设备1.1、硬盘、内存、缓存1.2、金字塔结构1.3、数据流通过程 2、数据结构内存效率3、数据结构缓存效率 前言 亲爱的家人们&#xff0c;创作很不容易&#xff0c;若对您有帮助的话&#xff0c;请点赞收藏加关注哦&#xff0c;您的关注是我持续创作的…

UllnnovationHub,一个开源的WPF控件库

目录 UllnnovationHub1.项目概述2.开发环境3.使用方法4.项目简介1.WPF原生控件1.Button2.GroupBox3.TabControl4.RadioButton5.SwitchButton6.TextBox7.PasswordBox8.CheckBox9.DateTimePicker10.Expander11.Card12.ListBox13.Treeview14.Combox15.Separator16.ListView17.Data…

【STM32-学习笔记-10-】BKP备份寄存器+时间戳

文章目录 BKP备份寄存器Ⅰ、BKP简介1. BKP的基本功能2. BKP的存储容量3. BKP的访问和操作4. BKP的应用场景5. BKP的控制寄存器 Ⅱ、BKP基本结构Ⅲ、BKP函数Ⅳ、BKP使用示例 时间戳一、Unix时间戳二、时间戳的转换&#xff08;time.h函数介绍&#xff09;Ⅰ、time()Ⅱ、mktime()…

Flowable 管理各业务流程:流程设计器 (获取流程模型 XML)、流程部署、启动流程、流程审批

文章目录 引言I 表结构主要表前缀及其用途核心表II 流程设计器(Flowable BPMN模型编辑器插件)Flowable-UIvue插件III 流程部署部署步骤例子:根据流程模型ID部署IV 启动流程启动步骤ACT_RE_PROCDEF:流程定义相关信息例子:根据流程 ID 启动流程V 流程审批审批步骤Flowable 审…

java根据模板导出word,并在word中插入echarts相关统计图片以及表格

引入依赖创建word模板创建ftl模板文件保存的ftl可能会出现占位符分割的问题&#xff0c;需要处理将ftl文件中的图片的Base64删除&#xff0c;并使用占位符代替插入表格&#xff0c;并指定表格的位置在图片下方 Echarts转图片根据模板生成word文档DocUtil导出word文档 生成的wor…

晨辉面试抽签和评分管理系统之十:如何搭建自己的数据库服务器,使用本软件的网络版

晨辉面试抽签和评分管理系统&#xff08;下载地址:www.chenhuisoft.cn&#xff09;是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…

Asp .Net Core 实现微服务:集成 Ocelot+Nacos+Swagger+Cors实现网关、服务注册、服务发现

什么是 Ocelot ? Ocelot是一个开源的ASP.NET Core微服务网关&#xff0c;它提供了API网关所需的所有功能&#xff0c;如路由、认证、限流、监控等。 Ocelot是一个简单、灵活且功能强大的API网关&#xff0c;它可以与现有的服务集成&#xff0c;并帮助您保护、监控和扩展您的…

mongoose 支持https踩坑纪实

简述 mongoose是C编写的嵌入式web服务&#xff0c;它能够支持https协议&#xff0c;可以简单的部署&#xff0c;但要做到完美部署&#xff0c;不是那么容易。 部署方法 本人使用的是最新的7.16版&#xff0c;以前版本似乎是要通过修改 头文件中的 MG_ENABLE_SSL 宏定义&…