[Java] Socket (UDP , TCP)

news2025/1/11 9:55:29

目录

什么是Socket ?

TCP api 与 UDP api 的特点 : 

UDP api

使用UDP Socket 实现一个单词翻译 : 

TCP api

使用TCP协议来实现一个回显服务


什么是Socket ?

应用层和传输层之间的桥梁 .

程序猿写网络代码 (应用层) , 要想发送这个数据 , 就需要去调用下层协议 , 应用层就要去调用传输层. 传输层给应用层提供了一组 api . 称为 Socket api ; 

Socket api 有两组 : 一组是基于UDP 的 api , 一组是基于 TCP 的api.

TCP api 与 UDP api 的特点 : 

TCP api UDP api 的特点 :

UDP : 无连接 , 不可靠传输 , 面向数据包 , 全双工 .

举个栗子 : 

无连接 : 就好比是发短信 , 我只用知道对方的手机号 , 就可以给他发送消息了. 这就是无连接,通信前 是不用建立连接的. (使用UDP通信的双方,不需要刻意保存对端的相关信息)

不可靠传输 : 就好比我们发的短信 , 短信发出去了 , 至于对方看没看见 , 对我来说不重要了.

面向数据报 : 在UDP 中 , 每次发送都是以一个数据报作为一个发送的单位. 

TCP : 有连接 , 可靠传输 , 面向字节流 , 全双工 . 

举个栗子 :

有连接 : 就好比打电话 , 我想跟对方交流就要先进行建立连接 , 等对方一接 , 我们就可以通话了 , (使用TCP 通信的双方 , 则需要刻意保存对方的相关信息).

可靠传输 : 对方接了电话之后 , 证明我们之间有连接了 , 一般我们都会试探性的问一句"能听到吗?" , 等对方说"能听到" , 表示我们说的话对方可以收到. (因此可靠传输指的就是消息传输出去后我们可以知道对方有没有收到).(但是我们建立连接后 , 不能够保证对方100%能收到)

面向字节流 : 以字节为传输的基本单位 , 读写方式非常灵活/方便.

单双工 全双工 的区别 : 

单双工 : 就像是一个吸管 , 只能一边进气 , 另一边出气.

全双工 : 就像是马路 , 在同一条马路上 , 汽车和人 , 都可以朝着相反的方向移动.

UDP api

UDP api 主要有两个类 : DatagramSocket , DatagramPacket .

由 DatagramSocket 创建出来的对象就是一个socket 对象 .

那什么是scoket对象呢 ?

socket 对象 对应到网卡这个硬件设备!

把socket 想象成为一个遥控器 , 这个遥控器控制的就是网卡.

它是一个特殊的文件(socket文件).

我们对socket 对象进行读操作 , 那么就是在读网卡.

        对socket 对象进行写操作 , 那么就是在进行写网卡.

由DatagramPacket 创建出来的对象就是一个UDP数据包 .

使用UDP api 进行读写操作时 , 每次就只能以数据包作为一个单位.

DatagramSocket 是UDP Socket , 用于发送和接收UDP 数据报.

DatagramSocket 两个常用的构造方法 : 一个是无参构造 , 一个是带有一个参数的构造方法.

两个方法都是用来给Socket 对象来绑定一个 端口号.

无参构造是系统自动来分配一个空闲的端口号.

带有一个参数的构造 : 自己主动来绑定一个端口号.

此处的Socket 对象要被客户端  / 服务器 都使用的 . 对于服务器来说 Socket 对象往往都要绑定一个具体的端口号(自己主动绑定) . 客户端这边则不需要手动指定 , 让系统自动分配即可.

这么做的好处 : 因为服务器它是用来处理客户端的请求的 , 因此它就要有一个明确的端口号,让客户端可以找到 . 对于客户端来说 , 属于主动发送请求的一方 , 服务器实现不需要知道客户端的端口号 , 因此让系统自动分配端口号给客户端.

不手动分配端口号给客户端是因为 : 我们不确定当前给客户端分配的这个端口号是不是已经被其他进程占用了 . 这样就会很麻烦 , 让系统自动分配会更好.

DatagramSocket 常见的方法 :

 重点关注一下 close() 方法 , Socket 它也是一个文件 , 文件用完了就要记得关闭 , 否则就会出现文件资源泄露的问题!!!!! 

DatagramPacket API

构造方法 : 

带有 SocketAddress 的构造方法 : 需要显式的设置地址 , 通常用来发送消息.

方法 : 

使用UDP Socket 实现一个单词翻译 : 

服务器 :

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.HashMap;


// 服务器
public class UdpTraServer {
    // 创建一个Socket 对象
    DatagramSocket socket = null;

    // 给服务器指定一个端口号
    public UdpTraServer(int port) throws SocketException {
        this.socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器已启动!");
        while (true) {
            // 1. 接收来自客户端的请求
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(requestPacket);
            // 将请求转变为String类型能够更方便的去处理.
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            // 2. 处理请求
            String response = process(request);
            // 3. 将请求打包成一个数据报
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress()); // 这里要指定客户端的IP地址和端口号
            // 4. 发送给客户端
            socket.send(responsePacket);
            System.out.printf("[%s : %d] 请求:  %s 响应 : %s \n",requestPacket.getAddress(),requestPacket.getPort(),request,response);
        }
    }

    private String process(String request) {
        HashMap<String,String> hash = new HashMap<>();
        hash.put("one","1");
        hash.put("two","2");
        hash.put("three","3");
        hash.put("four","4");
        return hash.getOrDefault(request,"null");
    }

    public static void main(String[] args) throws IOException {
        // 给服务器指定一个9090的端口号.
        UdpTraServer udpTraServer = new UdpTraServer(9090);
        udpTraServer.start();
    }

}

客户端 : 

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpTraClient {
    // 创建一个Socket 对象来进行网络编程.
    DatagramSocket socket = null;
    String serverIP = null;
    int port = 0;
    // 通过构造方法来指定服务器的IP地址和端口号
    public UdpTraClient(String serverIP,int port) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = serverIP;
        this.port = port;
    }

    public void start() throws IOException {
        Scanner input = new Scanner(System.in);
        while (true) {
            System.out.println("----> ");
            // 1. 从键盘上输入请求
            String request = input.nextLine();
            // 2. 将请求打包成数据包
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIP),port);
            // 3. 发送请求
            socket.send(requestPacket);
            // 4. 接收请求
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(responsePacket);
            // 将请求转变为String 进行输出
            String response = new String(responsePacket.getData(),0,requestPacket.getLength());
            System.out.printf("请求 : %s 响应 : %s \n",request,response);
        }
    }

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

注意在进行 receive 和 send 操作时 , 在等待数据时会处在阻塞状态 ! 

TCP api

面向字节流进行网络编程 .

TCP 中主要的核心类有两个 : 

ServerSocket : 专门给服务器提供的api , ServerSocket 主要做的事情就是负责和客户端建立连接.

构造方法 : 

创建服务器时 , 要指定一个端口号 . 

Socket :  通过Socket对象来进行服务器和客户端之间的通信 ! 

TCP的最最最核心 : 可靠性

当客户端主动向服务器发起连接请求 , 服务器就得同意一下. 表示服务器和客户端已经连接成功了 , 可以进行交互了 .(但是实际上这个accept ,  其实tcp连接的接受是在内核里已经完成了 , 这里的accept是应用层面的接受)

通过Socket中的  getInputStreamgetOutputStream 两个方法 , 可以进行对数据的输入和输出操作了 .

从InputStream 这里读数据就相当于从网卡接收 , 往OutputStream 这里写数据,相当于从网卡发送.

使用TCP协议来实现一个回显服务

约定 : 1. 每个请求是一个字符串(文本数据)

           2. 请求和请求之间 , 使用 \n 来分割

服务器 : 

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 TcpEchoServer {
    ServerSocket serverSocket = null;

    // 定义构造方法 , 为服务器设置一个端口号
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器已启动!");
        // 因为服务器要处理多个客户端的请求 , 套上一个循环
        while (true) {
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }
    private static void processConnection(Socket clientSocket) {
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            // 方便对字节流进行读写操作 创建 Scanner 和 PrintWriter
            Scanner scannerFromSocket = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);

            while (true) {
                // 1. 读取请求
                if (!scannerFromSocket.hasNext()) {
                    // 证明读取到流的末尾了  (对端的客户端已经关闭了)
                    System.out.printf("[%s : %d] 客户端已经下线! \n", clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                // 通过scannerFromSocket 对象来读取请求
                String request = scannerFromSocket.nextLine();
                // 处理请求
                String response = process(request);
                // 将请求写回客户端
                // 注意在这里写回的时候 使用的println 自带的换行操作
                printWriter.println(response);
                // 重点 : 刷新缓冲区
                printWriter.flush();
                System.out.printf("[%s : %d] req: %s; resp : %s \n", clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,response);

            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 断开连接后一定要去关闭文件 .
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 由于这里是回显服务 , 所以直接返回这个原请求就可以了
    private static String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }
}

客户端 : 

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 {
    public Socket socket = null;

    // 定义一个构造方法  给出要连接的端口号 , 和 IP地址
    public TcpEchoClient(int port, String ServerIp) throws IOException {
        // 重点注意 : 这个操作相当于让客户端和服务器建立TCP连接
        // 这里如果连接上了 , 服务器的 accept 就会返回
        socket = new Socket(ServerIp,port);
    }

    public void start() {
        Scanner input = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
             // 使用Scanner 和 PrintWrite 来对字节流进行输入和输出
            Scanner scannerFromSocket = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true) {
                System.out.println("---->  ");
                // 1. 得到请求
                String request = input.nextLine();
                // 2 把读取到的内容构造成请求 , 发送给服务器
                //   注意, 由于我们之前约定好 , 每个请求之间是由\n 来区分
                //   使用printWriter 的 println 就会自动带上换行
                printWriter.println(request);
                // 刷新缓冲区
                printWriter.flush();
                // 3. 读取响应
                // 通过scannerFromSocket
                String response = scannerFromSocket.nextLine();
                // 4. 把响应结果显示到控制台上
                System.out.printf("req : %s , resp : %s \n",request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

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

以上代码的思路 :

   先开启服务器 , 再开启客户端. 

1 . 服务器先运行start , 阻塞在accept .

2 . 客户端启动时 , 根据端口和IP地址 , 创建出Socket对象 , 同时和服务器建立连接 , 连接成功后 , 服务器的accept就会返回.

3 . 服务器和客户端会同时往下执行 , 客户端往下执行到从控制台读取用户输入也会阻塞. 而服务器就进入processConection方法 , 尝试从客户端读取请求 , 如果此时客户端还没有发送请求 , 那么读取操作也会阻塞.

4 . 当用户从控制台上输入内容后 , 客户端进行发送请求 , 同时往下执行 , 读取服务器的响应 , 再次进行阻塞 .

5 . 服务器收到客户端的请求后 , 从scanner 对象中读出 , 在process方法中进行处理响应 , 通过println方法将响应返回给客户端. 紧接着服务器就会回到等待请求的阻塞状态.

6 . 客户端通过scannerFromSocket 对象读取到响应结果后 , 将结果进行打印 , 然后再次从控制台上等待用户的输入了. 

刷新缓冲区 : 

无论是客户端将请求发送给服务器还是服务器将响应返回给客户端.都要进行一个刷新缓冲区的操作.

原因 : 在于速度 , 内存的速度比硬盘要快上几千倍 , 而网卡的速度大概率还比硬盘都要慢 .

对于上述我们进行的网络编程都是在对网卡进行读写操作.

对于提高IO的效率 (读写网卡 , 读写网卡都可以视为IO操作) 引入了缓冲区 , 使用缓冲区减少了IO次数 , 就可以提高整体的效率 , 例如 : 如果要进行多次写操作 , 就先把要写的数据放到一个内存构成了缓冲区中 , 在统一把这个缓冲区中的数据写入网卡中.

因此我们就要手动去刷新缓冲区 , 为了让对端第一时间拿到数据.

当客户端断开连接后 , 服务器要进行关闭连接操作 , 否则可能会造成文件泄露 , 不关闭文件就会一直被服务器占用着 , 而内存是有限的 , 达到一定程度后, 服务器可能就崩了.

设计服务器的初心是给多个客户端进行提供服务的 , 但是上述的TCP协议实现的服务器 , 它一次只能服务一个客户端 .

TCP的前提是要先建立连接  上面的代码 , 如果第一个客户端没有结束 , 就会导致processConnection这个方法不会结束, 从而就会导致服务器不会执行到accept这个方法 , 再去接收其他客户端的请求了 , 解决方案就是多线程.

    public void start() throws IOException {
        System.out.println("服务器已启动!");
        // 因为服务器要处理多个客户端的请求 , 套上一个循环
        while (true) {
            Socket clientSocket = serverSocket.accept();
            Thread t = new Thread(() -> {
                processConnection(clientSocket);
            });
            t.start();
        }
    }

或者使用线程池 

    public void start() throws IOException {
        System.out.println("服务器已启动!");
        // 因为服务器要处理多个客户端的请求 , 套上一个循环
        while (true) {
            ExecutorService pool = Executors.newCachedThreadPool();
            Socket clientSocket = serverSocket.accept();
            pool.submit(() -> {
                processConnection(clientSocket);
            });
        }
    }

TCP区别于UDP : 

UDP版本的程序就不需要多线程 , 也可以处理客户端的请求 , 原因在于UDP不需要处理连接 , UDP 只要一个循环 , 就可以处理所有客户端的请求了.

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

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

相关文章

uniapp图片转base64及JS各文件类型相互转换

uniapp图片转base64及JS各文件类型相互转换 1、chooseImage request arrayBufferToBase642、chooseImage getFileSystemManager3、chooseImage FileReader4、扩展-JS各文件类型相互转换4.1 File 转成 ArrayBuffer4.2 File 转成 blob4.3 File 转成 base644.4 ArrayBuffer 转…

【数据结构】前序遍历,中序遍历,后序遍历(二叉树)

一&#xff1a;给图求前序&#xff0c;中序&#xff0c;后序 前序遍历&#xff08;先序遍历&#xff09; 核心思想&#xff1a;根左右 前序序列&#xff1a;ABDEFCGH 先访问根结点A&#xff0c;打印A&#xff0c;然后访问左子树&#xff0c;此时左子树B又作为根节点&#xf…

JAVAWeb05-xml、DOM4J

1. xml概述 1.1 官方文档 地址: https://www.w3school.com.cn/xml/index.asp 1.2 为什么需要 XML 需求 1 : 两个程序间进行数据通信&#xff1f;需求 2 : 给一台服务器&#xff0c;做一个配置文件&#xff0c;当服务器程序启动时&#xff0c;去读取它应当监听的端口号、还有…

【数字人】使用Metahuman创建数字人模型(上)

这两年数字人类的概念可谓是风头正盛&#xff0c;市面上也流行起各式各样的数字人技术&#xff0c;效果能力及实现成本各不相同。本系列介绍基于Unreal Engine的Metahuman工具低成本构建一个拥有完整的控制权、免费、可商用、高仿真的数字人。本篇为构建基础人物模型 MetaHuma…

PHP快速入门14-Composer包管理安装与使用,附常见的20个使用例子

文章目录 前言一、关于Composer二、如何安装Composer2.1 Windows安装Composer2.2 Linux安装Composer 三、Composer常见的20个使用例子3.1 查找并安装依赖包3.2 更新依赖包3.3 安装指定版本的依赖包3.4 卸载依赖包3.5 查看当前项目依赖包列表3.6 初始化composer.json文件3.7 安装…

FAT32文件系统学习

FAT32文件系统组成及介绍 FAT32文件系统结构图&#xff1a; 下图演示了FAT32文件系统的DBR&#xff1a; 1.DBR及其保留扇区&#xff1a;含义是DOS引导记录&#xff0c;也称为操作系统引导记录&#xff0c;在DBR之后往往有一些保留扇区 跳转指令&#xff1a;跳转指令本身占用2字…

python程序打包成可执行文件【进阶篇】

python程序打包成可执行文件【进阶篇】 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 python程序打包成可执行文件【进阶篇】[TOC](文章目录) 前言安装PyInstaller包打包深度学习模型生成spec文件修改spec文件运行spec文件进行打包执行exe可执…

为什么倒谱可以分析回声

一个有趣的现象&#xff1a;倒谱上的第一个峰&#xff0c;恰好对应回声相比原声的延时。回声 y y y 是原始声音 x x x 延迟 t 0 t_0 t0​ 秒后的、带有衰减 α α α 的副本 y α x ( t − t 0 ) y αx (t - t_0) yαx(t−t0​) 方便起见&#xff0c;这里取 α 1 α …

抖音强势入局服装生意,出手就是1个亿,服装实体店出路在哪?

最近&#xff0c;抖音盯上了服装生意。 据悉&#xff0c;抖音近期组建了一个自营服装团队&#xff0c;在APP推出了一家“飞云织上”的店铺&#xff0c;店铺主体公司是“上海歆湃信息科技有限公司”。 根据爱企查显示&#xff0c;这家公司由抖音集团&#xff08;香港&#xff09…

RabbitMQ-消息模型

什么是MQ MQ全称是Message Queue,即消息对列&#xff01;消息队列是典型的&#xff1a;生产者、消费者模型。生产者不断向消息队列中生产消息&#xff0c;消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的&#xff0c;而且只关心消息的发送和接收&#xff0c;没…

IPV4与IPV6是什么?有什么区别?

目录 一. IPV4是什么&#xff1f; 二. IPV6是什么 三. IPv4和IPv6有什么区别&#xff1f; 1.地址类型 2.数据包大小 3.header区域字段数 4.可选字段 5.配置 6.安全性 7.与移动设备的兼容性 8.主要功能 IP协议&#xff08;互联网协议&#xff09;是互联网协议群&#…

USB TO SPI / USB TO I2C 软件概要 6--- 专业版调试器

所需设备&#xff1a; 1、USB转SPI_I2C适配器(专业版); 软件概述&#xff1a; SPI类: USB TO SPI 1.0-Slave SPI从机软件&#xff0c;适合单步调试&#xff0c;支持SPI工作模式0、1、2、3&#xff0c;自动跟随主机通讯速率&#xff0c;自动接收数据&#xff1b; USB TO S…

大城市的IT岗位,饱和了么?

现在学或即将更要学习IT专业的同学&#xff0c;经常会考虑这样一个问题&#xff1a;现在的计算机专业到处都有&#xff0c;大学里有、职业学校有&#xff0c;就连社会上的培训机构也有&#xff0c;而且专业分类更细化&#xff0c;有这么多的地方都在培训计算机相关专业&#xf…

二本4年测龄年仅25岁,五面阿里(定薪25K).....

体验了一下阿里的面试&#xff0c;不愧是大厂&#xff0c;考察范围既有深度也有宽度。努力回想了下面试的内容和自己当时的回答&#xff0c;这里给大家分享下自己的经验&#xff0c;也给自己做个归档&#xff0c;希望能给正在准备跳槽&#xff0c;找工作的朋友一点帮助&#xf…

每一个软件测试工程师都有要牢记的误区

最近跟一些刚刚进入软件测试行业的朋友去交流&#xff0c;发现了一个有趣的现象&#xff0c;就是对于这个行业的很多问题的认识都是一致的片面&#xff0c;当然也可以理解为误区。自己利用一点时间&#xff0c;把他们对于这个行业的认识误区都罗列出来&#xff0c;然后结合自己…

【Python从入门到进阶】16、文件的打开和关闭

接上篇《15、函数的定义和使用》 上一篇我们学习了Python中函数的定义和使用&#xff0c;包括函数的参数、返回值、局部变量和全景变量等操作。从本篇开始我们来开始学习Python对文件的一些操作&#xff0c;本篇我们主要讲解如何使用Python打开和关闭文件。 一、打开/创建文件…

全国青少年编程等级考试scratch四级真题2023年3月(含题库答题软件账号)

青少年编程等级考试scratch真题答题考试系统请点击 电子学会-全国青少年编程等级考试真题Scratch一级&#xff08;2019年3月&#xff09;在线答题_程序猿下山的博客-CSDN博客_小航答题助手 1.编写一段程序&#xff0c;从26个英文字母中&#xff0c;随机选出10个加入列表a。空白…

【id:45】【20分】A. Equation(类与对象+构造)

题目描述 建立一个类Equation&#xff0c;表达方程ax2bxc0。类中至少包含以下方法&#xff1a; 1、无参构造&#xff08;abc默认值为1.0、1.0、0&#xff09;与有参构造函数&#xff0c;用于初始化a、b、c的值&#xff1b; 2、set方法&#xff0c;用于修改a、b、c的值 3、ge…

JSP原理

1.什么是JSP Java Server Page:Java服务器端页面&#xff0c;也和Servlet一样&#xff0c;用于动态Web技术 最大的特点&#xff1a;写JSP就像再写HTML 页面生成了java JSP本质上就是servlet 3.输出页面前增加的代码 1.response.setContentType("text/html"); //设…

C++ ubuntu环境下安装编译部署环境,用onnxruntime部署ppyoloe_r模型

在新安装的ubuntu环境下修改源、安装gcc和cmake&#xff0c;编译安装opencv&#xff0c;安装onnxruntime环境。并编写cmakelist文件&#xff0c;编译与运行ppyoloe_r模型。 windows环境下onnx部署ppyoloe_r模型的代码可以参考 https://blog.csdn.net/a486259/article/details/1…