TCP流套接字编程

文章目录

  • TCP流套接字编程
    • ServerSocket API
    • Socket API
    • 示例:回显服务器
      • 服务器端
      • 客户端
    • 利用线程池实现并发编程

TCP流套接字编程

TCP和UDP差距是很大的,在数据传输方面,UDP是面向数据报的,而TCP是面向字节流的的,下面列出了使用TCP来实现网络编程所依赖的Socket类,通过这些类和具体的例子,来详细的讲解TCP网络编程。

ServerSocket API

ServerSocket 是Java提供给我们创建TCP服务端Socket所使用的API。

ServerSocket 的构造方法如下:

构造方法方法说明
ServerSocket(int port)创建一个服务端的Socket对象,并绑定到指定的端口

ServerSocket 方法:

方法方法说明
Socket accept()在客户端连接后,通过accept()方法,可以拿出内核中已经于客户端建立好的连接对象,这个对象就是Socket对象,如果没有建立好的连接,则阻塞等待
void close()关闭此套接字

Socket API

在Java中,也给我们提供了一个Socket类,而这个 Socket 既会给客户端使用又会给服务器使用,以上这个 Server Socket 和 Socket 这两个类都是用来表示系统中的Socket文件的,也就是抽象了网卡这样的概念。

Socket 构造方法:

构造方法方法说明
Socket(String host, int port)创建一个客户端的Socket,并指定对端主机的IP,对端进程的端口号,然后建立连接

Socket 方法:

方法方法说明
InetAddress getInetAddress()返回Socket所连接的地址
InpuStream getInputStream()返回Socket的输入流
OutputStream getOutpubStream()返回此Socket的输出流

注意:UDP是无连接的,而TCP是有连接的

这里的“有连接”和“无连接” 不是传统意义上的连接,而是通信双方都保存了对端的信息,而UDP呢,它是每发送一次数据,都要指定一次对端的IP和端口号,然后将生成的数据报作为send的参数发出去,下图就是UDP中的发送数据报的核心代码;

在这里插入图片描述

对于TCP来说,并不需要,前提是需要先把连接给建立上。而这个连接如何建立,是不需要我们通过代码来实现的,而是操作系统的内核自动负责完成的,对于客户端这边来说,主要是“发起连接”动作,在服务器这边的应用程序是没有任何感知的,因为,系统内核就直接完成了建立连接的流程(这个流程就是三次握手),完成这个建立连接的流程之后,建立好的连接就会放在内核中的一个队列里排队(每一个ServerSocket都有这样的队列),服务器这边的应用程序想要和客户端进行通信,就会通过accept()方法,将建立好的连接对象拿到应用程序中,这样就可以进行通信,如果队列中没有建立好的连接的话,accept()方法就会阻塞等待,这个队列呢就是一种生产者消费者模型。

下面通过两个示例来讲解这些方法的使用:

示例:回显服务器

服务器端

一:建立连接

public class TcpEchoServer {
    private ServerSocket socket = null;
    public TcpEchoServer(int port) throws IOException {
        //创建服务器端的Socket对象
        socket = new ServerSocket(port);
    }
    //启动服务器方法
    public void start() throws IOException {
        System.out.println("服务器启动");

        //1.通过accept方法将建立好的连接拿到应用程序中,如果没有,则阻塞等待
        Socket clientSocket = socket.accept();

        //通过这个方法,处理拿到的连接
        process_connection(clientScoket);
    }

二:计算请求,返回响应

    private void process_connection(Socket clientSocket){
        //打印连接上的对端信息
        System.out.printf("客户端上线:[%s:%d]\n", clientSocket.getInetAddress().toString(),clientSocket.getPort());

        //通过clientSocket中的流对象来使双方进行通信
        try(InputStream is = clientSocket.getInputStream();
            OutputStream os = clientSocket.getOutputStream()) {

            //因为服务器和客户端可能不止一次进行交互,所以通过while进行循环交互
            while(true) {
                //通过Scanner的方式将流对象中的请求数据读取出来
                Scanner scanner = new Scanner(is);
                //判断scanner是否有下一个数据,如果没有,连接结束
                if(!scanner.hasNext()) {
                    System.out.printf("客户端下线:[%s:%d]\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //拿到客户端请求,通过next()方法,约定读到"\n"结束
                String request = scanner.next();

                //通过这个方法根据请求计算相应
                String response = calculator_response(request);

                //返回服务器处理的响应
                //2.通过PrintWriter进行发送

               PrintWriter printWriter = new PrintWriter(os);
                //这里的println可不是打印到控制台,而是发送给OutputStream对象,然后发送给客户端
                printWriter.println(response);

                //通过flush方法刷新缓冲区,如果没有这个操作的话,写入的数据很可能会在缓冲区中,而没有被发送出去
                printWriter.flush();

                //打印一下这次交互的内容
                System.out.printf("[%s:%d], request:%s, response:%s\n",clientSocket.getInetAddress(),
                                    clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
    private String calculator_response(String request) {
        //因为我们写的是回显服务器,所以请求是什么,响应就是什么
        return request;
    }

    public static void main(String[] args) {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9999);
        try {
            tcpEchoServer.start();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

在这里插入图片描述

在这里插入图片描述

客户端

一、建立连接

public class TcpEchoClient {

    private Socket socket = null;
    public TcpEchoClient(String ip, int port) {
        try {
            //指定服务器的地址和端口号
            //当我们new这个对象时,系统内核就会自动帮我们完成建立连接的流程;
            socket = new Socket(ip, port);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

二、发起请求,获取响应

 //客户端启动方法
    public void start() {

            try(InputStream is = socket.getInputStream();
                OutputStream os = socket.getOutputStream()) {
                Scanner scanner = new Scanner(System.in);
                PrintWriter printWriter = new PrintWriter(os);
                Scanner in = new Scanner(is);
                while(true) {

                    //1.向服务器发起请求
                    System.out.println("请输入请求->:");
                    String request = scanner.next();
                    printWriter.println(request);
                    printWriter.flush();

                    //2.读取服务器返回来的响应
                    String response = in.next();
                    System.out.println("response:" + response);
                }

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
    }

    public static void main(String[] args) {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 9999);
        tcpEchoClient.start();
    }
}

运行之后:

在这里插入图片描述

在这里插入图片描述

但是,上述代码还存在一个问题,现在是一个服务器和一个客户端进行交互,如果多个客户端和一个服务器进行交互,那么这个bug就出来了!!!

下图为IDEA如何同时运行多个进程的操作步骤

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

同时运行多个客户端,分析一下代码会出现什么问题!!!

运行完之后,可以看到,虽然启动了两个客户端,但是,在服务器这边,并没有显示第二个客户端上线,这是为什么呢?下面就结合代码分析一下原因

在这里插入图片描述

在这里插入图片描述

如果想要在处理第一个客户端请求的过程中,accept快速的接收到第二个客户端建立起的连接,那么,就需要使用到并发编程!!!

利用线程池实现并发编程

利用线程池的方式分配一个新的线程去执行process_connection方法中的处理请求的逻辑,让主线程继续去获取到系统队列中已经建立好连接的其他客户端,然后再通过线程池分配新的线程去执行,利用这样的方式,就可以实现多个客户端与服务器进行交互。

    public void start() throws IOException {
        System.out.println("服务器启动");
        //创建一个线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();
        while(true) {
            //1.通过accept方法将建立好的连接拿到应用程序中
            Socket clientSocket = socket.accept();

            //通过这个方法,使用新的线程处理拿到的连接
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    process_connection(clientSocket);
                }
            });
        }
    }

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Windows磁盘管理中硬盘无法初始化怎么办?

硬盘未出现在“此电脑”选项下的情况并不少见,当您打开磁盘管理,它要么显示为磁盘未知,要么显示为未分配的空间,或者只是不显示磁盘容量。为了访问您的硬盘并充分利用它,您需要对其进行初始化。不幸的是,您…

基于SSM的社区管理系统

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:Vue 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目:是 目录…

Java多线程并发(二)

四种线程池 Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。 newCachedThreadPool 创建一个可根据需要创建新线程的线程池,但是在以前…

Endnote使用教程

原由 最近要进行开题报告,要求不低于60文献的阅读与引用,单独插入引入我觉得是非常繁琐的事情,所以就借助Endnote这个工具,减少我们的工作量。 使用方法 第一步:先新建一个数据库,这样子可以在这个数据库…

动态获取绝对路径

在Python中,可以使用 os模块 来获取当前工作目录的路径,并使用 os.path.join()函数 将相对路径与当前工作目录结合起来,形成一个动态获取的绝对路径 以下是一个简单的例子: import os# 获取当前工作目录的路径 current_director…

ArkTS快速入门

一、概述 ArkTS是鸿蒙生态的应用开发语言。它在保持TypeScript(简称TS)基本语法风格的基础上,对TS的动态类型特性施加更严格的约束,引入静态类型。同时,提供了声明式UI、状态管理等相应的能力,让开发者可以…

Docker Container(容器)——6

目录: 什么是容器?容器生活案例?为什么需要容器?容器的生命周期 容器 OOM容器异常退出容器暂停容器命令清单容器命令详解 docker createdocker rundocker psdocker logsdocker attachdocker execdocker startdocker stopdocker r…

Linux设置root初始密码

目录 一、Linux系统中普通用户和特权用户(root) 二、Linux系统中设置root初始密码 一、Linux系统中普通用户和特权用户(root) windows 系统中有普通用户和特权用户,特权用户是 administer,普通用户可以…

重新认识Word——多级列表和项目符号

重新认识Word——多级列表和项目符号 多级列表没有运用标题样式但标题格式统一 正式公本文书项目符号和自动编号项目符号自动编号软回车重新起头开始编号解决编号与文本距离过大问题 之前我们重新认识了Word里面的样式,现在的情况就是,我的一些文字已经运…

上海宝山区12月8日发生一起火灾 火势已扑灭 揭秘AI如何“救援”

在这个冬日的早晨,上海宝山区的居民经历了一场惊心动魄的火灾。幸运的是,火势很快就被扑灭了。但这起事件不禁让我们思考:如何更有效地预防和应对这样的紧急情况? 这时候,就不得不提到北京富维图像公司的一项创新技术—…

了解linux网络时间服务器

本章主要介绍网络时间服务器。 使用chrony配置时间服务器 配置chrony客户端向服务器同步时间 20.1 时间同步的必要性 些服务对时间要求非常严格,例如,图20-1所示的由三台服务器搭建的ceph集群。 这三台服务器的时间必须保持一致,如果不一致…

Python Django-allauth: 构建全面的用户身份验证系统

更多资料获取 📚 个人网站:ipengtao.com Django-allauth是一个功能强大的Django插件,旨在简化和定制Web应用程序中的用户身份验证和管理。本文将深入介绍Django-allauth的核心功能、基本用法以及实际应用场景,通过丰富的示例代码…

一个newman命令行让某大厂瘫痪半天,速看!

newman简介 newman是为Postman而生,专门用来运行Postman编写好的脚本; 使用newman,你可以很方便的用命令行来执行postman collections。 newman的安装 1.先下载Node.js;https://nodejs.org/en/ 2.安装NodeJs(很容易安装&#x…

Java IO流(二)(字节流FileOutputStream)

IO流体系 InputStream、OutputStream及Reader、Writer都是抽象类。 字节流 FileOutputStream 操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。 FileOutputStream字节输出流的细节 创建字节输出流对象 细节1:参数是字符串表示的路径或者是File对象…

zotero关闭翻译自动创建标签

zotero中文社区:https://plugins.zotero-chinese.com/#/

游戏架构之继承对象模型和组件对象模型

1.概述: 在所有游戏性架构相关的内容中,运行时对象模型可能是最复杂的系统,并且不同的游戏引擎呈现出的差异极大。例如Unity3D提供的组件模型,虚幻引擎提供的面向对象继承模型,其他一些游戏则使用一种不同于两者的基于…

【广州华锐互动VRAR】VR戒毒科普宣传系统有效提高戒毒成功率

随着科技的不断发展,虚拟现实(VR)技术已经逐渐渗透到各个领域,为人们的生活带来了前所未有的便利。在教育科普领域,VR技术的应用也日益广泛,本文将详细介绍广州华锐互动开发的VR戒毒科普宣传系统&#xff0…

12.8作业

1. 使用手动连接,将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中,在槽函数中判断ui界面上输入的账号是否为"admin",密码是…

Termux+Hexo结合内网穿透轻松实现安卓手机搭建博客网站发布公网访问

文章目录 前言 1.安装 Hexo2.安装cpolar3.远程访问4.固定公网地址 前言 Hexo 是一个用 Nodejs 编写的快速、简洁且高效的博客框架。Hexo 使用 Markdown 解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 下面介绍在Termux中安装个人hexo博客并结合…

接口自动化测试用例

1、接口文档 根据开发、产品的接口文档,以及评审,进行设计接口测试用例,它不像UI测试,有个界面,对于简单的系统,需求文档不提供也能覆盖所有功能,接口测试虽说可以抓包,但抓包无法覆…