socket实现TCP UDP

1、socket通信建立流程

1.1、创建服务端流程

  • 使用 socket 函数来创建 socket服务。

  • 使用 bind 函数绑定端口。

  • 使用 listen 函数监听端口。

  • 使用 accept 函数接收客户端请求。

1.2、创建客户端流程

  • 使用 socket 函数来创建 socket 服务。

  • 使用 connect 函数连接到 socket 服务端。

以下图表演示了客户端与服务端之间的通信流程:

1.3、创建服务端代码

package com.example.dyc.mysocket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MySocketServer {
    // 预定义字典
    private static final Map<String, String> dictionary;
    //字典初始化
    static {
        dictionary = new HashMap<>();
        dictionary.put("apple", "苹果");
        dictionary.put("pear", "梨");
    }
    //定义服务器端口,范围在[0, 65535]
    public static final int PORT = 8888;

    public static void main(String[] args) throws IOException {
        System.out.println(new Date() + ":" + 1);
        ServerSocket serverSocket = new ServerSocket(PORT);//从CLOSED到LISTEN状态
        System.out.println(new Date() + ":" + 2);
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        while (true) {
            System.out.println(new Date() + ":" + 3);
            Socket socket = serverSocket.accept();  // 阻塞,等待客户端发起连接,建立连接,到ESTABLISHED状态
            System.out.println(new Date() + ":" + 4);

            SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();//得到客户端地址+端口
            System.out.println(new Date() + ":" + remoteSocketAddress);
            InputStream inputStream = socket.getInputStream();//得到输入流
            Scanner scanner = new Scanner(inputStream, "UTF-8");//字符集编码
            OutputStream outputStream = socket.getOutputStream();//得到输出流
            Writer writer = new OutputStreamWriter(outputStream, "UTF-8");//字符集编码
            PrintWriter printWriter = new PrintWriter(writer);

            // TCP 是一种流式数据,没有明显分界的
            // 隐含着我们的请求一定 XXXX\n
            String request = scanner.nextLine();//由scanner得到客户端输入
            String response = dictionary.getOrDefault(request, "没有找到");
            // 响应的协议也是 XXX\n
            printWriter.println(response);//把响应传入输出
            printWriter.flush();//发送给客户端

            socket.close(); // 关闭连接
        }
    }
}

1.4、创建客户端代码

package com.example.dyc.mysocket;

import java.io.*;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Date;
import java.util.Scanner;

public class MySocketClient {
    public static void main(String[] args) throws IOException {
        System.out.println(new Date() + ":" + 1);
        Socket socket = new Socket("127.0.0.1", 8888);//刚建立完连接,传入客户端IP地址和端口

        SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();//得到服务器地址+端口
        System.out.println(new Date() + ":" + remoteSocketAddress);
        InputStream inputStream = socket.getInputStream();//得到输入流
        Scanner scanner = new Scanner(inputStream, "UTF-8");//字符集编码
        OutputStream outputStream = socket.getOutputStream();//得到输出流
        Writer writer = new OutputStreamWriter(outputStream, "UTF-8");//字符集编码
        PrintWriter printWriter = new PrintWriter(writer);

        printWriter.println("apple");//把apple传入输出
        printWriter.flush();//输出发送给服务器
        String response = scanner.nextLine();//由scanner得到输入的服务器响应
        System.out.println(new Date() + ":" + response);

        socket.close();//关闭连接
    }
}

2、socket实现BIO

2.1、BIO

传统的网络通讯模型,就是BIO,同步阻塞IO, 其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket去连接服务端的那个ServerSocket, ServerSocket接收到了一个的连接请求就创建一个Socket和一个线程去跟那个Socket进行通讯。接着客户端和服务端就进行阻塞式的通信,客户端发送一个请求,服务端Socket进行处理后返回响应,在响应返回前,客户端那边就阻塞等待,什么事情也做不了。 这种方式的缺点, 每次一个客户端接入,都需要在服务端创建一个线程来服务这个客户端,这样大量客户端来的时候,就会造成服务端的线程数量可能达到了几千甚至几万,这样就可能会造成服务端过载过高,最后崩溃死掉。

2.2、代码实现

Client


import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyBIOClient {
    public static void main(String[] args) throws Exception {
        // 创建 Socket 客户端
        Socket socket = new Socket();
        // 与服务端建立连接
        socket.connect(new InetSocketAddress("127.0.0.1", 8081));

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        int counter = 0;
        while (counter < 5) {
            String now = simpleDateFormat.format(new Date());
            // 发送请求
            socket.getOutputStream().write(now.getBytes("UTF-8"));
            socket.getOutputStream().flush();
            Thread.sleep(1000);
            counter++;
        }
        // 若方法运行结束后,不调用 close 函数,服务端则会报错:java.net.SocketException: Connection reset
        socket.close();
        System.out.println("客户端关闭了 Socket 连接~!");
    }
}

Serve

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyBIOServe {
    public static void main(String[] args) throws Exception {
        // 创建 Socket 服务端,并设置监听的端口
        ServerSocket serverSocket = new ServerSocket(8081);
        // 创建线程池以执行客户端请求(防止因请求过多,而导致的阻塞)
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 10, 60,
                TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
        while (true) {
            // 阻塞方法,监听客户端请求
            Socket socket = serverSocket.accept();
            System.out.println("\r\n" + socket);
            // 创建自定义请求处理器
            SocketHandler handler = new SocketHandler(socket);
            // 处理客户端请求
            poolExecutor.execute(handler);
        }
    }
}
SocketHandler
public class SocketHandler implements Runnable {
    private Socket socket;
    private static final byte[] BUFFER = new byte[1024];

    @Override
    public void run() {
        try {
            while (true){
                System.out.println(Thread.currentThread().getName());
                // 读取客户端 Socket 请求数据
                int read = socket.getInputStream().read(BUFFER);
                if (read != -1) {
                    System.out.println(new String(BUFFER, "UTF-8"));
                }else{
                    socket.close();
                    System.out.println("服务端关闭了 Socket 连接~!");
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public SocketHandler(Socket socket) {
        this.socket = socket;
    }
}

3、NIO

3.1、NIO

NIO: NIO是一种同步非阻塞IO, 基于Reactor模型来实现的。其实相当于就是一个线程处理大量的客户端的请求,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可。这里的核心就是非阻塞,就那个selector一个线程就可以不停轮询channel,所有客户端请求都不会阻塞,直接就会进来,大不了就是等待一下排着队而已。这里面优化BIO的核心就是,一个客户端并不是时时刻刻都有数据进行交互,没有必要死耗着一个线程不放,所以客户端选择了让线程歇一歇,只有客户端有相应的操作的时候才发起通知,创建一个线程来处理请求。

3.2、代码实现

MyNIOClient2 


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class MyNIOClient2 {
    public static void main(String[] args) {
        //创建远程地址
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
        SocketChannel channel = null;
        //定义缓存
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            //开启通道
            channel = SocketChannel.open();
            //连接远程远程服务器
            channel.connect(address);
            Scanner sc = new Scanner(System.in);
            while (true) {
                System.out.println("客户端即将给 服务器发送数据..");
                String line = "clinet2:" + sc.nextLine();
                if (line.equals("exit")) {
                    break;
                }
                //控制台输入数据写到缓存
                buffer.put(line.getBytes("UTF-8"));
                //重置buffer 游标
                buffer.flip();
                //数据发送到数据
                channel.write(buffer);
                //清空缓存数据
                buffer.clear();
                //读取服务器返回的数据
                int readLen = channel.read(buffer);
                if (readLen == -1) {
                    break;
                }
                //重置buffer游标
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                //读取数据到字节数组
                buffer.get(bytes);
                System.out.println("收到了服务器发送的数据 : " + new String(bytes, "UTF-8"));
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != channel) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

MyNIOService


import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Scanner;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyNIOService extends Thread {
    //1.声明多路复用器
    private Selector selector;
    //2.定义读写缓冲区
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);

    //3.定义构造方法初始化端口
    public MyNIOService(int port) {
        init(port);
    }

    //4.main方法启动线程
    public static void main(String[] args) {
        new Thread(new MyNIOService(8888)).start();
    }

    //5.初始化
    private void init(int port) {
        try {
            System.out.println("服务器正在启动......");
            //1)开启多路复用器
            this.selector = Selector.open();
            //2) 开启服务通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //3)设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //4)绑定端口
            serverSocketChannel.bind(new InetSocketAddress(port));
            //5)注册,标记服务通标状态
            serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器启动完毕");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        while (true) {
            try {
                //1.当有至少一个通道被选中,执行此方法
                this.selector.select();
                //2.获取选中的通道编号集合
                Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
                //3.遍历keys
                while (keys.hasNext()) {
                    SelectionKey key = keys.next();
                    //4.当前key需要从集合中移出,如果不移出,下次循环会执行对应的逻辑,造成业务错乱
                    keys.remove();
                    //5.判断通道是否有效
                    if (key.isValid()) {
                        try {
                            //6.判断是否可读
                            if (key.isAcceptable()) {
                                accept(key);
                            }
                        } catch (CancelledKeyException e) {
                            //出现异常断开连接
                            key.cancel();
                        }
                        try {
                            //7.判断是否可读
                            if (key.isReadable()) {
                                read(key);
                            }
                        } catch (CancelledKeyException e) {
                            //出现异常断开连接
                            key.cancel();
                        }
                        try {
                            //8.判断是否可写
                            if (key.isWritable()) {
                                write(key);
                            }
                        } catch (CancelledKeyException e) {
                            //出现异常断开连接
                            key.cancel();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void accept(SelectionKey key) {
        try {
            //1.当前通道在init方法中注册到了selector中的ServerSocketChannel
            ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
            //2.阻塞方法, 客户端发起后请求返回.
            SocketChannel channel = serverSocketChannel.accept();
            //3.serverSocketChannel设置为非阻塞
            channel.configureBlocking(false);
            //4.设置对应客户端的通道标记,设置次通道为可读时使用
            channel.register(this.selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //使用通道读取数据
    private void read(SelectionKey key) {
        try {
            //清空缓存
            this.readBuffer.clear();
            //获取当前通道对象
            SocketChannel channel = (SocketChannel) key.channel();
            //将通道的数据(客户发送的data)读到缓存中.
            int readLen = channel.read(readBuffer);
            //如果通道中没有数据
            if (readLen == -1) {
                //关闭通道
                key.channel().close();
                //关闭连接
                key.cancel();
                return;
            }
            //Buffer中有游标,游标不会重置,需要我们调用flip重置. 否则读取不一致
            this.readBuffer.flip();
            //创建有效字节长度数组
            byte[] bytes = new byte[readBuffer.remaining()];
            //读取buffer中数据保存在字节数组
            readBuffer.get(bytes);
            System.out.println("收到了从客户端 " + channel.getRemoteAddress() +
                    " : " + new String(bytes, "UTF-8"));
            //注册通道,标记为写操作
            channel.register(this.selector, SelectionKey.OP_WRITE);
        } catch (Exception e) {
        }
    }

    //给通道中写操作
    private void write(SelectionKey key) {
        //清空缓存
        this.readBuffer.clear();
        //获取当前通道对象
        SocketChannel channel = (SocketChannel) key.channel();
        //录入数据
        Scanner scanner = new Scanner(System.in);
        try {
            System.out.println("即将发送数据到客户端..");
            String line = scanner.nextLine();
            //把录入的数据写到Buffer中
            writeBuffer.put(line.getBytes("UTF-8"));
            //重置缓存游标
            writeBuffer.flip();
            channel.write(writeBuffer);
            //清空writeBuffer
            writeBuffer.clear();
            channel.register(this.selector, SelectionKey.OP_READ);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4、Netty

ps:后面再补充

参考文献:

Socket 之 BIO、NIO、Netty 简单实现 - 知乎 (zhihu.com)

什么是NIO?NIO和BIO,AIO之间的区别是什么? - 知乎 (zhihu.com)

java.nio.Buffer 中的 flip()方法_wrap.flip()-CSDN博客

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

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

相关文章

在k8s中部署hadoop后的使用,包括服务端及客户端(客户端的安装及与k8s服务的对接)

&#xff08;作者&#xff1a;陈玓玏&#xff09; 在https://blog.csdn.net/weixin_39750084/article/details/136744772?spm1001.2014.3001.5502和https://blog.csdn.net/weixin_39750084/article/details/136750613?spm1001.2014.3001.5502这两篇文章中&#xff0c;说明…

景源畅信电商:抖音小店有哪些比较热门的宣传方法?

抖音小店的热门宣传方法&#xff0c;是许多商家关注的焦点。在数字化营销时代&#xff0c;有效的宣传手段不仅能提升品牌知名度&#xff0c;还能吸引潜在消费者&#xff0c;促进销售。以下是针对抖音小店热门宣传方法的详细阐述&#xff1a; 一、短视频内容营销 作为抖音的核心…

ICode国际青少年编程竞赛- Python-2级训练场-坐标与列表遍历

ICode国际青少年编程竞赛- Python-2级训练场-坐标与列表遍历 1、 for i in range(5):Flyer[i].step(Dev.x -Flyer[i].x) Dev.step(Item.y - Dev.y)2、 for i in range(7):Flyer[i].step(Dev.y - Flyer[i].y) Dev.step(Item[2].x - Dev.x)3、 for i in range(5):Flyer[i].…

Prometheus(普罗米修斯)安装并添加采集端(2)

Prometheus介绍&#xff1a; Prometheus&#xff08;普罗米修斯&#xff09;基于Go语言开发&#xff0c;是一套开源且免费的监控系统&#xff0c;主要应用在容器领域的监控解决方案。 官方地址&#xff1a;https://prometheus.io/ 项目托管&#xff1a;https://github.com/pr…

嵌入式开发九:STM32时钟系统

时钟对于单片机来说是非常重要的&#xff0c;它为单片机工作提供一个稳定的机器周期从而使系统能够正常运行。时钟系统犹如人的心脏&#xff0c;一旦有问题整个系统就崩溃。我们知道 STM32 属于高级单片机&#xff0c;其内部有很多的外设&#xff0c;但不是所有外设都使用同一时…

7.基于麻雀搜索算法(SSA)优化VMD参数(SSA-VMD)

01.智能优化算法优化VMD参数的使用说明 02.基本原理 麻雀搜索算法&#xff08;SSA&#xff09;是一种基于鸟类觅食行为的启发式优化算法&#xff0c;它模拟了麻雀在觅食时的群体行为&#xff0c;通过模拟麻雀的觅食过程来寻找问题的最优解。SSA的基本原理是通过模拟麻雀的搜索…

康姿百德集团公司官网价格统一,产品编码可查真伪售后有保障

康姿百德床垫&#xff0c;静音设计让你享受安静睡眠环境 随着越来越多的人睡眠质量差&#xff0c;夜间难以入睡&#xff0c;人们开始意识到睡眠问题已经不仅仅是健康问题&#xff0c;更上升为一种社会问题&#xff0c;急需得到解决。作为寝具行业中的优质品牌&#xff0c;康姿…

DDD领域驱动模型设计

醍醐灌顶了朋友们 第一次写ddd还是 一路走来 丢失了东西 现在倒是也能找回来 只是有点可惜了 选择比努力更重要 独立功能 应用层&#xff1a;组织业务逻辑 领域&#xff1a;实体对象领域&#xff0c;业务核心 数据仓库&#xff1a; 不影响业务封装了数据操作&#xff0c;…

深入理解 Linux 文件系统与动静态库

目录 一、Linux 文件系统中的 inode 二、软硬链接 三、动静态库 在 Linux 系统中&#xff0c;文件系统和动静态库是非常重要的概念。本文将带大家深入了解这些内容&#xff0c;让你在技术之路上更进一步。 一、Linux 文件系统中的 inode 何为文件系统&#xff1f;对计算机中…

Fortinet的安全愿景SASO概述

FTNT SASE的独特方法&#xff0c;使其成为一家适应性极强的厂商&#xff0c;能够应对不断变化的网络和网络安全环境。FTNT开发了一种名为Secure Access Service Omni&#xff08;SASO&#xff09;的变体&#xff0c;以更准确地反映FTNT在融合网络和安全功能方面的实力。我们预计…

2024 VMware VCP一条龙通关-送题库

VMware VCP-DCV 2024&#xff08;2V0-21.23&#xff09;认证考试&#xff0c;2024年可高分通过。 1.5. An administrator has a host profile named Standard-Config. The administrator wants to change the other host profiles to use only the storage configuration setti…

电脑缺失opencl.dll怎么办,轻松解决opencl.dll的多种方法分享

当我们在操作电脑过程中遇到系统提示“由于找不到opencl.dll&#xff0c;无法继续执行代码”&#xff0c;这个错误会导致软件应用无法正常运行。OpenCL.dll作为一个与Open Computing Language&#xff08;开放计算语言&#xff09;相关的动态链接库文件&#xff0c;它在执行需要…

pytest教程-43-钩子函数-pytest_report_header

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest_runtest_makereport钩子函数的使用方法&#xff0c;本小节我们讲解一下pytest_report_header钩子函数的使用方法。 pytest_report_header 钩子函数允许你在 pytest 的终端报告的头部添…

....comic科学....食用手册....

1.点击链接后&#xff0c;保存漫画至夸克网盘&#xff0c;若是新用户需要用手机注册. 2.在应用商店下载夸克APP. 3.登录APP下载已保存的漫画. 3.1 进入APP点击 夸克网盘 3.2 点击“转存的内容”后&#xff0c;长按 漫画文件夹&#xff0c;点击下载&#xff0c;下载速度400K左…

在Linux操作系统中扩建swap容量

在Linux操作系统上创建硬盘分区不仅可以储存数据&#xff0c;还可以使用创建的硬盘分区去扩展机器上swap分区的大小——去扩展交换工具的容量。 有些软件对于swap分区的大小是有要求的&#xff0c;swap分区的大小小于多少就安装不上软件。 要扩展swap容量要么重装系统&#x…

黑啤:浓郁与深邃的完善整合

啤酒的世界丰富多彩&#xff0c;而黑啤作为其中的一种与众不同风格&#xff0c;以其浓郁的口感和深邃的色泽备受瞩目。Fendi club黑啤作为精酿啤酒的代表&#xff0c;将浓郁与深邃完善整合&#xff0c;为消费者带来了与众不同的味蕾盛宴。 首先&#xff0c;Fendi club黑啤在原料…

Zabbix5.0——安装与部署

目录 一、zabbix-server(192.168.206.134) 监控方 1. 环境准备 2.安装zabbix 2.1 准备zabbix-repo 2.2清理缓存 2.3安装zabbix主包&#xff08;服务器和代理&#xff09; 2.4安装zabbix前端包 3. 数据库安装 3.1 授权zabbix账号 3.2导入数据库&#xff08;初始化zabbix&#x…

【多客开源】游戏陪玩系统,游戏陪玩源码,游戏陪玩语音社交源码运营版游戏陪玩平台源码/tt语音聊天/声优服务/陪玩系统源码开黑/约玩源码

介绍 我们针对陪玩app源码市场的发展趋势&#xff0c;整合市面上主流陪玩app应用功能&#xff0c;自主开发了多客陪玩系统源码&#xff0c;并可为客户提供全部原生陪玩源码&#xff0c;进行二次开发&#xff0c;打造适用于线上游戏陪玩、语音聊天、心理咨询、情感陪伴等业务场…

SpringBoot之Zuul服务

概述 Spring Cloud Netflix zuul组件是微服务架构中的网关组件,Zuul作为统一网关,是所有访问该平台的请求入口,核心功能是路由和过滤。 目前公司业务就是基于Zuul搭建的网关服务,且提供的服务包括转发请求(路由)、黑名单IP访问拦截、URL资源访问时的权限拦截、统一访问日志记…

在windows下使用VS Code、CMake、Make进行代码编译

软件环境 Windows11VS CodeNoneCMake3.26.4-windows-x86_64MinGWNone 电脑系统配置 安装MinGW将MinGW安装文件夹中bin文件夹下的mingw32-make.exe复制并重命名为make.exe在文件夹中添加系统路径&#xff0c;具体位置为 系统->系统信息->高级系统设置->高级->环境…
最新文章