【Java EE初阶十二】网络编程TCP/IP协议(二)

news2025/1/24 20:48:39

1. 关于TCP

1.1 TCP 的socket api

        tcp的socket api和U大片的socket api差异很大,但是和前面所讲的文件操作很密切的联系

        下面主要讲解两个关键的类:

        1、ServerSocket:给服务器使用的类,使用这个类来绑定端口号

        2、Socket:即会给服务器使用,又会给客户端使用;

        TCP是字节流的,传输的基本单位是Byte;

        所谓连接:通信双方是否会记录保存对端的信息;

       对于UDP来说,每一次发送数据报都要手动在send方法中指定目标的地址(UDP自身没有存储这个信息)

       对于TCP来说,则不需要,前提是需要先把连接建立起来(连接如何建立,不需要我们通过代码进行干预,是系统内核自动负责完成的)

       对于应用程序来说,客户端这边主要是发起“建立连接”动作;

        服务器这边,主要是把建立好的连接从内核中拿到应用程序;

       如果有客户端和服务器建立连接买这个时候服务器的应用程序是不需要做出任何操作(也没有任何感知),内核直接完成了连接建立的流程(三次握手),完成流程之后,就会在内核的队列中排队(这个队列是每一个serverSocket都有这样一个队列),应用程序要想和这个客户端进行通信,就需要通过一个accept方法把内核队列中已经建立好的连接对象,拿到应用程序中;

2 基于TCP实现通信 

2.1 代码实现   

服务器代码:

package network;

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.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService service = Executors.newCachedThreadPool();
        while (true) {
            // 通过 accept 方法, 把内核中已经建立好的连接拿到应用程序中.
            // 建立连接的细节流程都是内核自动完成的. 应用程序只需要 "捡现成" 的.
            Socket clientSocket = serverSocket.accept();
            // 此处不应该直接调用 processConnection, 会导致服务器不能处理多个客户端.
            // 创建新的线程来调用更合理的做法.
            // 这种做法可行, 不够好
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();

            // 更好一点的办法, 是使用线程池.
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    // 通过这个方法, 来处理当前的连接.
    public void processConnection(Socket clientSocket) {
        // 进入方法, 先打印一个日志, 表示当前有客户端连上了.
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(),
                clientSocket.getPort());
        // 接下来进行数据的交互.
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 使用 try ( ) 方式, 避免后续用完了流对象, 忘记关闭.
            // 由于客户端发来的数据, 可能是 "多条数据", 针对多条数据, 就循环的处理.
            while (true) {
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
                    // 连接断开了. 此时循环就应该结束
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                    break;
                }
                // 1. 读取请求并解析. 此处就以 next 来作为读取请求的方式.
                // next 的规则是, 读到 "空白符" 就返回.
                String request = scanner.next();
                // 2. 根据请求, 计算响应.
                String response = process(request);
                // 3. 把响应写回到客户端.
                //    可以把 String 转成字节数组, 写入到 OutputStream
                //    也可以使用 PrintWriter 把 OutputStream 包裹一下, 来写入字符串.
                PrintWriter printWriter = new PrintWriter(outputStream);
                //    此处的 println 不是打印到控制台了, 而是写入到 outputStream 对应的流对象中, 也就是写入到 clientSocket 里面.
                //    自然这个数据也就通过网络发送出去了. (发给当前这个连接的另外一端)
                //    此处使用 println 带有 \n 也是为了后续 客户端这边 可以使用 scanner.next 来读取数据.
                printWriter.println(response);
                //    此处还要记得有个操作, 刷新缓冲区. 如果没有刷新操作, 可能数据仍然是在内存中, 没有被写入网卡.
                printWriter.flush();
                // 4. 打印一下这次请求交互过程的内容
                System.out.printf("[%s:%d] req=%s, resp=%s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 在这个地方, 进行 clientSocket 的关闭.
                // processConnection 就是在处理一个连接. 这个方法执行完毕, 这个连接也就处理完了.
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String process(String request) {
        // 此处也是写的回显服务器. 响应和请求是一样的.
        return request;
    }

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

客户端代码:

package network;

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

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // 需要在创建 Socket 的同时, 和服务器 "建立连接", 此时就得告诉 Socket 服务器在哪里~~
        // 具体建立连接的细节, 不需要咱们代码手动干预. 是内核自动负责的.
        // 当我们 new 这个对象的时候, 操作系统内核, 就开始进行 三次握手 具体细节, 完成建立连接的过程了.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        // tcp 的客户端行为和 udp 的客户端差不多.
        // 都是:
        // 3. 从服务器读取响应.
        // 4. 把响应显示到界面上.
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter writer = new PrintWriter(outputStream);
            Scanner scannerNetwork = new Scanner(inputStream);
            while (true) {
                // 1. 从控制台读取用户输入的内容
                System.out.print("-> ");
                String request = scanner.next();
                // 2. 把字符串作为请求, 发送给服务器
                //    这里使用 println, 是为了让请求后面带上换行.
                //    也就是和服务器读取请求, scanner.next 呼应
                writer.println(request);
                writer.flush();
                // 3. 读取服务器返回的响应.
                String response = scannerNetwork.next();
                // 4. 在界面上显示内容了.
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

2.2 代码分析 

1、基础内容

        所谓的空白符,是一类特殊的字符(类似于换行,回车符,空格,制表符,翻页付,垂直制表符),后续客户端发起的请求,会议空白符作为结束标记(此处就约定使用\n)

        TCP 是字节流通信方式,每次传输多少个字节,每次读取多少个字节,我们往往会手动约定出,从哪里到哪里是一个完整的数据报.上述这里就是约定了使用 \n 作为数据报的结束标记. 就正好可以搭配scanner.next 来完成请求的读取过程,

        ClientSocket 则是在循环中,每次有一个新的客户端来建立连接,都会创建出新的 clientSocket

        每一次执行到clientSocket语句时,,都会创建新的 clientsockete,并且这个 socket 最多使用到该客户端退出 (断开连接),此时,如果有很多客户端都来建立连接,此时,就意味着每个连接都会创建 clientSocket.当连接断开后clientsocket 就失去作用了,但是如果没有手动 close,此时这个 socket 对象就会占据着文件描述符表的位置

        这里的关闭, 只是关闭了 clientsocket 上自带的流对象,并没有关闭 socket 本身.在这个代码中,需要在方法末尾通过 finally 加上 close,保证当前这里的 socket 能够被正确关闭掉;

2、关于多线程

当前启动两个客户端,同时连接服务器.其中一个客户端(先启动的客户端)一切正常,另一个客户端 (后启动的客户端)则没法和服务器进行任何交豆.(服务器不会提示"建立连接”,也不会针对 请求 做出任何响应,这就是关于多线程的一个很明显的问题;

        第一个客户端过来之后,accept 就返回了,得到一个 clientSocket.进入processConnection
,又进入一个 while 循环,这个循环中, 就需要反复处理客户端发来的请求数据.如果客户端这会没发请求,服务器的代码就会阻塞在scanner.hasNext 这里;

        此时此刻,第二个客户端也过来建立连接了,此时连接是可以建立成功(内核负责的),建立成功之后,连接对象就会在内核的队列里等待代码通过 accept 把连接给取出来,在代码中处理
当前的代码,其实无法第一时间执行到第二次的 accept 

        为了让一个服务器可以同时接待多个客户端,上述问题解决的关键就是引入多线程,让每一个客户端都能进行入到accept方法,进入第二次循环;

3、关于引入线程池

        此时这个服务器,每个客户端都要创建一个线程,如果有很多客户端.频繁的来进行建立连接/断开连接,这个时候就会导致服务器频繁的 创建/销毁 线程,(开销就很大了),所以可以使用线程池,来进一步的优化关于线程开销的问题;

2.3 代码运行分析:

          tcp 程序, 客户端启动,就会和服务器建立连接,服务器这边就能感受到(accept 方法就会返回,进一步的进入到 processConnection 中,如果启动多个客户端,即多个客户端同时和服务器建立连接,默认情况下,IDEA 只允许一个代码只能创建一个进程.通过下图所示操作,勾选了 Allow multiple instances,此时就可以运行多个进程了.

        最后通过使用多线程和线程池的相关内容,完成tcp通信,如下图所示:

ps:本篇文章主要讲解了关于tcp实现通信连接的相关的知识点,如果大家感兴趣的话就请一键三连哦!!!

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

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

相关文章

服务治理中间件-Eureka

目录 简介 搭建Eureka服务 注册服务到Eureka 简介 Eureka是Spring团队开发的服务治理中间件,可以轻松在项目中,实现服务的注册与发现,相比于阿里巴巴的Nacos、Apache基金会的Zookeeper,更加契合Spring项目,缺点就是…

论文阅读:《Deep Learning-Based Human Pose Estimation: A Survey》——Part 1:2D HPE

目录 人体姿态识别概述 论文框架 HPE分类 人体建模模型 二维单人姿态估计 回归方法 目前发展 优化 基于热图的方法 基于CNN的几个网络 利用身体结构信息提供构建HPE网络 视频序列中的人体姿态估计 2D多人姿态识别 方法 自上而下 自下而上 2D HPE 总结 数据集…

GEE:最小距离(minimumDistance)回归教程(样本点、特征添加、训练、精度、参数优化)

作者:CSDN @ _养乐多_ 对于分类问题,这个输出通常是一个类别标签 ,而对于回归问题,输出通常是一个连续的数值。回归可以应用于多种场景,包括预测土壤PH值、土壤有机碳、土壤水分、碳密度、生物量、气温、海冰厚度、不透水面积百分比、植被覆盖度等。 本文将介绍在Google…

位运算+leetcode(1)

基础 1.基础知识 以下都是针对数字的二进制进行操作 >> 右移操作符<< 左移操作符~ 取反操作符 & 有0就是0&#xff0c;全一才一 | 有一才一 &#xff0c;全0才0^ 相同为0&#xff0c;相异为1 异或( ^ )运算的规律 a ^ 0 a a ^ a 0a ^ b ^ c a ^ (b …

2本对微服务拆分有帮助的书

迁移到云原生应用架构 可在线观看的免费书籍 https://pivotal.io/platform-as-a-service/migrating-to-cloud-native-application-architectures-ebook 微服务架构设计模式 世界十大架构师之一&#xff1a;克里斯理查森著

【51单片机】LED点阵屏(江科大)

9.1LED点阵屏 1.LED点阵屏介绍 LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。 2.LED点阵屏工作原理 LED点阵屏的结构类似于数码管,只不过是数码管把每一列的像素以“8”字型排列而已。原理图如下 每一行的阳极连在一起,每一列…

如何在C# Windows Forms应用程序中实现控件之间的连接线

帮我实现绘图工具多个控件连接线&#xff0c;请用c#代码实现 实现绘图工具中多个控件之间的连接线功能&#xff0c;可以通过以下几个步骤来进行&#xff1a; 定义连接线的数据模型&#xff1a;首先需要定义一个模型来表示连接线&#xff0c;这个模型应该包含起点和终点的坐标。…

【Effective Objective - C 2.0】——读书笔记(四)

文章目录 二十三、通过委托与数据源协议进行对象间通信二十四、将类的实现代码分散到便于管理的数个分类之中二十五、总是为第三方的分类名称加前缀二十六、切勿在分类里面声明属性二十七、使用“class-continuation分类”隐藏实现细节二十八、通过协议提供匿名对象 二十三、通…

【Chrono Engine学习总结】4-vehicle-4.1-vehicle的基本概念

由于Chrono的官方教程在一些细节方面解释的并不清楚&#xff0c;自己做了一些尝试&#xff0c;做学习总结。 1、基本介绍 Vehicle Overview Vehicle Mannel Vehicle的官方demo 1.1 Vehicle的构型 一个车辆由许多子系统构成&#xff1a;悬挂、转向、轮子/履带、刹车/油门、动…

书生谱语-大语言模型测试demo

课程内容简介 通用环境配置 开发机 InterStudio pip 换源 临时使用镜像源安装&#xff0c;如下所示&#xff1a;some-package 为你需要安装的包名 pip install -i https://mirrors.cernet.edu.cn/pypi/web/simple some-package设置pip默认镜像源&#xff0c;升级 pip 到最新…

力扣1122. 数组的相对排序(哈希表)

Problem: 1122. 数组的相对排序 文章目录 题目描述思路及解法复杂度Code 题目描述 思路及解法 1.利用arr2创建一个无序映射&#xff08;map集合&#xff09;&#xff0c;以其中的元素作为键&#xff0c;值默认设置为0&#xff1b; 2.扫描arr1数组统计arr2元素在其中的个数(将个…

Pandas教程12:常用的pd.set_option方法,显示所有行和列+不换行显示等等...

---------------pandas数据分析集合--------------- Python教程71&#xff1a;学习Pandas中一维数组Series Python教程74&#xff1a;Pandas中DataFrame数据创建方法及缺失值与重复值处理 Pandas数据化分析&#xff0c;DataFrame行列索引数据的选取&#xff0c;增加&#xff0c…

matlab发送串口数据,并进行串口数据头的添加,我们来看下pwm解析后并通过串口输出的效果

uintt16位的话会在上面前面加上00&#xff0c;16位的话一定是两个字节&#xff0c;一共16位的数据 如果是unint8的话就不会&#xff0c; 注意这里给的是13&#xff0c;但是现实的00 0D&#xff0c;这是大小端的问题&#xff0c;在matlanb里设置&#xff0c;我们就默认用这个模式…

python 笔记:shapely(形状篇)

主要是点&#xff08;point&#xff09;、线&#xff08;linestring&#xff09;、面&#xff08;surface&#xff09; 1 基本方法和属性 object.area 返回对象的面积&#xff08;浮点数&#xff09; object.bounds 返回一个&#xff08;minx, miny, maxx, maxy&#xff09;元…

基于Python的HTTP隧道安全性分析:魔法背后的锁与钥匙

当我们谈论基于Python的HTTP隧道时&#xff0c;不禁让人想起那些神秘的魔法门。但是&#xff0c;在魔法背后&#xff0c;我们也需要确保安全性&#xff0c;就像需要确保魔法不会落入邪恶之手一样。那么&#xff0c;基于Python的HTTP隧道在安全性方面表现如何呢&#xff1f;让我…

【Java程序设计】【C00270】基于Springboot的moba类游戏攻略分享平台(有论文)

基于Springboot的moba类游戏攻略分享平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的游戏攻略分享平台 本系统分为系统功能模块、管理员功能模块、以及用户后台功能模块。 系统功能模块&#xff1a;在平台首…

[VulnHub靶机渗透] Nyx

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

【Java程序设计】【C00253】基于Springboot的在线考试管理系统(有论文)

基于Springboot的在线考试管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的在线考试系统 本系统分为系统功能模块、管理员功能模块以及用户功能模块。 系统功能模块&#xff1a;系统登录&#xff0c;管理…

综合项目---博客

一.运行环境 192.168.32.132 Server-Web linux Web 192.168.32.133 Server-NFS-DNS linux NFS/DNS 基础配置 1.配置主机名静态ip 2.开启防火墙并配置 3.部分开启selinux并配置 4.服务器之间通过阿里云进行时间同步 5.服务器之间实现ssh免密…