【网络编程】TCP数据流套接字编程

目录

一. TCP API 

二. TCP回显服务器-客户端

1. 服务器

2. 客户端

3. 服务端-客户端工作流程

4. 服务器优化


TCP数据流套接字编程是一种基于有连接协议的网络通信方式


一. TCP API 

在TCP编程中,主要使用两个核心类ServerSocket Socket


ServerSocket

ServerSocket类只有服务器会使用,用于接受连接

ServerSocket构造方法:

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

 ServerSocket方法:

方法签名方法说明
Socket accept()

监听端口,如果有客户端连接后,则会返回一个服务端 Socket 对象

如果没有客户端连接,则会进入阻塞等待

void close()关闭此套接字
  •  accept()方法用于接收客户端连接请求并建立正式通信通道
  •  accept()方法是接受连接并返回Socket,真正和客户端进行交互的是Socket

 Socket

 Socket类负责具体的数据传输 

  • 客户端一开始就使用Socket进行通信(请求由客户端发起)
  • 服务器在接受客户端建立请求后,返回服务端Socket
  • 在双方建立连接之后,都会使用Socket进行通信 

Socket 构造方法:

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

 Socket 方法:

方法签名方法说明
InetAddress getInetAddress()返回的地址(IP和端口)
InputStream getInputStream()返回输入流
OutputStream getOutputStream()返回输出流
  • TCP面向字节流,基本传输单位是字节

二. TCP回显服务器-客户端

回显服务器

回显服务器:不进行任何的业务逻辑,只是将收到的数据显示出来


1. 服务器

  接收连接请求 

  • TCP是有连接的可靠通信
  • 真正建立连接的过程在内核中被实现,应用层只是调用相应API同意建立连接
  • 类比打电话,客户端拨号,服务器这边在响铃,通过调用accept接听

 代码实现:

            Socket clientSocket = serverSocket.accept();
  • accept()方法具有阻塞功能
  • accept()方法一次只能返回一个Socket对象,接收一次请求
  • 如果没有客户端发起连接请求,则会进入阻塞等待
  • 如果有一个客户端发起连接请求,则执行一次,如果有多个客户端发起连接请求,则执行多次

处理请求

 private void processConnection(Socket clientSocket) {}
  •  使用方法专门处理一次连接,在一次连接中可能会涉及多次请求响应交互

如何处理请求和返回响应? 

由于TCP面向字节流,我们可以字节流传输的类 InputStream和OutputStream

   try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){} catch (IOException e) {throw new RuntimeException(e);}
  •  使用try-with-resources管理InputStream和OutputStream,确保流自动关闭。 
  • InputStream从网卡中读取数据
  • OutputStream从网卡中写入数据

1)接收请求并解析

        Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){System.out.printf("[客户端ip:%s,端口号:%d],客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}String request = scanner.next();
  • 客户端和服务器双方都有自己的缓冲区
  • 客户端发送数据,会先将数据放入服务器缓冲区中
  • 如果服务器缓冲区中没有数据,hasNext()则会陷入阻塞等待中
  • 如果客户端退出,则会触发四次挥手断开连接,服务器会感知到,就会在hasNext()返回false。

2)根据请求计算响应

 回显服务器:不会处理数据,输入什么就会返回什么

            String response = process(request);

使用process方法来实现回显功能

    public  String process(String request) {return request;}

 如果想要实现特定的功能,直接在process中实现即可 

3)返回响应

 Scanner的写操作无法自己完成,只能进行读取操作,写操作需要依靠其他的类(PrintWriter) 

                PrintWriter printWriter = new PrintWriter(outputStream);
//                将数据写入数据的缓冲区中printWriter.println(respond);

冲刷缓冲区

 由于缓冲区的特殊机制,缓冲区只有满的时候,才会被发送出去

                printWriter.flush();
  • 我们这里要保证实时性,客户端每发送一次请求,服务器都要第一时间响应
  • IO操作比较低效,如果每进行一次IO,就要冲刷一次,效率很低,为了让这种低效的操作少一点,等缓冲区满了,才会冲刷

服务器总代码:

import java.io.*;
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;TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动");while(true){//从缓冲区内取出并同意链接//将取出的数据使用clientSocket另外保存起来,//每有一个客户端,就会出现一个clientSocket对象,所有使用完,必须关闭Socket clientSocket = serverSocket.accept();//进行数据分析/*            Thread t = new Thread(()->{processConnection(clientSocket);});t.start();*/
//            这样写开销大,会有很多次的创建和销毁,改进使用线程池ExecutorService service = Executors.newFixedThreadPool(3);service.submit(()->{processConnection(clientSocket);});}}//使用这个方法专门处理一次连接,在一次连接中可能会涉及多次请求交互private void processConnection(Socket clientSocket) {System.out.printf("[客户端ip:%s,端口号:%d],客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());//循环处理请求并返回响应(请求可能不止一次)//从网卡中读数据和写数据try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){while (true){
//                byte[] buffer = new byte[1024];
//                int n = inputStream.read(buffer);
//                //将字节数组转换为字符串
//                if(n==-1){
//                    System.out.printf("[客户端ip:%s,端口号:%d],客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());
//                    break;
//                }
//                String request = new String(buffer,0,n);Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){System.out.printf("[客户端ip:%s,端口号:%d],客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}
//                1.接受请求并解析//客户端必须有一个空格或者换行符String request = scanner.next();
//                2.根据请求计算响应String respond = process(request);
//                3.返回响应//返回的是字节数组类型
//                outputStream.write(request.getBytes(),0,request.getBytes().length);//返回字符串类型(各种类型)PrintWriter printWriter = new PrintWriter(outputStream);
//                将数据写入数据的缓冲区中printWriter.println(respond);//冲刷缓冲区printWriter.flush();//打印日志System.out.printf("[客户端ip:%s,端口号:%d],req:%s,resp:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,respond);}} catch (IOException e) {throw new RuntimeException(e);}finally {try {//必须进行close,clientSocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

注意: 

  • 在服务器中,ServerSocket对象不需要被消耗,整个程序中只有一个ServerSocket对象,它的生命周期要伴随整个程序,不能提前关闭,只有程序退出了,才会被释放
  • 方法中的Socket必须要释放,每出现一个客户端,就会随之出现一个Socket对象,如果不释放,Socket对象会越来越多,将文件描述符表占满(内存泄露问题)

2. 客户端

 构造方法

    TcpEchoClient(String serverIp,int serverPort) throws IOException {socket = new Socket(serverIp,serverPort);}
  • 这里不需要将serverIP和serverPort在类中保存
  • 因为tcp有链接,socket会保存好这两个值

客户端如何发送请求和接收响应?

客户端同样使用字节流进行传输

        try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){} catch (IOException e) {throw new RuntimeException(e);}
  •  使用try-with-resources管理InputStream和OutputStream,确保流自动关闭。 
  • InputStream从网卡中读取数据
  • OutputStream从网卡中写入数据

1)从控制台读取请求

            //客户端输入的数据Scanner scannerConsole = new Scanner(System.in);while(true){System.out.print("->");//客户端没有输入if(!scannerConsole.hasNext()){break;}
//              从控制台读取请求String request = scannerConsole.next();}
  • 使用Scanner进行输入,如果没有输入数据,hasNext()会进入阻塞等待

2)将请求发送给服务器

 Scanner只会读取数据,发送使用类PrintWriter

          PrintWriter writer = new PrintWriter(outputStream);writer.println(request);               
  •  向服务器发送数据

冲刷缓冲区 

                //冲刷缓冲区writer.flush();

将发送的数据,先放入缓冲区中,等待缓冲区满了,才会发送缓冲区中的内容 


3)接收服务器返回的响应

            Scanner scannerNetwork = new Scanner(inputStream);String respond = scannerNetwork.next();
  • 服务器发送的数据,先到达客户端的缓冲区,客户端要从缓冲区读出数据
  • 这里使用Scanner进行读出数据,也可以使用read()方法读取 

4)将响应数据显示在控制台

                System.out.println(respond);

将接收到的字符串响应,直接打印出来即可 


客户端总代码:


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;TcpEchoClient(String serverIp,int serverPort) throws IOException {
//        这里不需要将serverIP和serverPort在类中保存
//        因为tcp有链接,socket会保存好这两个值socket = new Socket(serverIp,serverPort);}public void start(){System.out.println("客户端启动");try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){//客户端输入的数据Scanner scannerConsole = new Scanner(System.in);//通过网络读取Scanner scannerNetwork = new Scanner(inputStream);//像服务器发送请求PrintWriter writer = new PrintWriter(outputStream);while(true){System.out.print("->");//客户端没有输入if(!scannerConsole.hasNext()){break;}
//                1. 从控制台读取请求String request = scannerConsole.next();
//                2.将请求发送给服务端writer.println(request);//冲刷缓冲区writer.flush();
//                3.接受服务端返回的响应//从数据缓冲区中读取出内容String respond = scannerNetwork.next();
//                4.将响应显示在控制台System.out.println(respond);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);client.start();}
}

3. 服务端-客户端工作流程

 无论是TCP还是UDP都是服务端先启动 

创建连接过程

  1. 服务器启动,由于没有客户端建立连接,accept()进入阻塞,等待客户端创建连接
  2. 客户端启动,客户端申请和服务器建立连接
  3. 服务器从accept()阻塞中返回,调用processConnection()方法进行交互

 双方交互过程

  1. 服务器进入processConnection()方法,执行到hasNext(),由于客户端没有发送数据,服务器读取不到数据,进入阻塞状态
  2. 客户端在hasNext()这里进入阻塞,等待用户在控制台中输入数据
  3. 用户输入数据,客户端从hasNext()中退出阻塞,将数据发送给服务器,next()阻塞等待服务器返回数据
  4. 服务器从hasNext()阻塞中返回,读取请求并处理,构造响应,发送给客户端
  5. 客户端读取响应并打印

4. 服务器优化

            Thread t = new Thread(()->{processConnection(clientSocket);});t.start();
  • 每来一个客户端,服务器就需要创建出一个新的线程
  • 每次客户端结束,服务器就需要销毁这个线程

如果客户端比较多,那么服务器就需要频繁的创建和销毁 ,开销大


 (1)可以通过引入线程池来避免频繁的创建和销毁

            ExecutorService service = Executors.newFixedThreadPool(3);service.submit(()->{processConnection(clientSocket);});

 如果有的客户端处理的过程很短(网站),也有可能客户端处理的时间会很长

处理时间很短的客户端,分配一个专门的线程,有点浪费,所有引入了IO多路复用技术


(2)IO多路复用技术

IO多路复用技术是操作系统提供的机制。 

让一个线程去同时去负责处理多个Socket对象

本质在于这些Socket对象不是同一时刻都需要处理 

虽然有多个Socket对象,但是同一时间活跃的Socket对象只是少数(大部分的Socket对象都是在等数据),我们可以在等的过程中,去处理活跃的Socket对象 


点赞的宝子今晚自动触发「躺赢锦鲤」buff! 

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

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

相关文章

力扣刷题Day 21:两数之和(1)

1.题目描述 2.思路 暴力解法虽然不超时间限制,但是题解实在太妙了,哈希大法好! 3.代码(Python3) class Solution:def twoSum(self, nums: List[int], target: int) -> List[int]:hash_table dict()for i, num i…

关于UE5的抗锯齿和TAA

关于闪烁和不稳定现象的详细解释 当您关闭抗锯齿技术时,场景中会出现严重的闪烁和不稳定现象,尤其在有细节纹理和小物体的场景中。这种现象的技术原因如下: 像素采样问题 在3D渲染中,每个像素只能表示一个颜色值,但…

【MySQL】MySQL建立索引不知道注意什么?

基本原则: 1.选择性原则: 选择高选择性的列建立索引(该列有大量不同的值) 2.适度原则:不是越多越好,每个索引都会增加写入开销 列选择注意事项: 1.常用查询条件列:WHERE字句中频繁使用的列 2.连接操作列…

Vue3 + TypeScript中provide和inject的用法示例

基础写法&#xff08;类型安全&#xff09; typescript // parent.component.vue import { provide, ref } from vue import type { InjectionKey } from vue// 1. 定义类型化的 InjectionKey const COUNTER_KEY Symbol() as InjectionKey<number> const USER_KEY Sy…

树莓派超全系列教程文档--(33)树莓派启动选项

树莓派启动选项 启动选项start_file &#xff0c;fixup_filecmdlinekernelarm_64bitramfsfileramfsaddrinitramfsauto_initramfsdisable_poe_fandisable_splashenable_uartforce_eeprom_reados_prefixotg_mode &#xff08;仅限Raspberry Pi 4&#xff09;overlay_prefix配置属…

java怎么找bug?Arthas原理与实战指南

Arthas原理与实战指南 1. Arthas简介 Arthas是阿里巴巴开源的Java诊断工具&#xff0c;其名字取自《魔兽世界》的人物阿尔萨斯。它面向线上问题定位&#xff0c;被广泛应用于性能分析、定位问题、安全审计等场景。Arthas的核心价值在于它能够在不修改应用代码、不重启Java进程…

Python自学第1天:变量,打印,类型转化

突然想学Python了。经过Deepseek的推荐&#xff0c;下载了一个Python3.12安装。安装过程请自行搜索。 乖乖从最基础的学起来&#xff0c;废话不说了&#xff0c;上链接&#xff0c;呃&#xff0c;打错了&#xff0c;上知识点。 变量的定义 # 定义一个整数类型的变量 age 10#…

基于STM32中断讲解

基于STM32中断讲解 一、NVIC讲解 简介&#xff1a;当一个中断请求到达时&#xff0c;NVIC会确定其优先级并决定是否应该中断当前执行的程序&#xff0c;以便及时响应和处理该中断请求。这种设计有助于提高系统的响应速度和可靠性&#xff0c;特别是在需要处理大量中断请求的实…

游戏盾和高防ip有什么区别

游戏盾和高防IP都是针对网络攻击的防护方案&#xff0c;但​​核心目标、技术侧重点和应用场景存在显著差异​​。以下是两者的详细对比分析&#xff1a; ​​一、核心定位与目标​​ ​​维度​​​​高防IP​​​​游戏盾​​​​核心目标​​抵御大流量网络攻击&#xff08…

Spark-SQL3

Spark-SQL 一.Spark-SQL核心编程&#xff08;四&#xff09; 1.数据加载与保存&#xff1a; 1&#xff09;通用方式&#xff1a; SparkSQL 提供了通用的保存数据和数据加载的方式。这里的通用指的是使用相同的API&#xff0c;根据不同的参数读取和保存不同格式的数据&#…

DeepSeek与Napkin:信息可视化领域的创新利器

摘要 在数字化信息爆炸的时代&#xff0c;如何高效地组织思路并将其转化为直观、清晰的可视化图表&#xff0c;成为众多领域面临的关键问题。本文深入剖析了DeepSeek与Napkin这两款工具&#xff0c;详细探讨它们在信息处理与可视化过程中的功能特性、协同工作机制、应用场景、…

conda 创建、激活、退出、删除环境命令

参考博客&#xff1a;Anaconda创建环境、删除环境、激活环境、退出环境 使用起来觉得有些不方便可以改进&#xff0c;故写此文。 1. 创建环境 使用 -y 跳过确认 conda create -n 你的环境名 -y 也可以直接选择特定版本 python 安装&#xff0c;以 3.10 为例&#xff1a; co…

嵌入式芯片中的 低功耗模式 内容细讲

电源域与运行级别概述 电源域&#xff08;Power Domain&#xff09; 核心域&#xff08;Core Domain&#xff09;&#xff1a;包括 CPU 核心和关键架构模块&#xff08;如 NVIC、CPU 内核寄存器&#xff09;。 外设域&#xff08;Peripheral Domain&#xff09;&#xff1a;…

Java中常见的锁synchronized、ReentrantLock、ReentrantReadWriteLock、StampedLock

在Java中&#xff0c;锁是实现多线程同步的核心机制。不同的锁适用于不同的场景&#xff0c;理解其实现原理和使用方法对优化性能和避免并发问题至关重要。 一、隐式锁&#xff1a;synchronized 关键字 实现原理 基于对象监视器&#xff08;Monitor&#xff09;&#xff1a;每…

@JsonView + 单一 DTO:如何实现多场景 JSON 字段动态渲染

JsonView 单一 DTO&#xff1a;如何实现多场景 JSON 字段动态渲染 JsonView 单一 DTO&#xff1a;如何实现多场景 JSON 字段动态渲染1、JsonView 注解产生的背景2、为了满足不同场景下返回对应的属性的做法有哪些&#xff1f;2.1 最快速的实现则是针对不同场景新建不同的 DTO…

Etcd 压缩整理

etcd数据存储 在实际生产中使用 ETCD 存储元数据&#xff0c;起初集群规模不大的时候元数据信息不多没有发现什么问题。随着集群规模越来越大&#xff0c;可能引发存储问题。 —auto-compaction-retention 由于ETCD数据存储多版本数据&#xff0c;随着写入的主键增加历史版本需…

【更新完毕】2025妈妈杯C题 mathercup数学建模挑战赛C题数学建模思路代码文章教学:音频文件的高质量读写与去噪优化

完整内容请看文章最下面的推广群 我将先给出文章、代码、结果的完整展示, 再给出四个问题详细的模型 面向音频质量优化与存储效率提升的自适应编码与去噪模型研究 摘 要 随着数字媒体技术的迅速发展&#xff0c;音频处理技术在信息时代的应用愈加广泛&#xff0c;特别是在存储…

React-请勿在循环或者条件语句中使用hooks

这是React Hooks的首要规则&#xff0c;这是因为React Hooks 是以单向循环链表的形式存储&#xff0c;即是有序的。循环是为了从最后一个节点移到一个节点的时候&#xff0c;只需通过next一步就可以拿到第一个节点&#xff0c;而不需要一层层回溯。React Hooks的执行&#xff0…

【大模型】 LangChain框架 -LangChain实现问答系统

LangChain 介绍与使用方法 1. 什么是 LangChain&#xff1f;2. LangChain 的主要功能3. 如何使用 LangChain&#xff1f;3.1 环境准备3.2 基本使用示例3.2.1 简单的问答系统3.2.2 结合外部工具 3.3 高级用法 4. 常见问题及解决方法4.1 安装问题4.2 运行问题4.3 性能问题 5. 实战…

企业级HAProxy高可用离线部署实战(附Kubernetes APIServer负载均衡配置)

企业级HAProxy高可用离线部署实战&#xff08;附Kubernetes APIServer负载均衡配置&#xff09; 摘要&#xff1a;本文深入讲解在离线环境下部署HAProxy 3.1.1的全流程&#xff0c;涵盖源码编译、系统服务封装、K8S APIServer四层负载配置等核心环节&#xff0c;并提供生产级高…