javaTCP数据报套接字编程

news2024/9/20 22:01:54

TCP套接字编程

  • 1.ServerSocket API
    • 1.1ServerSocket 的构造方法
    • 1.2ServerSocket 方法:
  • 2.Socket API
    • 2.1Socket构造方法
    • 2.2Socket方法
  • 3.TCP回显服务器
  • 4.TCP中的长短连接
  • 5.C10M问题

TCP提供的API主要有两个类:一个是专门给服务器用的SeverSocket对象,一个是既可以给服务器端用也可以给客户端用的Socket对象。

1.ServerSocket API

1.1ServerSocket 的构造方法

在这里插入图片描述

1.2ServerSocket 方法:

在这里插入图片描述

2.Socket API

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

2.1Socket构造方法

在这里插入图片描述

2.2Socket方法

在这里插入图片描述

3.TCP回显服务器

服务器端

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;

public class TCPEchoSever {

    private ServerSocket serverSocket = null;

    public TCPEchoSever(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        //socket对象用于与具体的客户端进行数据交流
        //severSocket只用于客户端与服务器端的连接
        System.out.println("服务器启动!");
        while(true){
            Socket socket = serverSocket.accept();
            processConection(socket);
        }
    }

    //使用这个方法来处理一个连接
    //一个连接对应一个客户端,但是这里可能涉及到多次交互
    public void processConection (Socket socket) throws IOException {
        System.out.printf("[%s,%d]客户端上线!\n", socket.getInetAddress().toString(),socket.getPort());
        //基于上述socket对象与客户端进行通信
        try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream  = socket.getOutputStream()) {
            while(true){
                //构建从缓冲区读取数据对象scanner
                Scanner scanner  = new Scanner(inputStream);
                //判断缓冲区里面有么有数据了,如果么有了就代表客户端下线
                if(!scanner.hasNext()){
                    System.out.printf("[%s,%d]客户端下线!\n",socket.getInetAddress().toString(),socket.getPort());
                    break;
                }
                //注意:此时使用的next是一直读取到换行符、空格、其他空白符结束,但是最终返回结果里不包含空白符
                //1.读取请求
                String request = scanner.next();
                //2.根据请求构建响应
                String respose = process(request);
                //3.将响应写入输出的缓冲区
                //构建一个打印流
                PrintWriter printWriter = new PrintWriter(outputStream);
                //将响应写入打印流
                printWriter.println(respose);
                printWriter.flush();
                System.out.printf("[%s,%d],req:%s,resp:%s\n",socket.getInetAddress().toString(),socket.getPort(),request,respose);
                            }
        }
        finally {
            socket.close();//socket本身也是一个文件,也要占用文件描述表里面的一个位置,用完也是需要关闭的。前面的实例中都没有这个要求,那是因为UDP的socket声明周期是整个
            //通信过程,所以没有关闭,而在tcp中,socket只用于与客户端的数据通信,不涉及连接,连接交给了severSocket
        }
    }
    public String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        TCPEchoSever tcpEchosever = new TCPEchoSever(9090);
        tcpEchosever.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 {

    //一定主要客户端的socket和服务器的socket不是一个对象,而是两个,一个用于服务器一个用于客户端,类似于打电话的两个电话,但是两个socket是有关联的
    //当客户端的socke创建的时候(被new的时候)服务器端的socket就会通过serverSocket.accept()的返回值创建出一个相关联的socket
    private Socket socket = null;

    public TCPEchoClient( String severIp, int port) throws IOException {
        //new这个对象的同时就会进行TCP的连接操作(四次握手)
        this.socket = new Socket(severIp,port);
    }

    public void start(){
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
                //1.先从键盘上读入用户输入的内容
                System.out.println("请输入信息:");
                String request = scanner.next();
                if (request.equals("exit")) {
                    System.out.println("请求结束!");
                    return;
                }
                //2.把读到的内容构造成请求(字符型的打印流),发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                //注意这不是打印,而是向printWriter里面写入的一个带换行符一串字符request
                printWriter.println(request);
                //在冲刷一下保证全部写入
                printWriter.flush();


                //3.读取服务器的响应
                Scanner responseScanner = new Scanner(inputStream);
                String response = responseScanner.next();

                //4.打印响应
                System.out.println(response);
                            }
        }catch (IOException e) {
                e.printStackTrace();
            }
    }

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

结果
在这里插入图片描述

在这里插入图片描述

  1. severSocket.accept();效果是接收连接,前提是。得有客户端来建立连接,客户端在构造Socket对象的时候,就会指定服务器的ip和端口号,但是如果么有客户端来连接,那么accept()就会阻塞。severSocket.accept();返回值是一个Socket对象。
  2. outputStream相当于对应着一个文件描述符(sockte文件的文件描述符),我们通过outputStream就可以往这个文件描述符里面写数据了。但是因为outputStream本身的方法不方便写字符串,所以我们就将这个流转换了一下,用一个PrintWriter对象来表示(对于的文件描述符还是同一个);这样我们可以使用PrintWriter的prinln方法往文件里面写入字符串了。PrintWriter写 和OutputStream写是往同一个地方写,只不过写的更方便了。
  3. printWriter.println(request);这一句中将println换成print可不可以呢?答案是不可以的。TCP是面向字节流的协议(字节流的特征就在于读写以字节为单位,一次读多少个字节,都行),那么此时接收方如何知道,这一次一共要读多少字节呢?,这里我们就采用是println在输入的字符串后面加上\n来作为当前代码请求或者响应的分割。此处还有一个疑问在于输入的时候不也按下了换行符(按下了回城键)吗?String request = scanner.next();但是很遗憾的是scanner.next()这个方法是读取到换行符结束(但是读取的字符串里不包含换行符)。
  4. 此外再提一点,这个情况在C里面会导致下一次scanner的时候,会直接读取到\n而直接结束读取,但是这种情况实际在java里不会出现,JVM中,这个\n也会被读取走,但是不作为返回值。

当前的代码还有一个大问题,那就是当前的服务器是指只能服务一个客户端程序的,如果启动第二个实例,实际第二个实例是没有办法正常工作的

第一个客户端正常工作
在这里插入图片描述第二个客户端没有收到服务器的响应
在这里插入图片描述
服务器端也是么有响应第二个客户端的在这里插入图片描述此时如果我们将第一个客户端关闭,那么第二个客户端就会工作就会正常得到响应;
第一个客户端
在这里插入图片描述
第二个客户端
在这里插入图片描述

服务器端
在这里插入图片描述

那么是什么原因呢?
是这样的,我们的服务器代码里实际是两层循环

while(true){
            Socket socket = serverSocket.accept();
            processConection(socket);
        }
public void processConection (Socket socket) throws IOException {
        System.out.printf("[%s,%d]客户端上线!\n", socket.getInetAddress().toString(),socket.getPort());
        //基于上述socket对象与客户端进行通信
        try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream  = socket.getOutputStream()) {
            while(true){

                //构建从缓冲区读取数据对象scanner
                Scanner scanner  = new Scanner(inputStream);
                //判断缓冲区里面有么有数据了,如果么有了就代表客户端下线
                if(!scanner.hasNext()){
                    System.out.printf("[%s,%d]客户端下线!\n",socket.getInetAddress().toString(),socket.getPort());
                    break;
                }
                //注意:此时使用的next是一直读取到换行符、空格、其他空白符结束,但是最终返回结果里不包含空白符
                //1.读取请求
                String request = scanner.next();
                //2.根据请求构建响应
                String respose = process(request);
                //3.将响应写入输出的缓冲区
                //构建一个打印流
                PrintWriter printWriter = new PrintWriter(outputStream);
                //将响应写入打印流
                printWriter.println(respose);
                printWriter.flush();
                System.out.printf("[%s,%d],req:%s,resp:%s\n",socket.getInetAddress().toString(),socket.getPort(),request,respose);
        }
        }catch (IOException e){
            e.printStackTrace();
        }
        finally {
            socket.close();//socket本身也是一个文件,也要占用文件描述表里面的一个位置,用完也是需要关闭的。前面的实例中都没有这个要求,那是因为UDP的socket声明周期是整个
            //通信过程,所以没有关闭,而在tcp中,socket只用于与客户端的数据通信,不涉及连接,连接交给了severSocket
        }
    }

所以就如果说第一个客户端与服务端连接了,那么就执行了Socket socket = serverSocket.accept(); processConection(socket);这两句,而且如果第一个客户端只要不输入exit,那么代码会一直卡在processConection里面while循环里,此时第二个客户端来,服务器端的代码仍在while循环里,没有办法重新执行accept,所以也就没有办法处理第二个客户端的请求。
那么我们如何来解决呢?可以采用线程的思想,我们可以用主线程专门负责accept,每次accept都去创建一个新的线程去处理新的请求。每个线程是一个独立的执行流,彼此之间的并发关系。

所以我们只需要在每次accept之后去创建一个线程,让这个线程去执行procesConnaction就行

 while(true){
            Thread t = new Thread(()->{
                try {
                    Socket socket = serverSocket.accept();
                    processConection(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }

更加好一点的方式是使用线程池的方式


        ExecutorService threadPool = Executors.newCachedThreadPool();//构建一个动态大小的线程池
        while(true){
            Socket socket = serverSocket.accept();
            threadPool.submit(()->{
                try {
                    processConection(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
        

结果

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
注意:

  1. TCP连接有短连接和长连接的区别,短连接是请求后客户端与服务器端只会有一次数据交换,之后就释放连接了。但是长连接是建立连接后客户端和服务器端会进行多次的数据传输后才会释放连接。而在代码里体现的就是是否在processConenaction里是否会有循环。

4.TCP中的长短连接

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

对比以上长短连接,两者区别如下:
建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要 第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时 的,长连接效率更高。
主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送 请求,也可以是服务端主动发。
两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于 客户端与服务端通信频繁的场景,如聊天室,实时游戏等。扩展了解。

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

5.C10M问题

在实际开发中,可能服务器端只有几个这样的数量级,但是客户端的数量是非常庞大的,如果有几万个客户端,我们就要创建几万个线程这样可能机器可能还能承受,但是如果十万级别的线程数,我们除了去增加成本增加服务器数量,还可以用什么办法呢?所谓C10M问题。C10K问题就是如何解决单机处理1w客户端的问题,所以C10M问题是说单机如何处理比一万多很多的线程开销(并不是一定说处理1kw个线程)

要知道,这个主要解决的无非是线程开销问题,那么有没有什么办法可以做到一个线程去处理多个客户端连接呢?——IO多路复用。
那么为什么一个线程可以处理多个请求呢?因为多个请求本身也是交替上处理机 的,并不是一个请求一直占用着处理机。所以我们可以 这样处理,给这个线程安排个集合,这个集合就放一堆连接,这个线程就负责监听这个集合,哪个连接有数据来了。线程就来处理哪个连接。在操作系统里提供里一些原生API,在java里封装了很多NIO。
在这里我们就不过多介绍了。

那么至此,我们TCP的套接字编程就介绍到这里。

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

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

相关文章

MyBatis-Plus框架

说明&#xff1a;Mybaits-Plus是Mybatis框架的升级&#xff0c;该框架提供了一系列API&#xff0c;用于操作数据&#xff0c;可以免受手搓SQL语句的痛苦。 一、使用 第一步&#xff1a;添加依赖 使用前&#xff0c;需先添加对应的依赖&#xff0c;建议使用最新的版本 <d…

有什么事让你觉得在Linux上顺理成章,换到Windows上就令你费解?

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f…

Linux红帽认证适合哪些人学习

近几年来&#xff0c;技术的迭代速度越来越快&#xff0c;云计算&#xff0c;大数据&#xff0c;人工智能&#xff0c;物联网等技术扑面而来&#xff0c;全球各大互联网公司都在抢先发展这些技术&#xff0c;而无一例外的这些技术背后都是使用的Linux系统&#xff0c;双11&…

JAVA_SSM Demo(WEB端)

仓库地址&#xff1a;https://gitee.com/ThMyGitee/SSMDemo.git CSDN的友友们&#xff0c;项目如果适合您的话&#xff0c;麻烦给个小小的Star&#xff0c;谢谢啦&#xff01; JAVA_SSM Demo(WEB端) 1.开发环境 JDK1.8 Tomcat 8.5.60 IDEA 2019.3 MySQL 5.7.20 Maven 3.6 S…

USB协议总结

1、简介 在了解USB协议之前&#xff0c;先了解下该总线协议出现的背景。在USB总线出现之前&#xff0c;计算机与键盘、鼠标、扫描仪、打印机都使用专用的接口连接&#xff0c;不同设备的接口不能互用&#xff0c;扩展性很差。每次插拔设备都要关闭计算机&#xff0c;不支持热插…

【Matlab】智能优化算法_麻雀搜索算法SSA

【Matlab】智能优化算法_麻雀搜索算法SSA 1.背景介绍2.数学模型3.文件结构4.伪代码5.详细代码及注释5.1 Get_Functions_details.m5.2 main.m5.3 SSA.m 6.运行结果7.参考文献 1.背景介绍 麻雀通常是群居的鸟类&#xff0c;有很多种类。它们分布在世界的大部分地区&#xff0c;喜…

TI毫米波雷达软硬件概述

TI毫米波雷达概述 1.TI毫米波雷达传感器芯片及分类&#xff1a;1.1 工业毫米波雷达传感器器件选型应用场景 1.2 汽车毫米波雷达选型使用场景 1.3 基于AWR2243 的 TI 级联雷达 &#xff1a;1.4 不喜欢看文字的也可以戳视频-> 2. TI软件入门概述&#xff1a;2.1 TI RadarToolb…

【C】指针详解(一篇文章带你玩转指针)

指针详解 指针是什么&#xff1f;指针和指针类型指针加减整数指针的解引用 野指针野指针的成因如何规避野指针 指针和数组的关系数组名是什么&#xff1f; 二级指针二级指针是什么&#xff1f;二级指针的运算 字符指针指针数组和数组指针指针数组数组名和&数组名数组指针数…

开源一款轻量线程池项目

基于Arrow的轻量线程池 大家好&#xff0c;我是光城&#xff0c;最近花了几周业余时间&#xff0c;开发出这款轻量线程池&#xff0c;代码也全部开源啦&#xff0c;欢迎大家star。 本线程池的设计与实现会有涉及非常多的知识&#xff0c;这些内容也都会以视频的方式分享在知识星…

分析气象数据:向Python Cartopy地图中添加循环点

大家好&#xff0c;在使用Python和Cartopy对气候数据进行可视化分析的过程中&#xff0c;有一个叫做循环点&#xff08;cyclic point&#xff09;的术语&#xff0c;它在地理空间栅格数据可视化领域中很重要。 1.循环点的定义和作用 有时&#xff0c;当我们试图绘制地理空间数…

性能测试的基本概念和重要指标

这里写自定义目录标题 一、什么是性能测试二、为什么要学习性能三、性能测试与功能测试的区别1、性能测试和功能测试的区别&#xff1f;2、性能测试是在什么时候做?3、我们在做我们的产品的时候&#xff0c;我们只做功能不做性能可不可以&#xff1f;4、性能测试主要是这几个方…

金融计量学第2课堂-金融时间序列线性模型

量化策略开发&#xff0c;高质量社群&#xff0c;交易思路分享等相关内容 一、相关性和平稳性 1、相关性 &#xff08;1&#xff09;皮尔森相关系数 皮尔森相关系数是最常见、最常用的一个相关系数计算方法。作为衡量两个随机变量x和y线性相关程度的重要指标&#xff0c;在这…

赋能智能智造-RK3568智能主板助力机器人产业高速发展

机器人作为现代制造业的重要一环&#xff0c;正在以惊人的速度推动着生产效率和智能化水平的提升&#xff0c;它们在生产线上的准确操作和高效工作&#xff0c;为企业带来了巨大的竞争优势。关于工业机器人的编程和控制技术&#xff0c;在过去几年中已经有了很多发展和新的应用…

十.Redis持久化

十.Redis持久化 一&#xff1a;RDB持久化二&#xff1a;AOF持久化三&#xff1a;扩展 redis持久化 Redis是内存数据库&#xff0c;如果不将内存中的数据库状态保存到磁盘&#xff0c;那么服务进程退出&#xff0c;服务器中的数据库状态也会消失&#xff0c;所以Redis提供了持久…

《Spring系列》第18章 监听器Listener

前言 陆续阅读了Spring Boot源码中&#xff0c;有不少地方都用到了监听器。每次看到监听器的代码都一头雾水&#xff0c;不懂其中的设计理念&#xff0c;这次决定肝一篇监听器的博文。 一、监听器 1.概述 何为监听器&#xff1f;就是用来监听程序执行的。监听器可以做什么事…

青岛大学_王卓老师【数据结构与算法】Week05_03_队列的定义和特点_学习笔记

本文是个人学习笔记&#xff0c;素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享&#xff0c; 另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权&#xff0c;请留言作删文处理。 课程视频链接&#xff1a; 数据结构与算法基础…

Vue3通透教程【十七】Vite构建TS版本Vue项目

文章目录 &#x1f31f; 写在前面&#x1f31f; 创建TS版本的Vue3项目&#x1f31f; 插件安装&#x1f31f; 写在最后 &#x1f31f; 写在前面 专栏介绍&#xff1a; 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章&#xff0c;应粉丝要求开始更新 Vue3 的相关技术文章&#…

DocFastSearchTool(文档快速搜索工具)开发日志

目录 项目介绍 项目调研背景 项目需求分析 开发环境 项目涉及基础知识点 项目设计 设计基础 项目框架or架构 项目框架的搭建 系统工具模块--遍历目录 SQLite数据库 在Windows上安装SQLite SQLite命令 SQLite-C/C的API 安装SQLite源码 数据库操作的重要接口 连接…

PB从入坑到放弃(三)数据窗口

PB从入坑到放弃&#xff08;三&#xff09;数据窗口 写在前面一、 数据窗口画板1.1 Design 视窗1.1.1 General tab页设置1.1.2 Generation tab页设置1.1.3 Prefixes tab页设置 1.2 Preview 视窗1.2.1 查找数据1.2.2 翻页1.2.3 增加、删除数据 1.3 Control List 视窗1.4 Data 视…

学生成绩分析项目

数据采集 导入必要的库 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns加载数据集 df pd.read_csv(D:\\桌面\\数据\\student_marks.csv)显示数据框的前几行 # 显示数据框的形状 print("Shape of the dataframe:", df.shape)#显示…