[Java EE] 网络编程与通信原理(三):网络编程Socket套接字(TCP协议)

news2024/9/19 10:09:14

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(94平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

目录

  • 1. TCP套接字编程
    • 1.1 API介绍
      • 1.1.1 ServerSocket
      • 1.1.2 Socket
    • 1.2 代码实例
      • 1.2.1 回显服务器
      • 1.2.2 回显客户端
    • 1.3 为服务器引入多线程
  • 2. 拓展了解:长连接与短连接

1. TCP套接字编程

前面的UDP套接字是基于数据报为基本单位进行传输的,而今天我们要叙述的TCP套接字是基于字节为单位进行传输的.

1.1 API介绍

1.1.1 ServerSocket

本类是创建在服务器端的Socket的API.

  • 构造方法:
方法签名方法说明
ServerSocket(int port)创建⼀个服务端流套接字Socket,并绑定到指定端⼝
  • 常用成员方法
方法签名方法说明
Socket accept()开始接听指定端⼝(创建时绑定的端⼝),有客户端连接后,返回⼀个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待(没有客户端与之进行连接),注意这里返回的数据类型是Socket类型
void close()关闭此套接字

举例说明:买房
有一天一位老哥想要买一套房子,他在马马路牙子上见到了一位西装革履的销售小哥,这时候这位销售小哥就问老哥:“哥,要买房吗?”.这位老哥正好想买套房子,于是跟着小哥去了售楼部.到了售楼部之后,这位小哥一挥手,来了一个Leader风格穿着的小姐姐,哥老哥一桶踢里哐啷地介绍房子.
在这个过程中,ServerSocket的构造方法的作用就好像是这个销售小哥一样,用来揽客.(用来和客户端用端口号建立连接),而这个售楼部的小姐姐就好像ServerSocket中的accept方法一样,在销售小哥揽客之后,接待客户(在与客户端建立连接之后,接通客户端的请求).
在这里插入图片描述

1.1.2 Socket

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服
务端Socket
,也就是Socket类在客户端和服务器中都要用.
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据
的。

  • Socket的构造方法
方法签名方法说明
Socket(String host,int port)创建⼀个客户端流套接字Socket,并与对应IP的主机上,对应端⼝的进程建⽴连接

[注意] 客户端与服务器进行连接的时候,构造方法本身就可以与指定的服务器进行连接,也就是拨号的过程.

  • Socket的常用成员方法
方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输⼊流
OutputStream getOutputStream()返回此套接字的输出流

这里我们看到,在使用这些成员方法的时候,可以获取到Socket套接字的输入流和输出流,之后我们在客户端这边向着服务器发送请求的时候,使用的是输出流,反之从服务器获取响应的时候,我们用的是输入流,反之,在服务器这边,向着客户端返回响应的时候,使用输出流,接收请求的时候,使用输入流,这里的输入和输出的参照是以自身为参照.后续我们使用read()或者Scanner从InputStream读取数据之后,就可以对数据进行操作,处理好之后,在通过write()操作写回OutputStream.

[注意] 这里使用到的输入流和输出流,我们在之前的文件IO章节提到过.
https://blog.csdn.net/2301_80050796/article/details/138851572?spm=1001.2014.3001.5501

1.2 代码实例

在代码中,我们整体会涉及到三个Socket套接字:

  1. 服务器ServerSocket
  2. 服务器Socket,通过这个Socket和客户端提供交换能力
  3. 客户端Socket,通过这个Socket和服务器提供交换能力

1.2.1 回显服务器

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

public class TCP_Echo_Server {
    private ServerSocket server = null;//服务器用于操作网卡的Socket
    //构造方法指定端口号
    public TCP_Echo_Server(int port) throws IOException {
        server = new ServerSocket(port);//传入端口号,创建服务器Socket
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = server.accept();//接收客户端请求,返回Socket对象
            processConnection(clientSocket);//传入accept的请求,处理请求
        }
    }
    private void processConnection(Socket cilentSocket) throws IOException {
        //打印登录日志
        System.out.printf("[%s:%d] 客户端上线\n",cilentSocket.getInetAddress(),cilentSocket.getPort());
        //从获取到的请求中获取到输入流和输出流,由于它们两个需要关闭,所以写在try中
        try (InputStream inputStream = cilentSocket.getInputStream();
        OutputStream outputStream = cilentSocket.getOutputStream()){
            while (true){
                Scanner scanner = new Scanner(inputStream);//使用Scanner的方式读取输入流中的数据
                if (!scanner.hasNext()){//如果输入流中没有数据了,就结束循环
                    System.out.printf("[%s:%d] 客户端下线\n",cilentSocket.getInetAddress(),cilentSocket.getPort());
                    break;
                }
                String request = scanner.next();//读取字符串
                String response = process(request);//处理请求,返回响应
                outputStream.write(response.getBytes());//把响应写回输出流
                System.out.printf("[%s:%d] req = %s,resp = %s\n",cilentSocket.getInetAddress(),cilentSocket.getPort(),
                request,response);//打印请求响应日志
            }
        }finally {
            cilentSocket.close();//关闭Socket.每一个客户端请求都有一个Socket,用完就没用了,所以要关闭
        }
    }
    private String process(String request){
        return request + '\n';//之所以要加上一个换行符,是因为响应写回到客户端要把一大串字节分为若干数据报
    }

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

[注意的几点]

  1. 注意在accept接听请求的时候,在外层加上while死循环.
  2. 服务器在通过ServerSocket接听客户端发送来的请求并读取数据之后,需要关闭cilentSocket对象,而且为了防止程序在处理请求的过程中出现异常导致程序以外终止,我们还需要把它放入finally中.不关闭就会导致文件资源泄露,是当前的cilentSocket对象的文件描述符表得不到释放.

我们还是那上面的买房的例子来说明
销售小哥不可能只揽你一个人,他需要不停地从外面揽客,就需要不停有小姐姐来接待,所以需要while死循环.而售楼部的小姐姐也不可能光给你自己提供服务,而是在接待完你之后,还会在接待其他人,所以在接待你之后,就需要close.

  1. 就像我们前面说的,使用InputStream和OutputStream之后需要关闭,所以我们就需要把输入流和输出流的创建写入try的括号中.
  2. 这里不使用read()读取,读取出来的是字节byte[],使用Scanner进行读取的时候,会对字节进行转换,自动转换为字符串.
  3. scanner.next()读取数据的时候,由于我们后边写到的客户端会使用"空白符"来将一大串数据进行分割,每次分割都对应的是一个应用层数据报,所以我们这里在使用next读取的时候,读取到的就是一个个完整的"字节流请求",而不是没有分隔符的"一大坨数据".
  4. 与第5条相同的道理,在返回响应的时候,我们同样要在数据的末尾加上"空白符",以便客户端进行读取. return request + '\n';

1.2.2 回显客户端

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

public class TCP_Echo_Client {
    private Socket socket = null;//客户端用于操作网卡的Socket

    public TCP_Echo_Client(String IP,int Port) throws IOException {
        socket = new Socket(IP,Port);//通过构造方法传入的服务器的IP地址和端口号构造Socket
    }
    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);//客户端通过控制台输入请求
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()){
            while (true){
                System.out.println(">>");
                String request = scanner.next();//通过Scanner获取到要发送的请求
                request += '\n';//为请求加上一个空白符,以便服务器通过Scanner.next读取
                outputStream.write(request.getBytes());//写入输出流
                Scanner scanner1 = new Scanner(inputStream);//从输入流读取服务器响应
                String response = scanner1.next();
                System.out.println(response);
            }
        }
    }

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

[需要注意的几点]

  1. request += '\n';这里要在请求的后面加上\n,这样在服务器读取响应的时候,就是一个个完整的"字节流请求".
  2. scanner1.next();读取"字节流响应"的时候,由于前面服务器使用\n对数据进行过分割,所以我们在这里就可以使用这样的方式进行读取.

1.3 为服务器引入多线程

上面的代码出现的最大的问题就是,不可以同时给多个客户端提供服务.这其实不是API的问题,是我们代码结构的问题,就是由于我们在上述的代码中出现了死循环嵌套的代码方式:
第一个客户端在被服务器accept()接听之后,进入processConnection(clientSocket);方法内部会有while (true)循环,这就使得服务器在没有等到第一个客户端结束运行之后,上层start()方法中的死循环无法进行下一个循环接听下一个客户端.所以会出现不支持同时给多个客户端提供服务的现象.
在这里插入图片描述
先启动的客户端有响应.
在这里插入图片描述
后启动的客户端无响应.
所以我们在服务器的代码中引入了多线程.accept()接听一个客户端,就给这个客户端的Socket单独创建一个线程,这样在接听下一个客户端的时候,就不会受上一个客户端的影响.

  • 引入多线程
public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = server.accept();//接收客户端请求,返回Socket对象
            Thread thread = new Thread(()->{//使用多线程同时处理多个客户端发来的请求
                try {
                    processConnection(clientSocket);//传入accept的请求,处理请求
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        }
    }

其中每个创建的新线程为每个客户端的Socket提供服务,而main线程用来循环处理接听操作.
当然我们也可以引入线程池,我们在前面提到过线程池的概念.
https://blog.csdn.net/2301_80050796/article/details/138388735?spm=1001.2014.3001.5501
从下图可以看到,同时启动的两个客户端都有反应:
在这里插入图片描述
在这里插入图片描述

服务器也有两个客户端的登录日志:
在这里插入图片描述

  • 引入线程池
 public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();//使用线程池同时处理多个客户端发来的请求
        while (true){
            Socket clientSocket = server.accept();//接收客户端请求,返回Socket对象
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);//传入accept的请求,处理请求
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

[注意]

  • 这里我们使用线程数量动态变化的线程池创建方式.Executors.newCachedThreadPool();,而使用创建固定线程的线程池创建方式不合乎逻辑,我们不可能只给固定几个客户端提供服务.
  • 这里我们使用线程池的方式,比循环创建线程的方式更加高效,创建线程会涉及到线程的创建和销毁,会有一定的开销,而线程池在用完一个线程之后,就会将这个线程回收回线程池,使得每一个线程更加充分地利用起来.

2. 拓展了解:长连接与短连接

TCP发送数据时,需要先建⽴连接,什么时候关闭连接就决定是短连接还是⻓连接:
短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能⼀次收发
数据。
长连接:不关闭连接,⼀直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可
以多次收发数据。

基于BIO(同步阻塞IO)的长连接会⼀直占⽤系统资源。对于并发要求很⾼的服务端系统来说,这样的消耗是不能承受的。
由于每个连接都需要不停的阻塞等待接收数据,所以每个连接都会在⼀个线程中运⾏
⼀次阻塞等待对应着⼀次请求、响应,不停处理也就是⻓连接的特性:⼀直不关闭连接,不停的处理
请求。
实际应⽤时,服务端⼀般是基于NIO(即同步非阻塞IO或IO多路复用)来实现长连接,性能可以极大的提升.一个线程在同时管理多个clientSocket.

举例说明:下楼上小摊买饭
一家三口,爸爸妈妈和孩子.三个人要吃不同的饭,第一种方案就是,三个人都下楼,各买各的.就是同步阻塞IO.
第二种方案就是只让爸爸一个人下去买,爸爸就可以在买完一份饭之后,在等待这个饭做出来的途中,再去买另一个饭,这样就会大大提高效率.就是IO多路复用.
在这里插入图片描述

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

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

相关文章

第36届世界超级小姐大赛安徽赛区正式启动

5月26日,在现场几百位出席仪式的社会名流、时尚界大咖、文化旅游机构负责人和前沿品牌代表以及现场嘉宾的共同见证下,第36届世界超级小姐大赛安徽赛区活动的神秘面纱终于在安徽黄山悠悠湖文化中心正式揭开。伴随着高亢激情的现代音乐,长发飘逸、身形灵动…

“AURORA-M:首个遵循人类审查安全指令微调的开源多语言模型

在人工智能领域,多模态学习是一个日益增长的研究领域,它涉及将来自不同源(如图像、文本、音频等)的信息结合起来。但高昂的训练计算成本限制了模型的普及性,使得小型机构和个人难以负担。而且现有模型在多语言能力上受…

结算协同,打通企业上下游业、财、票、资

市场变革,转型当先 随着电子发票的普及与数字化浪潮的涌动,企业正面临着前所未有的转型挑战。如何在快速变化的市场中站稳脚跟,提升竞争力?答案在于数字化转型,特别是供应链结算流程的革新。 无纸化、自动化&#xff0…

数字孪生为什么这么火?水务离不开它的原因又是什么?

数字孪生利用可视化技术结合视频融合、BIM、5G、物联网、云计算和大数据等先进技术,围绕实现水质达标、安全生产、高效节能等生产、运营和管理目标。它构建了一个三维场景,涵盖自来水厂区的建筑、生产设备、管线等设施,以实现对水厂实时运行信…

分享目前堪称最好用的车机桌面app,支持画中画模式!

好用的车机桌面了,支持悬浮画中画! 如果你想改变单调的车机桌面,那么这篇文章你不要错过了!支持地图悬浮、画中画的车机桌面,不要错过! 今天阿星给大家分享3款好用、好看的大屏桌面软件,重要的…

Python数据分析常用函数

Python基础 数字处理函数 Python提供了用于数字处理的内置函数和内置模块(math),使用内置模块,需要先导入 import math。 内置函数math模块abs(-5)返回绝对值math.ceil(2.3)返回不小于x的最小整数divmod(9,4)返回商和余数math.floor(2.3)返回不大于x的…

Unity OutLine 模型外描边效果

效果展示: 下载链接

十四天学会Vue——Vue核心(理论+实战)中篇(第二天)

声明:是接着上篇讲的哦,感兴趣可以去看一看~ 这里一些代码就不写了,为了缩减代码量,大家知道就可以了: Vue.config.productionTip false //阻止 vue 在启动时生成生产提示。热身小tips,可以安装这个插件&…

记一次重定向问题(浏览器安全)解决

近期做单点登陆功能,本身应该是一个很简单的功能,却发生了意向不到的问题…让我们看下: 首先第三方给出的地址需要通过JWT框架获取token拼接后跳转,我这边为了方便首选肯定是考虑用response.sendRedirect(url),但是做好…

源代码防泄漏方案需要具备哪些因素?

首选选择加密软件对公司来说是一项关键决策,需要细致考虑多个因素。 选择合适的加密软件:关键因素与推荐方案 一、稳定性:加密软件的核心 稳定性是评估加密软件的首要因素。一个不稳定的加密软件可能导致数据损坏或系统冲突,影响…

【机器学习】机器学习在信息安全领域中的典型应用

🚀🚀🚀传送门 🔒机器学习在信息安全领域中的典型应用📕利用机器学习检测恶意行为并阻断攻击🌈使用机器学习分析移动终端安全状况⭐借助机器学习提高信息安全分析水平🎬依靠机器学习自动完成重复…

“按摩”科技?

都说A股股民是特别善于学习的,这不市场又现新概念——“按摩科技”,成立仅6年,把上门按摩干到35亿营收也是没谁了,现在号称有1000万用户,3万家入驻商户数的按摩平台,难道就凭借2.5万名女技师,活…

【YOLO 系列】基于YOLO V8的学生上课行为检测系统【python源码+Pyqt5界面+数据集+训练代码】

前言 在现代教育环境中,学生上课行为的监测对于提升教学质量和学生学习效率具有重要意义。然而,传统的人工观察方法不仅效率低下,而且难以保证客观性和准确性。为了解决这一问题,我们启动了这个项目,目的是利用YOLOV8…

【LeetCode算法】第83题:删除排序链表中的重复元素

目录 一、题目描述 二、初次解答 三、官方解法 四、总结 一、题目描述 二、初次解答 1. 思路:双指针法,只需遍历一遍。使用low指向前面的元素,high用于查找low后面与low不同内容的节点。将具有不同内容的节点链接在low后面,实…

make disclean V=1 分析

文章目录 make distclean步骤1:2090-2114行,执行依赖 clean步骤2:2120-2124行,执行依赖 $(mrproper-dirs)步骤3:2118-2129行,执行依赖 mrproper步骤4:2135-2142行,实现 distclean 编…

恶意退市潮?

一张A4纸,炸出一池鱼。史上(最)严新规,这一拳打到了(违规减持)上。 新规算是对新国九条的补充,更是给大股东们上紧箍咒。那市场买账吗?昨晚爆出19家董监高亲属(违规&…

post请求

文章目录 一、get请求和post请求区别二、get请求和post请求的用法对比1.get请求2.post请求 三、如何知道是get请求还是post请求 一、get请求和post请求区别 二者区别就是一句话:post请求更安全 二、get请求和post请求的用法对比 1.get请求 get请求: 请求参数&am…

RK3568笔记二十七:LPRNet车牌识别

若该文为原创文章,转载请注明原文出处。 记录自训练并在RK3568上部署。 一、介绍 LPRNet的Pytorch实现,一种高性能和轻量级的车牌识别框架。完全适用于中国车牌识别(Chinese License Plate Recognition)及国外车牌识别&#xf…

Vue2和Vue3生命周期的对比

Vue2和Vue3生命周期的对比 Vue2 和 Vue3 生命周期对照表Vue2 和 Vue3 生命周期图示 Vue2 和 Vue3 生命周期对照表 触发时机Vue2.xVue3.x组件创建时运行beforeCreate setup createdsetup 挂载在DOM时运行beforeMountonBeforeMountmountedonMounted响应数据修改时运行beforeUpdat…

张大哥笔记:赚钱高手养成计划---如何将一份时间产生N份收入?

我们常说的赚钱的四种境界有哪些? 1.靠体力挣钱 2.靠技能挣钱 3.靠知识挣钱 4.靠平台钱生钱 所以对应的收入的模式就会是下面4种模式: 1.一份时间卖1次 2.一份时间卖N次 3.一份时间溢价卖N次 4.购买他人时间为自己所用 时间对于每个人都是相同的…