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

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() {@Overridepublic 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.mzph.cn/news/690151.shtml

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

相关文章

全网最容易理解的KMP算法讲解

引言 其实网上有很多讲解KMP算法的文章,详略不一,我认为有两点没有解释清楚: 第一点:匹配失败以后,模式串的位移 第二点:next数组的生成算法 希望本篇文章能将KMP算法清晰易懂的拆解开来。 暴力匹配 …

网络同步—帧同步和状态同步解析

概述 同步就是要多个客户端表现效果是一致的,而且对于大多数的游戏,不仅仅要表现一致,还要客户端和服务器的数据也是一致的。所以同步是个网络游戏概念,只有网络游戏才需要同步,而单机游戏是不需要同步的。 帧同步和…

算法-3-基本的数据结构

单双链表 1.单链表双链表如何反转 import java.util.ArrayList; import java.util.List;public class Code01_ReverseList {public static class Node {public int value;public Node next;public Node(int data) {value data;}}public static class DoubleNode {public int…

掘根宝典之C++深复制与浅复制(复制构造函数,默认复制构造函数)

到目前为止我们已经学了构造函数,默认构造函数,析构函数:http://t.csdnimg.cn/EOQxx 转换函数,转换构造函数:http://t.csdnimg.cn/kiHo6 友元函数:http://t.csdnimg.cn/To8Tj 接下来我们来学习一个新函数…

python毕设选题 - 大数据全国疫情数据分析与3D可视化 - python 大数据

文章目录 0 前言1 课题背景2 实现效果3 设计原理4 部分代码5 最后 0 前言 🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的…

2024阿里云云服务器ECS价格表出炉

2024年最新阿里云服务器租用费用优惠价格表,轻量2核2G3M带宽轻量服务器一年61元,折合5元1个月,新老用户同享99元一年服务器,2核4G5M服务器ECS优惠价199元一年,2核4G4M轻量服务器165元一年,2核4G服务器30元3…

【Funny Game】 人生重开模拟器

目录 【Funny Game】 人生重开模拟器! 人生重开模拟器! 文章所属专区 Funny Game 人生重开模拟器! 人生重开模拟器,让你体验从零开始的奇妙人生。在这个充满惊喜和挑战的游戏中,你可以自由选择性别、出生地、家庭背景…

String.format()详细用法

String 类有一个强大的字符串格式化方法 format()。下面是常用的方法总结。 一、占位符类型 String formatted String.format("%s今年%d岁。", "小李", 25); // "小李今年25岁。" 二、字符串和整数格式化 // 将第二个入参拼接到模板中,入参长…

职业性格在求职应聘和跳槽中的作用

性格测试对跳槽者的影响大不大?首先我们要弄清楚两个问题,性格对我们的职业生涯又没有影响,性格测试是什么,职场中有哪些应用?性格可以说从生下来就有了,随着我们的成长,我们的性格也越来越根深…

大模型训练流程(一)预训练

预训练GPU内存分析: GPU占用内存 模型权重 梯度 优化器内存(动量估计和梯度方差) 中间激活值*batchsize GPU初始化内存 训练流程 (选基座 —> 扩词表 —> 采样&切分数据 —> 设置学习参数 —> 训练 —>…

什么是美颜SDK?美颜SDK在短视频平台中的作用探究

在这个以视频为主导的平台上,美颜技术在其中扮演了不可或缺的角色。本文将探讨美颜SDK的本质,以及它在短视频平台中所发挥的作用。 一、什么是美颜SDK? 美颜SDK是一种软件开发工具包,其主要功能是通过算法对图像进行美化处理。…

【教3妹学编程-算法题】人员站位的方案数 II

2哥 : 3妹,今天第一天上班啊,开工大吉~ 3妹:2哥,开工大吉鸭,有没有开工红包? 2哥 : 我们公司比较扣,估计不会发的。 3妹:我们公司估计也一样,不过依然挡不住我打工人的热…

HarmonyOS router页面跳转

默认启动页面index.ets import router from ohos.router import {BusinessError} from ohos.baseEntry Component struct Index {State message: string Hello World;build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)//添加按钮&am…

【 JS 进阶 】原型对象、面向对象

目标 了解构造函数原型对象的语法特征,掌握 JavaScript 中面向对象编程的实现方式,基于面向对象编程思想实现 DOM 操作的封装。 了解面向对象编程的一般特征掌握基于构造函数原型对象的逻辑封装掌握基于原型对象实现的继承理解何为原型链及其作用能够处理…

C++Qt:noteBookPro_01

一、创建项目 选择Qt Widgets 常用的是QWidgets和MainWindow。两者的区别: QWidgets用于简单的窗口,没有内置的菜单栏、工具栏和状态栏。适用于简单专用的应用程序,不需要复杂的界面组件。 MainWindow是包含完整的菜单栏、工具栏和状态栏的主…

Linux 主机数据拷贝与 Linux 服务器之间拷贝文件的方法

Linux 主机数据拷贝与 Linux 服务器之间拷贝文件的方法 1. 使用 scp 命令2. 使用 rsync 命令3. 使用 scp 和 rsync 的图形界面工具4. 使用 FTP/SFTP 协议总结与比较 在 Linux 系统中,数据拷贝是日常操作中的常见需求,尤其是在不同主机或服务器之间进行文…

近场2D beamforming Heatmap图

文章目录 想法代码目前啥样想法 参考论文Beam Focusing for Near-Field Multiuser MIMO Communications,可视化beam focusing效应 代码 clc; clear;% 网格范围 D = 1; % 整个均匀平面阵列的孔径 lambda = 1e-2; % 波长0.01m,单位:米 30GhzN_d = floor(2 * D / lambda); %…

PS自由变换的小技巧--墙面广告牌

墙面广告牌,如何用PS做出看上去特别真实的一个效果 1.首先,我们会有墙面跟广告栏2个图层 2.然后将广告牌复制一层 3.接着用钢笔工具画出墙面的透视,也就是两条线,这两条线的交叉点就是墙面的透视点 4.接着选中广告牌复制图层&…

杨氏矩阵和杨辉三角

杨氏矩阵 有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。 要求:时间复杂度小于O(N); 分析 若要满足要求时间复杂度小于O(N),就不能每一行一个个…

TCP流量控制+拥塞控制

流量控制: 目标:流量控制主要解决的是发送方和接收方之间处理能力的不匹配问题。它的目的是确保发送方不会发送数据过快,以至于接收方无法及时接收并处理这些数据,从而避免数据包在网络中堆积和丢失。实现方式:在TCP协…