【网络】UDP和TCP之间的差别和回显服务器

简介: 【网络】UDP和TCP之间的差别和回显服务器

学习多线程,打破了以往对于程序的认知

学习网络编程,将会再次打破对于程序的认知


套接字:Socket 单词

操作系统给应用程序(传输层给应用层)提供的 API,起了个名字,就叫 Socket API

Socket 本身是“插槽”的意思

  • 电脑的主板,插着各种其他的硬件

接下来学习的就是操作系统提供的 Socket API(Java 版本的)

UDP 和 TCP 之间的差别

socket API 提供了两组不同的 APIUDP 有一套,TCP 也有一套


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

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

有连接/无连接

此处谈到的连接,是“抽象”的连接

  • 通信双方,如果保存了通信对端的信息,就相当与是“有连接”;如果不保存对端的信息,就是“无连接
  • 连接:通信双方 A 保存了 B 的信息(IP 和端口号),B 也保存了 A 的信息
  • 如果通信双方,各自把对方的信息删除掉,此时就相当与“断开了连接

举个栗子:

  • 将来你和你的另一半去领证,结婚证上就会写上两个人的名字,贴上照片。一式两份,你保存一份,你的另一半保存一份
  • 你的本上保留了 ta 的信息,你翻开本就能看到另一个人是 ta
  • ta 的本上保留了你的信息,ta 翻开本就能看到另一个人是你
  • 此时你们俩就相当于建立了“抽象的/逻辑上的连接

可靠传输/不可靠传输

此处谈到的“可靠”,不是指 100% 能到达对方,而是 “尽可能”到达对方

  • 因为网络环境非常复杂,存在很多的不确定因素(你再厉害的技术,也抵不过挖掘机一铲子)
    相对来说,不可靠就是完全不考虑数据是否能到达对方

TCP 内置了一些机制,能够保证可靠传输

  1. 感知到对方是不是收到了
  2. 重传机制,在对方没收到的时候进行重试

UDP 则没有这种可靠性机制,完全不管发出去的数据是否顺利到达对方


直观感觉,可靠比不可靠传输更好?

  • 但可靠传输要付出代价,TCP 协议设计就要比 UDP 复杂很多,也会损失一些传输数据的效率

面向字节流/面向数据报

TCP 是面向字节流的,TCP 的传输过程就和文件流/水流是一样的特点

  • 从文件读写 100 个字节
  1. 一次读写 100 字节
  2. 两次,一次读写 50 字节
  3. 十次,一次读写 10 字节
  • TCP 读写,和文件读写是一摸一样的

UDP 是面向数据报的,传输数据的基本单位不是字节,而是“UDP 数据报

  • 一次发送/接收,必须是完整的 UDP 数据报

这些差别,会直接影响到代码的写法

全双工/半双工

全双工:一个通信链路,可以发送数据,也可以接收数据(双向通信

半双工:一个通信链路,只能发送/只能接收(单向通信)

有一根网线,怎么进行双向通信呢?

  • 全双工这个事情,物理层面上,并非是只有一根线在连接
  • 一根网线里,有 8 根铜线,分成 4 4 一组(四根就可以正常工作,另外四根是防止意外情况发生的铜线备份)
  • 主要的四根线中,两根线用来负责发送,两根用来接收

UDP/TCP API 的使用

UDP API

API 就是一组函数/一组类

DatagramSocket

网卡的遥控器


代表一个 Socket 对象

  • 属于操作系统的概念,Socket就可以认为是操作系统中,广义的文件里面的一种文件类型
  • 这样的文件,就是网卡/控制台/键盘/显卡…这种硬件设备抽象的表示形式
  • 所以 Socket 也具有一些文件的特性,操作文件需要先打开、再读写、再关闭。Socket 也是这样
  • 包括创建一个 Socket 对象,也会占用一个文件描述符表里面的资源
  • 在这里Socket对象,就是网卡的代言人
  • 因为我们通过代码直接操作网卡是不好操作的
  • 网卡有很多种型号,之间提供的 API 都会有差别
  • 于是操作系统就把网卡概念封装成 Socket,应用程序员就不需要关注硬件的差异和细节,直接统一操作 Socket 对象就能间接的操作网卡了
  • Socket 就像万能遥控器一样

构造方法
方法签名 方法说明
DatagramSocket () 创建⼀个 UDP 数据报套接字的 Socket,绑定到本机任意⼀个随机端⼝(⼀般⽤于客⼾端)
DatagramSocket (int port) 创建⼀个 UDP 数据报套接字的 Socket,绑定到本机指定的端⼝需要指定端口号,⼀般⽤于服务端)
方法
方法签名 方法说明
void receive (DatagramPacket p) 从此套接字接收数据报(如果没有接收到数据报,该⽅法会阻塞等待)
void send (DatagramPacket p) 从此套接字发送数据报包(不会阻塞等待,直接发送)
void close () 关闭此数据报套接字

DatagramPacket

UDP 传输数据的基本单位


代表一个 UDP 数据报

构造方法
方法签名 方法说明
DatagramPacket(byte[] buf, int length) 构造⼀个 DatagramPacket 以⽤来接收数据报,接收的数据保存在字节数组(第⼀个参数 buf)中,接收指定 ⻓度(第⼆个参数 length
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address) 构造⼀个 DatagramPacket 以⽤来发送数据报,发送的数据为字节数组(第⼀个参数 buf)中,从 0 到指定⻓ 度(第⼆个参数 length)。address 指定⽬的主机的 IP 和端⼝号
方法
方法签名 方法说明
InetAddress getAddress() 从接收的数据报中,获取发送端主机 IP 地址;或从发送的数据报中,获取接收端主机 IP 地址
int getPort() 从接收的数据报中,获取发送端主机的端⼝号;或从发送的数据报中,获取接收端主机端口号
byte[] getData() 获取数据报中的数据

回显服务器(Echo Server

最简单的客户端服务器程序,不涉及到业务流程,只是对与 API 的用法做演示

客户端发送什么样的请求,服务器就返回什么样的响应,没有任何业务逻辑,没有进行任何计算或者处理

  • 网络编程必须要使用网卡,就需要用到Socket对象
  • 创建一个 DatagramSocket 对象,之后在基于这个对象进行操作
import java.net.DatagramSocket;  
import java.net.SocketException;  
  
public class UdpEchoServer {  
    private DatagramSocket socket = null;  
  
    public UdpEchoServer(int port) throws SocketException {  
    //SocketException 异常是 IOException 的子类
        socket = new DatagramSocket(port);  
    }
}
  • 对于服务器这一端来说,需要在 socket 对象创建的时候,就指定一个端口号 port,作为构造方法的参数
  • 后续服务器开始运行之后,操作系统就会把端口号和该进程关联起来
  • 端口号的作用就是来区分进程的,一台主机上可能有很多个进程很多个程序,都要去操作网络。当我们收到数据的时候,哪个进程来处理,就需要通过端口号去区分
  • 所以就需要在程序一启动的时候,就把这个程序关联哪个端口指明清楚

  • 在调用这个构造方法的过程中,JVM就会调用系统的Socket API,完成“端口号-进程”之间的关联动作
  • 这样的操作也叫“绑定端口号”(系统原生 API 名字就叫 bind
  • 绑定好了端口号之后,就明确了端口号和进程之间的关联关系

  • 对于一个系统来说,同一时刻,一个端口号只能被一个进程绑定;但是一个进程可以绑定多个端口号(通过创建多个Socket对象来完成)
  • 因为端口号是用来区分进程,收到数据之后,明确说这个数据要给谁,如果一个端口号对应到多个进程,那么就难以起到区分的效果
  • 如果有多个进程,尝试绑定一个端口号,只有一个能绑定成功,后来的都会绑定失败
1. 接收请求
  • 通过 start 来启动服务器的核心流程
  • 对于服务器来说,主要的工作,就是不停地处理客户端发来的请求,因为客户端什么时候会发来请求是未知的,所以要时刻待命
public void start() {  
    System.out.println("服务器启动!");  
    //通过一个死循环来不停地处理请求  
    while(true) {  
      //1. 读取客户端的请求并解析
      socket.receive();  
    }
}
  • 7*24 小时工作的服务器来说,服务器里面有死循环是很正常的,不是说死循环就是代码 bug
  1. 读取客户端的请求并解析
  • receive 是从网卡上读取数据,但是调用 receive 的时候,网卡上不一定就有数据
  • 当调用 start 方法之后程序启动,就立刻调用了 receive,一调用 receive,就会立刻从网卡中读取数据,但这个时候客户端可能还没来,网卡中还没有数据
  • 如果网卡上收到数据了,receive 立刻返回,获取收到的数据;如果没有收到数据,receive 就会阻塞等待,直到真正收到数据为止
  • 此处 receive 也是通过“输出型参数”获取到网卡上收到的数据的
  • receive的参数是DatagramPacket
  • 我们就需要构造一个空的 DatagramPacket 对象,将其作为参数传递给 receive
public void start() throws IOException {  
    System.out.println("服务器启动!");  
    //通过一个死循环来不停地处理请求  
    while(true) {  
        //1. 读取客户端的请求并解析  
        DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);  
        socket.receive(requestPacket);  
    }
}
  • DatagramPacket 自身需要存储数据,但是数据的空间具体多大,需要外部来定义,自身不负责
  • 需要指定requestPacket所需要存储数据/持有数据的基数
  • 指定一个字节数组,和其长度
  • 大小没什么讲究,只要能确保能够存储下你通讯的一个数据包即可
  • 收到的请求数据是通过二进制 byte[] 的形式来体现的,而我们后续要将其进行处理,最好将它转成字符串才好处理
public void start() throws IOException {  
    System.out.println("服务器启动!");  
    //通过一个死循环来不停地处理请求  
    while(true) {  
        //1. 读取客户端的请求并解析  
        DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);  
        socket.receive(requestPacket);  
        
      //将收到的二进制 byte[] 数据转换成字符串  
        String request = new String(requestPacket.getData(),0,requestPacket.getLength());  
    }
}
  • 构造String可以基于字节数组构造,也可以基于字符数组进行构造
  • 此处 DatagramPacket 里面持有的就是字节数组,我们就取出里面包含的字节数
  • 此处就指定了:是哪个字节数组、从哪开始构造、构造多长
2. 根据请求计算响应
  • 请求(request):客户端主动给服务器发起的数据
  • 响应(response):服务器给客户端返回的数据

此处是一个回显服务器,响应就是请求

public void start() throws IOException {  
    System.out.println("服务器启动!");  
    //通过一个死循环来不停地处理请求  
    while(true) {  
        //1. 读取客户端的请求并解析  
        DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);  
        socket.receive(requestPacket);  
        //将收到的二进制 byte[] 数据转换成字符串  
        String request = new String(requestPacket.getData(),0,requestPacket.getLength());  
  
        //2. 根据请求计算响应  
        String response = process(request);  
    }
}  
  
//请求是什么,响应就是什么  
private String process(String request) {  
    return request;  
}
3. 将响应写回客户端

此时需要主动的将数据通过网卡发送回客户端

  • receive相似,send的参数是DatagramPacket
  • 我们就需要构造一个 DatagramPacket 对象,将其作为参数传递给 send
  • 但此时不能使用空的数组来构造 DatagramPacket 对象
  • 需要使用刚刚的 response 数据进行构造
public void start() throws IOException {  
    System.out.println("服务器启动!");  
    //通过一个死循环来不停地处理请求  
    while(true) {  
        //1. 读取客户端的请求并解析  
        DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);  
        socket.receive(requestPacket);  
        //将收到的二进制 byte[] 数据转换成字符串  
        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());  
        socket.send(responsePacket);  
    }
}  
  
//请求是什么,响应就是什么  
private String process(String request) {  
    return request;  
}
  • String 可以基于字节数组来构造,也可以随时取出里面的字节数组
  • response.getBytes().length不能写成response.length
  • 前者是在获取字节数组,得到字节数组的长度,单位是“字节
  • 后者是在获取字符串中字符的个数,单位是“字符
  • UDP有一个特点——无连接
  • 所谓的连接,就是通信双方保存对方的信息(IP+端口号)
  • 就是说 DatagramSocket 这个对象中,不持有对方(客户端)和 IP 端口的,进行 send 的时候,就需要在 send 的数据包里,把要“发给谁”这样的信息,写进去,才能够正确的把数据进行返回
  • 所以要将信息也作为参数,传入responsePacket
  • 客户端刚才给服务器发了一个请求 requestPacket,这个包记录了这个数据是从哪来,从哪来就让它回哪去,所以直接获取这个 requestPacket 的信息就可以了
  • 客户端的 IP 和端口就都包含在 requestPacket.getSocketAddress()
  • 后续往外发送数据包的时候,就知道该发去哪了 >- 相比之下,TCP 代码中,因为 TCP 是有连接的,则无需关心对端的 IP 和端口,只管发送数据即可
  • 如果字符串里都是英文字母/阿拉伯数字/英文标点符号的话,都是 ASCII 编码的,一个字符也就是一个字节这么长
  • 如果字符串里有中文,是 UTF8 编码的,一个中文就是 3 个字节
  • UTF8 也是能兼容 ASCII,当使用 UTF8 表示英文的时候,和 ASCII 表示英文是完全相同的
完整代码
import java.io.IOException;  
import java.net.DatagramPacket;  
import java.net.DatagramSocket;  
import java.net.SocketException;  
  
public class UdpEchoServer {  
    private DatagramSocket socket = null;  
  
    public UdpEchoServer(int port) throws SocketException {  
        socket = new DatagramSocket(port);  
    }  
    
    public void start() throws IOException {  
        System.out.println("服务器启动!");  
        //通过一个死循环来不停地处理请求  
        while(true) {  
            //1. 读取客户端的请求并解析  
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);  
            socket.receive(requestPacket);  
            //将收到的二进制 byte[] 数据转换成字符串  
            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());  
            socket.send(responsePacket);  
  
            //4. 打印日志  
            System.out.printf("[%s:%d req=%s, res=%s\n",requestPacket.getAddress(),requestPacket.getPort(),request,response);  
        }    
    }  
  
  
    //请求是什么,响应就是什么  
    private String process(String request) {  
        return request;  
    }  
  
  
    public static void main(String[] args) throws IOException {  
        UdpEchoServer server = new UdpEchoServer(9090);  
        server.start();  
    }
}


相关文章
|
24天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
16天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
20天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2577 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
3天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
2天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
163 2
|
20天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1576 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
22天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
977 14
|
4天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
221 2
|
17天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
734 9