socket实现TCP UDP

news2024/12/23 0:22:41

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.coloradmin.cn/o/1658404.html

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

相关文章

YOLOv5-7.0改进(三)添加损失函数EIoU、AlphaIoU、SIoU、WIoU、MPDIoU、NWD

前言 损失函数的改进一直是涨点的重要技巧&#xff0c;本篇博客将使用六个不同损失函数对算法进行改进&#xff0c;并绘制出改进结果对比图~ 往期回顾 YOLOv5-7.0改进&#xff08;一&#xff09;MobileNetv3替换主干网络 YOLOv5-7.0改进&#xff08;二&#xff09;BiFPN替换…

连续31年稳健增长,73.25%分红率再创新高,伊利的实力是什么?

文 | 螳螂观察 作者 | 易不二 4月29日&#xff0c;伊利股份发布2023年年报及2024年一季报。 年报显示&#xff0c;2023年&#xff0c;伊利实现营业总收入1261.79亿元&#xff0c;归母净利润104.29亿元&#xff0c;双创历史新高&#xff0c;实现连续31年稳健增长。公司拟每10…

腾讯云一年99元服务器,2核2G4M服务器1年99元

近日&#xff0c;腾讯云推出了一项令人瞩目的优惠活动&#xff1a;其2核2G4M的云服务器&#xff0c;现在仅需99元即可享用一年&#xff01;这一价格无疑在市场上引起了广泛关注&#xff0c;成为了众多企业和个人用户的首选。腾讯云服务器性价比是很高的&#xff0c;我的使用体验…

【漏洞复现】Apahce HTTPd 2.4.49(CVE-2021-41773)路径穿越漏洞

简介&#xff1a; Apache HTTP Server是一个开源、跨平台的Web服务器&#xff0c;它在全球范围内被广泛使用。2021年10月5日&#xff0c;Apache发布更新公告&#xff0c;修复了Apache HTTP Server2.4.49中的一个路径遍历和文件泄露漏洞&#xff08;CVE-2021-41773&#xff09;。…

Docker部署Metabase

文章目录 Docker安装MetabaseCentOS7安装Docker获取最新的 Docker 镜像启动Metabase容器在Metabase初始化时查看日志访问Metabase Metabase 的 ClickHouse 驱动程序安装环境简介删除容器创建容器下载click house驱动放入驱动重启容器将元数据库连接到 ClickHouse报错解决 Docke…

6份不用辞职就能赚钱的副业,上班族必看!

在这个经济浪潮中&#xff0c;生活成本的上升与工资增长的缓慢形成了鲜明对比。对于许多上班族来说&#xff0c;寻找额外收入的途径显得尤为迫切。 今天&#xff0c;就让我们一起探索那些适合在业余时间开展的副业&#xff0c;为你的财务自由之路添砖加瓦。 1. 闲鱼二手手机售卖…

Zip压缩归档库-libzip介绍

1.简介 libzip是一个C库&#xff0c;用于读取、创建和修改zip格式的压缩文件。它支持从zip文件中读取、写入、添加和删除文件&#xff0c;还支持密码保护的zip文件。libzip是跨平台的&#xff0c;可以在多种操作系统上使用&#xff0c;包括Linux、Windows和macOS。 常用接口介…

5月9日作业

1&#xff0c;创建一对父子进程&#xff1a;父进程负责向文件中写入 长方形的长和宽子进程负责读取文件中的长宽信息后&#xff0c;计算长方形的面积。 1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #…

10. Django Auth认证系统

10. Auth认证系统 Django除了内置的Admin后台系统之外, 还内置了Auth认证系统. 整个Auth认证系统可分为三大部分: 用户信息, 用户权限和用户组, 在数据库中分别对应数据表auth_user, auth_permission和auth_group.10.1 内置User实现用户管理 用户管理是网站必备的功能之一, D…

【ETAS CP AUTOSAR工具链】RTA-OS基本概念与开发流程

RTA-OS基于早期ETAS操作系统的成熟技术&#xff0c;迄今为止&#xff0c;已在全球超过3.5亿个ECU中使用。RTA-OS是一个可静态配置的抢占式实时操作系统(RTOS)&#xff0c;它常被用于资源受限但有着高性能要求的方案中。内核的实现不仅遵循了AUTOSAR R3.x、R4.0、R4.1、R4.2、R4…

刷题《面试经典150题》(第九天)

加油&#xff01; 学习目标&#xff1a;学习内容&#xff1a;学习时间&#xff1a;知识点学习内容&#xff1a;跳跃游戏 II - 力扣&#xff08;LeetCode&#xff09;H 指数 - 力扣&#xff08;LeetCode&#xff09;盛最多水的容器 - 力扣&#xff08;LeetCode&#xff09;矩阵置…

全平台 GUI库, 物联网,嵌入式,单片机,桌面应用都行

跨平台最小头文件GUI库 GuiLite是一个轻量级、高效的GUI库&#xff0c;拥有仅4千行的C代码&#xff0c;且零依赖&#xff0c;采用单一头文件库&#xff08;GuiLite.h&#xff09;。这个库不仅提供高效渲染&#xff0c;即使在单片机上也能流畅运行&#xff0c;展现了卓越的性能表…

Power BI 可以取代 Excel 吗?

首先说明&#xff0c;Power BI并不能完全取代Excel。 Power BI和Excel虽然都是Microsoft推出的数据工具&#xff0c;但他们的设计目标并不相同&#xff0c;因此它们在数据分析和处理方面各自有独特的优势。两者更多是互补关系。 在做报表可视化方面Power BI确实优势明显&…

人脸可调色美颜SDK解决方案,让妆容更加自然、真实

在追求个性化和差异化的美妆时代&#xff0c;美摄科技以其前沿技术&#xff0c;为企业带来了一场美妆革新的风暴。我们全新推出的人脸可调色美颜SDK解决方案&#xff0c;将为您提供前所未有的美妆体验&#xff0c;让每一位用户都能轻松打造属于自己的独特妆容。 可调色技术&am…

JavaScript算法描述【排序与搜索】六大经典排序|搜索旋转排序数组|在排序数组中查找元素的第一个和最后一个位置、数组中的第K个|

&#x1f427;主页详情&#xff1a;Choice~的个人主页 文章目录 搜索旋转排序数组方法一 二分查询最大最小值思路详解代码方法二 二分查询中间数 在排序数组中查找元素的第一个和最后一个位置、数组中的第K个最大元素和颜色分类在排序数组中查找元素的第一个和最后一个位置方法…

day-31 给植物浇水

思路 每次前进浇水时进行判断&#xff1a; &#xff08;1&#xff09;如果当前水足够&#xff0c;则前进一步浇水 &#xff08;2&#xff09;如果当前水量不够,则返回-1处加水再返回&#xff0c;再前进一步浇水 解题方法 用一个变量po记录即将浇水的植物编号&#xff0c;当pop…

05-09 周四 vLLM的部署和实践

05-09 周四 vLLM能够做什么事情 时间版本修改人描述2024年5月9日14:33:05V0.1宋全恒新建文档 简介 GitHub项目 vllm-project 官方网站上解释了Fast、和flexible and easy to use的原因。 注&#xff0c;如果要使用ModelScope中的模型&#xff0c;请设置环境变量&#xff1a; e…

深度学习——前馈全连接神经网络(鸢尾花)

前馈全连接神经网络对鸢尾花数据集进行分类 1.导入所需要的包2.打印训练集和测试集二维数组3.定义模型4.打印模型信息5.权重和偏执6.编译网络和训练网络7.打印二维数据表格8.绘制图像9.查看准确率 1.鸢尾花数据集可以用 from sklearn.datasets import load_iris 方式获取&#…

汽车行业芯片 车规级芯片 单车芯片( soc mcu)数量

链接&#xff1a;https://xueqiu.com/3000217281/272114755 10大车规级MCU芯片10大车规级MCU芯片 汽车芯片是什么&#xff1f; 汽车芯片即车规级芯片&#xff0c;标准要高于工业级和民用级芯片&#xff0c;仅次于军工级芯片。芯片大概有以下四种级别&#xff0c;分别是军工级…

基于OceanBase+Flink CDC,云粒智慧实时数仓演进之路

摘要&#xff1a;本文整理自云粒智慧高级技术专家付大伟在 4 月 20 日的 2024 OceanBase 开发者大会上的分享&#xff0c;讲述了其数据中台在传统数仓技术框架下做的一系列努力后&#xff0c;跨进 FlinkCDC 结合 OceanBase 的实时数仓演进过程。 内容主要分为以下几个部分: 业务…