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

🌸个人主页: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;//客户端用于操作网卡的Socketpublic 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() {@Overridepublic 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.rhkb.cn/news/333181.html

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

相关文章

Spring MVC+mybatis项目入门:旅游网(四)用户注册——mybatis的配置与使用以及Spring MVC重定向

个人博客:Spring MVCmybatis项目入门:旅游网(四)用户注册2-持久化 | iwtss blog 先看这个! 这是18年的文章,回收站里恢复的,现阶段看基本是没有参考意义的,技术老旧脱离时代(2024年…

网络协议——FTP(简介、搭建FTP服务端)

一、简介 1、什么是FTP? FTP(File Transfer Protocol,文件传输协议) TCP/IP 协议组的协议之一。常用20(数据)、21(命令)端口作为通讯端口。(22为SSH端口)F…

BookStack VS HelpLook两款知识库软件的区别

现在很多企业都会进行知识管理,在这个过程中,选择一个合适的知识库软件是一个不可避免的问题。在众多知识库软件中,HelpLook和BookStack这两款软件备受企业瞩目。不知如何选择,今天LookLook同学就简单介绍一下这两款知识库的区别&…

微信公众号完成自动回复,自定义菜单

微信公众号完成自动回复,自定义菜单 首先要获取到微信公众号的开发者权限,这一步省略,可以自行百度 微信公众号对接自己的服务器 首先第一步需要有自己的服务器和固定的ip, 其中,80/443端口需要有其中一个&#xff0…

唯众云课堂:领航智慧教育,赋能职教未来,打造高效人才培养新平台

随着《中国智慧教育发展报告 2023》的发布,智慧教育被正式定义为数字教育发展的高级阶段。然而,各职院在智慧教育的发展道路上,往往面临着诸多挑战,如缺乏一体化教学平台、优质教学资源不足等。唯众凭借深厚的产业洞察与教育实践经…

【云原生】K8s管理工具--Kubectl详解(一)

一、陈述式管理 1.1、陈述式资源管理方法 kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口kubectl 是官方的 CLI 命令行工具,用于与 apiserver 进行通信,将用户在命令行输入的命令,组织并转化为apiserver 能识…

一文搞透常见的Python编码陷阱(上)(分析+案例)

一个认为一切根源都是“自己不够强”的INTJ 个人主页:用哲学编程-CSDN博客专栏:每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 一、别忘了冒号 1. if 语句 2. while 语句 3. for 语句 4. 函数定义 5. 类定义 6. try/except 语句 …

太速科技-基于FPGA Spartan6 的双路光纤PCIe采集卡(2路光纤卡)

基于FPGA Spartan6 的双路光纤PCIe采集卡(2路光纤卡) 1、板卡概述   板卡采用xilinx Spartan6系列芯片,支持 PCI Express Base Specification 1.1 x1。内含丰富的逻辑资源和存储单元,板卡FPGA外接双片32M*16bit DDR2缓存器,支持乒乓操作。…

Ubuntu执行命令出现乱码,菱形符号

1、问题描述 如题,Ubuntu执行命令出现乱码,菱形符号(见下图): 2、解决办法 export LC_ALLC 再运行就好了

fpga系列 HDL 00 : 可编程逻辑器件原理

一次性可编程器件(融保险丝实现) 一次性可编程器件(One-Time Programmable Device,简称 OTP)是一种在制造后仅能编程一次的存储设备。OTP器件在编程后数据不可更改。这些器件在很多应用场景中具有独特的优势和用途。 …

Web组态可视化编辑器 快速绘制组态图

演示地址:by组态[web组态插件] 随着工业智能制造的发展,工业企业对设备可视化、远程运维的需求日趋强烈,传统的单机版组态软件已经不能满足越来越复杂的控制需求,那么实现Web组态可视化界面成为了主要的技术路径。 行业痛点 对于…

nvm安装教程及使用nvm管理多个node版本

文章目录 前言一、nvm 安装教程温馨提示macOS/LinuxWindows 二、安装 node 前言 工作中,你可能会遇到以下场景: 我想使用 pnpm 命令安装依赖,但是在使用 pnpm 命令时提示如下 $ pnpm -v ERROR: This version of pnpm requires at least No…

移动云主机ECS搭建Kubernetes集群:详细步骤与指南

目录 云主机 ECS:云计算的强大引擎什么是云主机ECS?为何选择云主机ECS? 使用移动云ECS进行Kubernetes集群搭建1. 环境准备2. 安装步骤2.1 在每一个节点上执行的操作2.1.1 系统准备2.1.2 安装Docker2.1.3 安装Kubernetes的安装组件 2.2 在Mast…

chrome浏览器驱动下载

跑自动化的时候,需要打开谷歌浏览器,这个时候提示浏览器驱动找不到咋办呢? 1、网上搜索找到了这篇文章:https://www.cnblogs.com/laoluoits/p/17710501.html;按照文章介绍, 首先找到:CNPM Bin…

捷报!恒瑞医药ADC创新药SHR-A1921卵巢癌适应症拟纳入突破性治疗品种公示

近日,恒瑞医药自主研发的TROP-2抗体偶联药物(antibody-drug-conjugate, ADC)注射用SHR-A1921用于治疗铂耐药复发上皮性卵巢癌、输卵管癌或原发性腹膜癌适应症被国家药品监督管理局药品审评中心拟纳入突破性治疗品种公示名单。今年3月&#xf…

内网安全--域渗透准备知识

目录 知识点: 0x01 0x02 0x03 系列点: Linux主机信息收集 windows主机信息收集 知识点: 0、域产生原因 1、内网域的区别 2、如何判断在域内 3、域内常见信息收集 4、域内自动化工具收集 -局域网&工作组&域环境区别 -域…

Linux如何在目录下灵活创建、浏览、删除百万个文件

文章目录 一、创建百万级小文件1、单核CPU情况2、多核CPU情况3、执行效率对比3.1、单核的顺序执行3.2、多核的并发执行 二、如何列出/浏览这些文件1、查看目录下文件的数量2、列出?3、ls -f(关闭排序功能)3.1、执行效率对比 4、通过重定向导入…

HiWoo Box边缘计算网关

​在数字化浪潮汹涌的今天,边缘计算网关成为了连接物理世界与数字世界的桥梁,其重要性日益凸显。HiWoo Box,作为一款功能强大的边缘计算网关,不仅具备了传统网关的基本功能,更在数据采集、处理、传输等方面展现出了卓越…

“二叉堆:不是,啊?”

目录 前言一、堆的概念及结构堆的性质:堆的结构:最大堆最小堆堆顶注意 二、堆的实现1.初始化堆2. 堆的插入什么是堆的向上调整算法? 3.堆的删除什么是堆的向下调整算法? 4.获取堆顶的数据5.获取堆的数据个数6.堆的判空7.堆的销毁 三、建堆的时…

一文了解安卓内存抖动

目录 目录一、什么是内存抖动?1.1 Android里的内存抖动1.2 如何直观查看这种现象1.3 内存抖动带来的风险 二、如何避免内存抖动 目录 一、什么是内存抖动? 在程序里,每创建一个对象,就会有一块内存分配给它,每分配一…