网络编程—Socket套接字(TCP)

上篇文章:

网络编程—Socket套接字(UDP)https://blog.csdn.net/sniper_fandc/article/details/146923670?fromshare=blogdetail&sharetype=blogdetail&sharerId=146923670&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

目录

1 TCP流套接字

2 模拟实现TCP服务器


1 TCP流套接字

        基于TCP的Socket主要有:ServerSocket和Socket,ServerSocket用于创建TCP服务器端的Socket,而Socket用于创建TCP客户端的Socket。操作方式也类似文件。

构造方法/方法

含义

ServerSocket(int port)

构造方法,创建一个服务端流套接字Socket,并绑定到指定端口

Socket accept()

普通方法,开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待

void close()

关闭TCP套接字

        因为TCP是面向流的数据读写方式,因此没有像DatagramPacket数据报的API,只需创建Socket后,采用类似InputStream和OutputStream的操作方式。也可以对InputStream和OutputStream进行Scanner和PrintWriter的包装,便于字符数据的读写。

构造方法/方法

含义

Socket(String host, int port)

构造方法,创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

InetAddress getInetAddress()

从套接字中获取连接的IP地址

InputStream getInputStream()

返回套接字中的输入流(读请求)

OutputStream getOutputStream()

返回套接字中的输出流(写响应)

        注意:Socket可能有两种获得的方式,1是使用Socket构造方法,2是使用ServerSocket的方法accept()。也就是说ServerSocket的主要作用就是创建TCP服务器的全局连接监听,客户端作为连接发起方,因此直接创建Socket表示申请建立连接,而ServerSocket的accept()方法一旦监听到有客户端申请建立连接,就返回一个Socket用于建立服务器和客户端之间的连接。

        上述分析方式也透露了ServerSocket和Socket的生命周期,ServerSocket的生命周期伴随整个服务器进程,而Socket的生命周期只是一次连接周期。

2 模拟实现TCP服务器

public class TcpServer {//服务器端口号private final int PORT = 8000;//创建服务器private ServerSocket serverSocket = null;public TcpServer() throws IOException {serverSocket = new ServerSocket(PORT);}//启动服务器public void start() throws IOException {System.out.println("服务器启动成功");ExecutorService executorService = Executors.newCachedThreadPool();while(true){//将建立的TCP连接拿到应用程序中(accept()会阻塞,直到建立连接)Socket clientSocket = serverSocket.accept();//[版本1]直接调用processConnect()就会导致第一个客户端连接执行到该方法while中,服务器线程从而无法执行accept//进而无法一个服务器为多个客户端服务//[版本2]解决方案:多线程(一个线程accept(),一个线程processConnect())(新的问题:频繁创建销毁线程)//            Thread t = new Thread(() ->{//                try {//                    processConnect(clientSocket);//                } catch (IOException e) {//                    e.printStackTrace();//                }//            });//            t.start();//[版本3]解决方案:线程池(新的问题:线程数量太多了(IO多路复用->NIO))executorService.submit(new Runnable() {@Overridepublic void run() {try {processConnect(clientSocket);} catch (IOException e) {e.printStackTrace();}}});}}//给当前连接的客户端提供服务(一个连接只进行一次数据交互服务(短连接)||一个连接进行多次数据交互服务(长连接))//长连接版本(去掉循环就是短连接版本)public void processConnect(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){Scanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while(true){if(!scanner.hasNext()){//如果没有请求说明客户端断开连接System.out.printf("[%s:%d] 断开连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());break;}//1.读取请求并解析String request = scanner.next();//2.根据请求计算响应String response = process(request);//3.响应写回客户端// (注意此处不能使用next()类的函数,因为这类函数读取结束标志是空白符:换行符、回车符等,输入没有这些符号服务器就会被阻塞在这类函数)printWriter.println(response);//刷新一下缓冲区printWriter.flush();System.out.printf("[%s:%d] request:%s, response:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);}}finally {//连接用完需要关闭(clientSocket生命周期是一次连接周期,而serverSocket生命周期是整个服务器运行周期)clientSocket.close();}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpServer tcpServer = new TcpServer();tcpServer.start();}}public class TcpClient {//创建客户端private Socket socket = null;public TcpClient() throws IOException {//new对象时就是和TCP服务器建立连接(因此需要直到服务器地址)socket = new Socket("127.0.0.1",8000);}//启动服务器public void start() throws IOException {Scanner scanner = new Scanner(System.in);try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){Scanner scannerNet = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while(true){//1.读取用户输入System.out.print(">");//注意此时next()读取到换行就结束了,但是读取的数据不含空白符,即没有回车符String request = scanner.next();//2.发送请求// (注意此处不能使用next()类的函数,因为这类函数读取结束标志是空白符:换行符、回车符等,输入没有这些符号服务器就会被阻塞在这类函数)printWriter.println(request);printWriter.flush();//3.接收响应String response = scannerNet.next();//4.将响应返回给用户System.out.printf("request:%s, response:%s\n",request,response);}}}public static void main(String[] args) throws IOException {TcpClient tcpClient= new TcpClient();tcpClient.start();}}

运行结果如下:

        上述代码需要注意3点:

        1.服务器端什么时候该关闭clientSocket(即关闭连接)?当服务器端processConnect方法内部从循环跳出时,证明此时客户端没有数据要发送,此时可以关闭连接,采用try-catch-finally方式,防止出现异常无法正常关闭。

        2.如何处理next()引起的阻塞问题?上述代码很多地方可能要用到Scanner的next()方法,但是该方法会读取到空白符(回车换行等)才能结束,当客户端输入数据时可能不会携带空白符(在命令行中敲回车,该回车会被接收数据的next识别,发送的请求中并不携带回车符),此时就会导致服务器端一直未识别到结束,从而一直无响应。解决的办法就是在发送的数据中添加空白符,比如使用println()方法会自动在数据结尾添加回车符。

        3.如何解决服务器端只能为一个客户端服务?当不采用多线程方案时,第一个客户端建立连接发送请求,进入processConnect方法内部时,服务器端的主线程就会进入while中,从而其他客户端申请建立连接时,服务器主线程无法通过accept()监听建立连接的申请。采用多线程方案,线程池实现一个线程为一个客户端服务(注意,当并发量很大时,线程池的线程数量很多,就会导致资源浪费调度困难等问题,此时需要采用NIO(非阻塞IO)的方式,这是一种I/O多路复用的技术,可以实现一个线程管理多个客户端)。

下篇文章:

网络编程—TCP/IP模型(UDP协议与自定义协议)https://blog.csdn.net/sniper_fandc/article/details/146923934?fromshare=blogdetail&sharetype=blogdetail&sharerId=146923934&sharerefer=PC&sharesource=sniper_fandc&sharefrom=from_link

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

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

相关文章

SkyWalking+Springboot实战(最详细)

本篇文章记录了作者在0到1学习SkyWalking的过程,记录了对SkyWalking的部署,学习,使用Bug解决等等过程 一、什么是SkyWalking 官方文档: Apache SkyWalkinghttps://skywalking.apache.org/ SkyWalking 是一个开源的分布式追踪、性…

Arduino示例代码讲解:Row-Column Scanning an 8x8 LED matrix with X-Y input LED矩阵

Arduino示例代码讲解:Row-Column Scanning an 8x8 LED matrix with X-Y input LED矩阵 Row-Column Scanning an 8x8 LED matrix with X-Y input LED矩阵功能概述硬件部分:软件部分:代码逐行解释定义常量定义变量`setup()` 函数`loop()` 函数`readSensors()` 函数`refreshScr…

多线程编程中的锁策略

目录 1.悲观锁vs乐观锁 关键总结 悲观锁: 乐观锁: 选择建议 用 悲观锁 当: 用 乐观锁 当: 2.重量级锁vs轻量级锁 选择建议 用 轻量级锁: 用 重量级锁: 3.挂起等待锁vs自旋锁 关键细节说明 选择…

负载均衡是什么,Kubernetes如何自动实现负载均衡

负载均衡是什么? 负载均衡(Load Balancing) 是一种网络技术,用于将网络流量(如 HTTP 请求、TCP 连接等)分发到多个服务器或服务实例上,以避免单个服务器过载,提高系统的可用性、可扩…

React-01React创建第一个项目(npm install -g create-react-app)

1. React特点 JSX是javaScript语法的扩展,React开发不一定使用JSX。单向响应的数据流,React实现单向数据流,减少重复代码,比传统数据绑定更简单。等等 JSX是js的语法扩展,允许在js中编写类似HTML的代码 const …

小程序中的网络请求

在小程序中,使用 wx.request( ) 这个方法来发送网路请求,整个请求的方式和 jQuery 里面的 $.ajax 方法是非常相似的。 在 wx.request( ) 这个方法中,接收一个配置对象,该配置对象中能够配置的项目如下表: 关于服务器…

jvm 的attach 和agent机制

Java 的 Attach 和 Agent 机制在实际应用中得到了广泛的成功应用,尤其是在监控、调试、性能分析、故障排查等方面。以下是这两种机制在实际场景中的一些成功应用案例: 1. 性能监控与分析 Java Agent 和 Attach 机制广泛应用于性能监控和分析&#xff0…

基于SpringBoot的“留守儿童网站”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“留守儿童网站”的设计与实现(源码数据库文档PPT) 开发语言:Java 数据库:MySQL 技术:SpringBoot 工具:IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 局部E-R图 系统首页界面 系统注册…

iPhone XR:一代神机,止步于此

什么样的 iPhone ,才配称为一代神机? 我曾经用过iPhone 4S、iPhone 6S Plus、iPhone 8 Plus,iPhone SE2、iPhone XR、iPhone 13、iPhone 14 Plus、iPhone 15/Pro。 不管硬件再怎么卷,不管囊中是否羞涩,主力机基本没考…

【VUE】RuoYi-Vue3项目结构的分析

【VUE】RuoYi-Vue3项目结构的分析 1. 项目地址2. RuoYi-Vue3项目结构2.1 整体结构2.2 package.json2.2.1 🧾 基本信息2.2.2 🔧 脚本命令(scripts)2.2.3 🌍 仓库信息2.2.4 📦 项目依赖(dependenc…

架构师面试(二十五):分布式存储 Leader 设计

问题 在非常多的分布式存储系统中,如:Zookeeper、Etcd、Kafka等,往往会存在一个 【Leader】 角色,并由该角色负责数据的写入,这样设计最主要的原因是什么呢? A. 唯一负责数据写入的 Leader 角色可以避免并…

使用YoloV5和Mediapipe实现——上课玩手机检测(附完整源码)

目录 效果展示 应用场景举例 1. 课堂或考试监控(看到这个学生党还会爱我吗) 2. 驾驶安全监控(防止开车玩手机) 3. 企业办公管理(防止工作时间玩手机) 4. 监狱、戒毒所、特殊场所安保 5. 家长监管&am…

GPT-4o从语义分割到深度图生成,大模型狂潮下的计算机视觉:技术进步≠替代危机

随着上周,GPT-4o原生多模态图像生成功能的推出,更多玩法也被开发出来。一夜之间,GPT-4o原生多模态能力的释放,让图像生成、语义分割、深度图构建这些曾需要专业工具链支持的复杂任务,变成了普通人输入一句话就能实现的…

Pytorch 张量操作

在深度学习中,数据的表示和处理是至关重要的。PyTorch 作为一个强大的深度学习框架,其核心数据结构是张量(Tensor)。张量是一个多维数组,类似于 NumPy 的数组,但具有更强大的功能,尤其是在 GPU …

小程序中跨页面组件共享数据的实现方法与对比

小程序中跨页面/组件共享数据的实现方法与对比 在小程序开发中,实现不同页面或组件之间的数据共享是常见需求。以下是几种主要实现方式的详细总结与对比分析: 一、常用数据共享方法 全局变量(getApp())、本地缓存(w…

vue中的 拖拽

拖拽总结 实现方式特点适用场景HTML5 原生拖拽 API✅ 直接使用 dataTransfer 进行数据传输 ✅ 兼容性好(大部分浏览器支持) ✅ 适合简单的拖拽场景低代码平台、表单生成器、组件拖拽Vue/React 组件库(如 Vue Draggable、SortableJS&#xff…

MySQL 函数(入门版)

目录 一、字符串函数 1、常用的字符串函数 2、函数演示 3、具体案例 二、数值函数 1、常用的数值函数 2、函数演示 3、具体案例 三、日期函数 1、常用的日期函数 2、函数演示 3、具体案例 四、流程函数 1、常用的流程函数 2、函数演示 3、具体案例 在MySQL中&a…

基于快速开发平台与智能手表的区域心电监测与AI预警系统(源码+论文+部署讲解等)

需要源代码,演示视频,ppt设计原稿资料,请文末卡片联系 !](https://i-blog.csdnimg.cn/direct/242d53cd069940b5b7a6db2bb031d406.png#pic_center)

【神经网络】python实现神经网络(三)——正向学习的模拟演练

有了之前的经验(【神经网络】python实现神经网络(二)——正向推理的模拟演练),我们继续来介绍如何正向训练神经网络中的超参(包含权重以及偏置),本章大致的流程图如下: 一.损失函数 神经网络以某个指标为基准寻求最优权重参数,而这个指标即可称之为 “损失函数” 。(…

分区格式变RAW故障深度解析与数据恢复实战指南‌

分区格式变RAW的本质‌ 当存储设备(如硬盘、U盘或移动硬盘)的分区突然显示为RAW格式时,意味着操作系统无法识别其原有的文件系统结构(如NTFS、FAT32等)。此时,用户访问该分区会提示“需要格式化”或直接显示…