《JavaEE》网络编程套接字

news2024/11/26 9:56:55

文章目录

  • Socket
    • 我们来解释一下上面叫做有无连接
    • 我们再来理解一下上面是可靠传输和不可靠传输
    • 面向字节流与面向数据报的区别(后期会具体分析这个)
    • 全双工 单双工
  • UDP
    • DatagramSocket
    • DatagramPacket
    • 我们来尝试写一下UDP版本的代码
  • TCP
    • ServerSocket
    • Socket
    • 我们来尝试写一下TCP版本的代码

👑作者主页:Java冰激凌
📖专栏链接:JavaEE

Socket

Socket是操作系统给应用程序提供的一组API 可以认为Socket是应用层和传输层之间的桥梁 这就好比是相亲 男方与女方之间的联系是媒婆建立起来的 这个媒婆起到的作用也就是桥梁
传输层中有两类核心协议 TCPUDP
所以说 在Socket 的API也对应的有两层
TCP:有连接 可靠传输 面向字节流 全双工
UDP:无连接 不可靠传输 面向数据报 全双工


我们来解释一下上面叫做有无连接

有连接:就像我们打电话 当我们拨打电话之后 要等待对方接通电话才可以开始交流
无连接:就像发微信 我们给对方发送了消息之后 我们不关心对方是否看到了这个消息 只能保证我们消息发了(就是发了 也只能是发了 因为我们的网络中难免会有波动 这个消息有可能会丢失的 所以对方可能压根没有收到消息)


我们再来理解一下上面是可靠传输和不可靠传输

可靠传输:我们明确对方当前是否收到了消息 也就是说 还是以发微信为例 当我们发送过消息后 会等待对方给我们回复确认收到消息 如果对方一段时间后没有回复消息 我们会再次重发消息 (这个机制叫做超时重传 后期会讲) 简单总结:传输过程中发送方知道接收方有没有收到消息
不可靠传输:传输过程中 发送方不知道接收方有没有收到消息
此处有一共非常致命的错误理解!!!
可靠传输只能保证数据传输过去对方100%可以接收到 并不能代表可靠传输是安全的传输 并不能保证安全性


面向字节流与面向数据报的区别(后期会具体分析这个)

面向字节流以字节为单位进行传输 (非常类似于文件操作中的字节流)
面向数据报以数据报为单位进行传输(一个数据报都会明确大小 )一次发送/接收必须是一个完整的数据报 不能是半个 也不能是一个半 必须是一个


全双工 单双工

单双工:一条通道单向通信
全双工:一条通道双向通信
我们可以当做是单行路和双行路 一条路只能向南走 不可以往北走 这个就是单双工 一条路南北都可以走 就是全双工


UDP

我们来尝试着使用Java语言编写一共简单的客户端和服务器进行一个简单的交互
首先我们来认识一下UDP socket API

DatagramSocket

创建了一个UDP版本的socket对象 代表着操作系统中的一个socket文件

DatagramPacket

表示了一个UDP数据报 每次发送和接收 都是在传输一个UDP数据报

我们来尝试写一下UDP版本的代码

我们来写一个回显服务器

public class UDPEchoServer {
    //我们先来定义一个DatagramSocket类 用来接收数据和发送数据
    DatagramSocket datagramSocket = null;
    //在构造方法中我们将指定端口号 
    // 为何要指定端口号?
    // 因为我们客户端在发送的时候要明确知道服务器的端口号 所以此处我们手动指定
    public UDPEchoServer(int port) throws SocketException {
        datagramSocket = new DatagramSocket(port);
    }
    //启动服务器方法
    public void start() throws IOException {
        System.out.println("服务器启动");
        //因为服务器要不停的接收处理数据 所以我们将它设置为一个无限循环
        while(true){
            //我们先来创建一个DatagramPacket类用来接收从客户端接收到的数据
            DatagramPacket requsePacket = new DatagramPacket(new byte[1024],1024);
            //接收客户端发来的数据 当没有接收到数据的时候 这个方法会阻塞等待
            datagramSocket.receive(requsePacket);
            //我们为了方便处理 将接收到的数据转换为字符串进行处理
            String requse = new String(requsePacket.getData(),0,requsePacket.getLength());
            //处理数据
            String repouse = process(requse);
            //打包数据报 其中将要返回的数据 以及端口号都要放入
            DatagramPacket repousePacket = new DatagramPacket(repouse.getBytes(), repouse.getBytes().length,
                    requsePacket.getSocketAddress());
            //发送数据报
            datagramSocket.send(repousePacket);
            //打印查看数据
            System.out.printf("[%s : %d] req : %s ; reps : %s\n",requsePacket.getAddress().toString(),
                    requsePacket.getPort(),requse,repouse);
        }
    }
    //因为我们写的是回显服务器 所以直接返回接收到的字符串即可
    private String process (String requse) {
        return requse;
    }

    public static void main (String[] args) throws IOException {
        UDPEchoServer udpEchoServer = new UDPEchoServer(8848);
        udpEchoServer.start();
    }
}

来解读一下这个代码

  1. 读取客户端发来的请求
  2. 根据请求计算机响应
  3. 把响应写回到客户端
  4. 站在服务器的角度:
    源IP:本机的IP 源端口:指定的端口(服务器绑定的端口号) 目的IP:包含在 收到的数据报中 目的端口:包含在收到的数据报中协议类型:UDP

我们来解答一个问题 为啥服务器上来就接收 而不是发送呢?
因为服务器的定义 就是被动接收请求的这一方 主动发起请求的这一方叫做客户端


那么我们怎么来验证这个代码呢?此时我们还需要来完成一个客户端 用来向服务器发送数据并且获取到服务器返回的数据之后再打印到屏幕上


public class UDPEchoClient {
    private DatagramSocket datagramSocket = null;
    private String IP;//目的IP
    private int port;//目的端口

    public UDPEchoClient(String IP,int port) throws SocketException {
        //此处我们不分配端口号 为了我们的客户端程序可以主动分配一个可用的端口号
        datagramSocket = new DatagramSocket();
        this.IP = IP;
        this.port = port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        while(true){
            //提醒客户端输入
            System.out.printf("-->");
            //获取客户端的输入
            String requse = scanner.nextLine();
            //打包为UDP数据报 其中要放入要传入的目的端口 目的IP
            DatagramPacket requsePacket = new DatagramPacket(requse.getBytes(),requse.getBytes().length,
                    InetAddress.getByName(IP) ,port);
            //发送数据报
            datagramSocket.send(requsePacket);
            //创建对象用于接收服务发回的数据报
            DatagramPacket repousePacket = new DatagramPacket(new byte[1024],1024);
            //将获取到的数据报填充到数据报中
            datagramSocket.receive(repousePacket);
            //打印
            String repouse = new String(repousePacket.getData(),0,repousePacket.getLength());
            System.out.printf("req : %s : reps : %s\n",requse,repouse);
        }
    }

    public static void main (String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient("127.0.0.1",8848);
        udpEchoClient.start();
    }
}

在这里插入图片描述

好了 代码大功告成 我们来尝试着运行一下这个代码
在这里插入图片描述
我们也可以为这个回显服务来升级一下 我们来做一个简单的英文翻译词典
实现思路很简单 我们之前已经完成了回显服务器 我们只需要对返回方法进行处理即可 看以下代码

public class UDPEchoDicServer extends UDPEchoServer{
    //为了方便查询 也为了可以保证我们的查找速度 我们选择哈希表来做一个数据存储
    private Map<String,String> map = new HashMap<>();

    public UDPEchoDicServer (int port) throws SocketException {
        super(port);
        //我们只做一个简单的词库
        map.put("dog","小狗");
        map.put("cat","小猫");
        map.put("synchronized","同步");
    }

    @Override
    public String process (String requse) {
        //因为我们继承了处理后返回方法 所以直接继承之后重写即可
        return map.getOrDefault(requse,"暂时未学习该单词");

    }

    public static void main (String[] args) throws IOException {
        UDPEchoDicServer udpEchoDicServer = new UDPEchoDicServer(8848);
        udpEchoDicServer.start();
    }
}

接下来完成我们的客户端代码

public class UDPEchoClient {
    private DatagramSocket datagramSocket = null;
    private String IP;//目的IP
    private int port;//目的端口

    public UDPEchoClient(String IP,int port) throws SocketException {
        //此处我们不分配端口号 为了我们的客户端程序可以主动分配一个可用的端口号
        datagramSocket = new DatagramSocket();
        this.IP = IP;
        this.port = port;
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        while(true){
            //提醒客户端输入
            System.out.printf("-->");
            //获取客户端的输入
            String requse = scanner.nextLine();
            //打包为UDP数据报 其中要放入要传入的目的端口 目的IP
            DatagramPacket requsePacket = new DatagramPacket(requse.getBytes(),requse.getBytes().length,
                    InetAddress.getByName(IP) ,port);
            //发送数据报
            datagramSocket.send(requsePacket);
            //创建对象用于接收服务发回的数据报
            DatagramPacket repousePacket = new DatagramPacket(new byte[1024],1024);
            //将获取到的数据报填充到数据报中
            datagramSocket.receive(repousePacket);
            //打印
            String repouse = new String(repousePacket.getData(),0,repousePacket.getLength());
            System.out.printf("req : %s : reps : %s\n",requse,repouse);
        }
    }

    public static void main (String[] args) throws IOException {
        UDPEchoClient udpEchoClient = new UDPEchoClient("127.0.0.1",8848);
        udpEchoClient.start();
    }
}

有细心的小伙伴已经发现了 其实我们的客户端并没有做任何修改 我们直接接受返回的结果即可 至于为啥 嘿嘿 这就好比我们使用一款软件 这个软件其实更新的频率并不低 但是我们并不明确他更新的代码是什么 功能有时候也并不明确 但是客户端只需要使用就可以了呀 完全不影响我们的使用
我们来看一下效果吧
在这里插入图片描述


TCP

ServerSocket

提供给服务器使用的类(专门给服务器用的)

Socket

主要通过这样的类描述一个socket文件即可 而不需要专门的类表示"传输的包" 因为是面向字节流 以字节为单位传输的(既需要给服务器用 又需要给客户端用)

我们来尝试写一下TCP版本的代码

我们还是做一个TCP版本的回显服务器 之后再升级为词典服务器

public class TCPEchoServer {
    private ServerSocket socket;
    public TCPEchoServer(int port) throws IOException {\
        //创建唯一的端口号
        socket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            //获取客户端发送来的连接
            Socket clientSocket = socket.accept();
            //处理连接后的工作
            SocketConnection(clientSocket);
        }
    }

    private void SocketConnection (Socket clientSocket) throws IOException {
        System.out.printf("[%s : %d]客户端上线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //采用此方法能大大减少代码量
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while(true){
                //我们约定当输入回车的时候结束输入 
                //当客户端断开连接后我们服务器也要跟客户端断开连接
                if(!scanner.hasNext()){
                    System.out.printf("[%s : %d]客户端下线\n",clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                String requse = scanner.next();
                String repouse = process(requse);
                printWriter.write(repouse);
                printWriter.flush();//这个刷新一定要 否则客户端无法知道数据已经传输完毕
                System.out.printf("[%s : %d] req : %s : reps %s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),requse,repouse);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //与客户端断开连接  也就是释放这个连接
            clientSocket.close();
        }
    }
    public String process (String requse) {
        return requse;
    }

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

ok 我们当前已经完成了服务器的代码 为了验证服务器 我们来做一个客户端端口

public class TCPEchoClient {
    private Socket socket;

    public TCPEchoClient(String ip,int port) throws IOException {
        socket = new Socket(ip,port);
    }

    public void start(){
        System.out.println("客户端已经连接成功");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            Scanner SocketScanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while(true){
                System.out.printf("-->");
                String requse = scanner.next();
                printWriter.println(requse);
                printWriter.flush();
                String repouse = SocketScanner.next();
                System.out.printf("req : %s : reps : %s\n",requse,repouse);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

大功告成 我们来看一下代码效果
在这里插入图片描述
当我们客户端连接的时候 我们的服务器这边会显示到有客户端连接到 那么我们思考一个问题 我们对于一个服务器 是应该一对一服务一个客户端呢 还是一个服务器服务多个客户端 yes 就是要服务多个客户端 而我们当前的代码还有这个致命的缺陷 当前的服务器只能进行一对一的服务 无法做到一对多的服务
而我们也是知道的 当对于一个代码编译第二次的时候会提醒你重新编译运行吗 我们只需要这样操作即可打开能打开多个窗口的功能 ~
在这里插入图片描述
做完以上操作之后 我们来尝试的运行多个客户端吧
在这里插入图片描述
我们会发现当第二个客户端打开的时候 我们是无法进行连接处理的 这是因为我们在编写服务器代码的时候 采用了一个where循环当有一个客户端连接进来之后 其他服务只能一直等着这个客户端断开连接 在这里插入图片描述
所以 为了解决这个问题 我们决定引入多线程 每当一个客户端发起连接的时候 我们创建一个线程去处理这个 并不会影响我们继续接收其他客户端发来的连接请求 我们将这一段代码进行简单的修改
在这里插入图片描述
好 我们再来看一下加入多线程之后的效果
在这里插入图片描述
我们创建了五个客户端 并且对服务器发送请求都被一一处理掉了
问题又来喽
UDP的服务器会不会出现这样的问题呢?
答案是不会的 这又要说起我们的UDP和TCP 他们之间的不同 UDP是无连接的 他只会处理接收来的请求 但是TCP是有连接的 要先进行连接才可以进行处理


我们再来实现一个TCP的词典服务器吧~

public class TCPEchoDicServer extends TCPEchoServer{

    private Map<String,String> map = new HashMap<>();
    
    public TCPEchoDicServer (int port) throws IOException {
        super(port);
        map.put("dog","小狗");
        map.put("cat","小猫");
        map.put("synchronized","同步");
    }

    @Override
    public String process (String requse) {
        return map.getOrDefault(requse,"暂未学习该单词");
    }

    public static void main (String[] args) throws IOException {
        TCPEchoDicServer tcpEchoDicServer = new TCPEchoDicServer(8888);
        tcpEchoDicServer.start();
    }
}

还是刚刚的思路 客户端的代码我们还是不变 我们直接上效果图
在这里插入图片描述


总结一下 这篇博客主要总结的是TCP和UDP的区别 以及举例了socket代码的实现来凸显他们之间的区别

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

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

相关文章

什么年代了?不会还有人不会插件化吧?

一&#xff0e;到底什么是插件化开发 插件化开发是将整个app拆分成很多模块&#xff0c;这些模块包括一个宿主和多个插件&#xff0c;每个模块都是一个apk&#xff0c;最终打包的时候将宿主apk和插件apk分开或者联合打包。 为什么要将一定要使用插件化开发呢&#xff1f; 宿…

【Linux】一文带你掌握Linux权限!

Linux权限 问题一&#xff1a;Linux下的用户分类有哪些&#xff1f;问题二&#xff1a;什么叫做权限&#xff1f;问题三&#xff1a;见一见没有权限是什么现象(看看就好了)问题四&#xff1a;权限的修改问题。chmodchownchgrp 问题五&#xff1a;其他问题(三个小问题)文件的初始…

51单片机(十五)直流电机驱动(PWM)

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

【并发编程】深入理解Java并发之synchronized实现原理

文章目录 一、synchronized 实现同步示例二、synchronized 典型错误示例三、Java 对象头与 Monitor四、synchronized代码块底层原理五、synchronized方法底层原理六、Java虚拟机对synchronized的优化 一、synchronized 实现同步示例 public class MyThread extends Thread{pri…

分布式事务之Seata讲解

文章目录 1 Seata1.1 简介1.2 架构1.3 四种事务模式1.3.1 XA1.3.1.1 定义1.3.1.2 优缺点1.3.1.3 代码中实现 1.3.2 AT1.3.2.1 定义1.3.2.2 全局锁1.3.2.2.1 AT模式脏写问题1.3.2.2.2 全局锁 1.3.2.3 AT模式优缺点1.3.2.4 与XA模式区别1.3.2.5 代码中实现 1.3.3 TCC模式1.3.3.1 …

云服务器配合CookieCloud插件,实现浏览器网站Cookie同步

CookieCloud是由方糖开发的一个浏览器网站Cookie同步工具&#xff0c;Cookie是一个可以短时间代表我们登录身份的凭证&#xff0c;CookieCloud同步Cookie其实就是在同步登录状态&#xff0c;由一个浏览器插件和一个可以自行搭建的服务器端组成&#xff0c;可以定时地、在本地加…

linux pl320 mbox控制器驱动分析 - (2) 消息传递示例

linux pl320 mbox控制器驱动分析 - &#xff08;2&#xff09;消息传递示例 1 Messaging from Core0 to Core12 Back-to-back messaging from Core0 to Core13 Messaging from Core0 to Cores 1, 2, and 3 using Auto Acknowledge4 Auto Link messaging from Core0 to Core1 us…

算法设计 || 第5题:钓鱼问题-北京大学网站在线算法题(贪心算法)

目录 &#xff08;一&#xff09;题目网址视频网址 &#xff08;二&#xff09;手写草稿思考 Part1: 慕课PPT Part2: 笨蛋的学习 &#xff08;一&#xff09;题目网址视频网址 北京大学网站在线算法题&#xff1a;1042 -- Gone Fishing (poj.org) 视频讲解&#xff08;北…

进一步了解傅里叶变换的应用(附案例代码)

傅里叶变换&#xff08;Fourier Transform&#xff09;是一种非常常见的数学工具&#xff0c;能够将一个函数&#xff08;或时域信号&#xff09;分解成一些基本频率的合成。它使我们可以将时域信号&#xff08;例如波形图&#xff09;转换成频域信号&#xff0c;因而更容易地看…

ArcGIS按比例缩放图斑

今天介绍一下&#xff0c;在ArcGIS中&#xff0c;当我们需要按比例缩放图斑时&#xff0c;该如何操作呢。 可以使用“缩放”工具对要素进行缩放&#xff08;使整个要素变大或变小&#xff09;。在处理比例略有误差的数据&#xff08;例如&#xff0c;来自多个源的细分宗地&…

ChatGPT背后的大预言模型 以及《ChatGPT全能应用一本通》介绍

大型语言模型已经彻底改变了我们通过自然语言处理进行交互的方式&#xff0c;使得更多的基于语言的应用程序成为可能&#xff0c;例如语言翻译&#xff0c;问答&#xff0c;文本摘要和聊天机器人。 由于这些模型是在大量文本数据集&#xff08;如书籍&#xff0c;文章和社交媒…

C learning_13 操作符前篇(条件操作符、 逗号表达式、 下标引用、函数调用和结构成员、 表达式求值)

目录 条件操作符 逗号表达式 下标引用、函数调用和结构成员 1. [ ] 下标引用操作符 2. ( ) 函数调用操作符 3. 访问一个结构的成员 表达式求值 1.隐式类型转换 2.算术转换 3.操作符的属性 条件操作符 条件操作符是一种用于简化条件表达式的运算符。它通常表示为问号 …

《游戏编程模式》--重访设计模式--学习

序 在线阅读地址&#xff1a; 命令模式 Design Patterns Revisited 游戏设计模式 (tkchu.me) 参考文章&#xff1a; GameDesignPattern_U3D_Version/Assets/002FlyweightPattern at master TYJia/GameDesignPattern_U3D_Version GitHub 看到了没见过的观点&#xff1a;…

我的算法基础实验代码-下篇

第一题 题目介绍 输入一些数值&#xff0c;求出现的次数最多的数。如果有多个并列&#xff0c;则从大到小输出。 解题思路 代码实现 package com.nineWeek;import java.util.*;/*** author WangYH* version 2021.1.3* date 2023/5/7 18:29*/public class NumMostTimes {pu…

第十四届蓝桥杯b组c/c++

D:飞机降落&#xff08;全排列&#xff09; #include<iostream> #include<cstring> using namespace std;const int N 12; int n; struct node{int t, d, l; //t为此飞机的最早降落时间 d为盘旋时间 l为降落所需时间 }p[N]; bool st[N];//DFS求全排列模型 bool d…

【真题解析】系统集成项目管理工程师 2021 年下半年真题卷

本文为系统集成项目管理工程师考试(软考) 2021 年上半年真题&#xff0c;包含答案与详细解析。考试共分为两科&#xff0c;成绩均 ≥45 即可通过考试&#xff1a; 综合知识&#xff08;选择题 75 道&#xff0c;75分&#xff09;案例分析&#xff08;问答题 4 道&#xff0c;75…

ChatGPT 学习与使用总结

ChatGPT 学习与使用总结 最近ChatGPT大火&#xff0c;2023有可能就是AGI元年了。近两个月使用下来&#xff0c;ChatGPT给我最深刻的感觉就是它所具备的理解和思维能力&#xff0c;第一次体验时真的是非常震撼&#xff0c;完全是之前各种『人工智障』智能助理所不能比拟的&…

Windows系统出现蓝屏怎么办?这些方法可以修复!

Windows 系统蓝屏死机&#xff08;BSOD&#xff09;&#xff0c;也被称为“停止错误”&#xff0c;是Windows系统最常见的故障之一。 当Windows遇到严重的故障时就会显示蓝屏&#xff0c;系统崩溃。蓝屏上显示一个停止代码&#xff0c;如"MEMORY_MANAGEMENT"&#xf…

NeRF与三维重建专栏(一)领域背景、难点与数据集介绍

前言 关于该系列专栏&#xff0c;主要介绍NeRF在三维重建中的应用&#xff08;这里我们特指MVS&#xff0c;multi-view stereo&#xff0c;也即输入带位姿的图片&#xff0c;输出三维结构例如点云、mesh等&#xff1b;并且后面的工作也都是围绕MVS with NeRF讨论的。虽然也有w…

人类与ChatGPT:互动中的共同进步

一、ChatGPT的发展历程 1. GPT模型 ChatGPT是由OpenAI推出的一款聊天机器人&#xff0c;其核心技术基于GPT模型。GPT模型&#xff08;Generative Pre-training Transformer&#xff09;是一种基于Transformer结构的预训练语言模型。它在大规模的文本语料库上进行无监督的预训…