当前位置: 首页 > news >正文

一文理解TCP与UDP


Socket套接字

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。 基于Socket套接字的网络程序开发就是网络编程。

Socket套接字主要针对传输层协议划分为如下三类:

  1. 流套接字:使用传输层TCP协议
  2. 数据报套接字:使用传输层UDP协议
  3. 原始套接字

那么这三种协议有什么不同呢?接下来我们就一个一个认识,

传输层TCP协议

TCP,即Transmission Control Protocol(传输控制协议),传输层协议。

以下为TCP的特点:

  1. 有连接
  2. 可靠传输
  3. 面向字节流
  4. 有接收缓冲区,也有发送缓冲区
  5. 大小不限

注意:下文会讲解这些特点到底是什么意思。

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的 情况下,是⽆边界的数据,可以多次发送,也可以分开多次接收。

传输层UDP协议

UDP,即User Datagram Protocol(用户数据报协议),传输层协议。 以下为UDP的特点

  1. 无连接
  2. 不可靠传输
  3. 面向数据报
  4. 有接收缓冲区,无发送缓冲区
  5. 大小受限:一次最多传输64k

原因: 我们注意到, UDP协议首部中有⼀个16位的最大长度. 也就是说一个UDP能传输的数据最大长度是 64K(包含UDP首部).

  1. 注意:下文会讲解这些特点到底是什么意思。

对于数据报来说,可以简单的理解为,传输数据是⼀块⼀块的,发送⼀块数据假如100个字节,必须⼀ 次发送,接收也必须⼀次接收100个字节,⽽不能分100次,每次接收1个字节。

至于第三种就不详细讲了,想了解的可以自己找资料来了解哦。这里就简单讲作用:

原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。 我们不学习原始套接字,简单了解即可。

下面讲解一下上面tcp所说的特点:

  1. 有连接:在数据传输开始之前,必须在两端建立一个连接。这通过一个称为三次握手的过程完成。
  2. 可靠性:TCP确保数据正确无误地从源传输到目的地。它通过序列号、确认应答、重传机制等来实现。但也不是绝对安全的,只是在传输中尽量保证数据正确无误。
  3. 全双工通信:TCP允许通信双方同时发送和接收数据。可以简单理解为:可以从左到右,也可从右到左,左右代表通信双方
  4. 字节流:虽然应用程序可能发送的是消息或数据块,但TCP将它们视为字节流。后面代码中就会发现我们使用TCP网络编程时会使用到输出流和输入流即:InputStream和OutputStream。
  5. 面向数据包:UDP是面向报文的,只在应用层交下来的报文前增加了首部后就向下交付网络层。它指的是协议在传输数据时将数据封装成一个个独立的数据单元,这些数据单元被称为数据包。

当然上面对两种协议的解释是比较简陋的。


UDP数据报套接字

想要学会实现UDP数据报套接字,那就先要认识两个类 DatagramSocket和 DatagramPacket (也可以说是两个api)

两个类的主要作用:

DatagramSocket:用于发送和接收数据的。

DatagramPacket: 是DatagramSocket发送和接收的数据报。

一个形象的比喻:一个是快递车(DatagramSocket),一个是包裹(DtagramPacket)


DtagramPacket的主要方法:

DatagramPacket(byte[] buf, int length):构造⼀个DatagramPacket以⽤来接收数据报,接收的 数据保存在字节数组(第⼀个参数buf)中,接收指定 ⻓度(第⼆个参数length)

DatagramPacket(byte[] buf, int offset, int length, SocketAddress address): 构造⼀个DatagramPacket以⽤来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定⻓ 度(第⼆个参数length)。address指定⽬的主机的IP 和端⼝号


DtagramSocket的主要方法:

InetAddress getAddress() :从接收的数据报中,获取发送端主机IP地址;或从发 送的数据报中,获取接收端主机IP地址

int getPort(): 从接收的数据报中,获取发送端主机的端口号;或从 发送的数据报中,获取接收端主机端口号


下面就是服务端和客户端实现一个简单的回响服务端(即收到并返回客户端发来的请求)和客户端

服务端:

首先我们就需要先构造一个DatagramSocket,我们这里就先创建一个没有实例化的DatagramSocket socket,然后在构造方法中实例化这个socket对象,并且我们就传入一个端口号port通过调用socket的带参数的方法进行设置该服务端的端口号。

然后设置一个启动服务端的方法start(),在start中我们的服务器上线,然后我们需要用一个while循环不断区接收来自客户端的请求。这里我们先初始化一个DatagramPacket对象,然后调用DatagramSocket的receive方法并把DatagramPacket对象传入进方法中进行获取请求,这时请求就是储存在这个DatagramPacket中。

再然后把DatagramPacket对象的数据通过new String(packet.getData(),0, packet.getLength())获取String类型的请求,并通过process处理请求获取响应respond。

最后将String类型的respond转化为一个DatagramPacket对象的数据(相当于打包包裹)这里的包裹与接收不一样,发送时需要加上要发送去的目标客户端的ip和端口号,我们可以使用packet.getSocketAddress()获取这两样东西放入构造方法中即可,然后交给快递车发货(即:DatagramSocket)

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpService {DatagramSocket socket;public UdpService(int port) throws SocketException {socket=new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器启动");while(true){DatagramPacket packet=new DatagramPacket(new byte[4096],4096);socket.receive(packet);String request=new String(packet.getData(),0, packet.getLength());String respond=process(request);DatagramPacket packetrespond=new DatagramPacket(respond.getBytes(),0,respond.getBytes().length,packet.getSocketAddress());socket.send(packetrespond);System.out.println("port:"+packetrespon.getPort()+"ip"+packetrespon.getAddress());}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpService service=new UdpService(9090);service.start();}}

TCP流套接字

在TCP流套接字编程中,我们要学习到两个比较重要的类(也可以说是API),那么是哪两个类呢,接下来我们就来学习一下吧。

这两个类分别是

  • ServerSocket:主要是服务端使用,是创建TCP服务端Socket的API。
  • Socket:无论是服务端还是客户端,都是需要使用这个类。

ServerSocket类

ServerSocket的构造方法是需要传入一个端口号, ServerSocket(int port) ;用来作为这个服务端的端口号。

ServerSocket常用的方法就是: Socket accept() ,其返回值为Socket

accept作用: 开始监听指定端⼝(创建时绑定的端⼝),有客⼾端 连接后,返回⼀个服务端Socket对象,并基于该 Socket建⽴与客⼾端的连接,否则阻塞等待

Socket类

Socket的构造方法需要传入两个值, Socket(String host, int port) ,一个String以及int类型,分别代表要访服务器的IP地址和服务器端口号。

不管是客户端还是服务端Socket,都是双方建立链接以后,保存的对端信息,以及用来与对方接受数据的。

Socket本身是是无法直接传输数据的,但是它可以通过本身的方法获取对应的流对象,通过流对象进行,请求和反应。以下是Socket的一些方法:

InetAddress getInetAddress() 返回套接字所连接的地址

InputStream getInputStream() 返回此套接字的输⼊流

OutputStream getOutputStream() 返回此套接字的输出流

简单模拟实现一下服务端和客户端:

服务端:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpServer {private ServerSocket serverSocket=null;public TcpServer(int port) throws IOException {serverSocket=new ServerSocket(port);}public void start() throws IOException {ExecutorService executorService= Executors.newCachedThreadPool();while(true){Socket socket=serverSocket.accept();executorService.submit(()->{processConnection(socket);});}}private String process(String request) {return request;}private void processConnection(Socket socket)  {System.out.println("客户端上线");try(InputStream inputStream=socket.getInputStream();OutputStream outputStream=socket.getOutputStream()){//套一层壳Scanner sc=new Scanner(inputStream);PrintWriter writer=new PrintWriter(outputStream);while(true) {if (!sc.hasNext()) {System.out.println("客户端下线");break;}//套壳读请求String request = sc.next();//套壳回响String respond = process(request);writer.println(respond);writer.flush();System.out.println("已经回响"+"ip:"+socket.getInetAddress());}} catch (IOException e) {throw new RuntimeException(e);}finally {try {serverSocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}public static void main(String[] args) throws IOException {TcpServer tcpServer=new TcpServer(9090);tcpServer.start();}}

服务端中:

首先创建一个TcpServer类,把这个对象作为一个服务端,这个类的成员属性就是上面所说的ServerSocket类对象开始时我们将这个类对象初始化为null,在构造方法时传入一个端口号对ServerSocket成员在进行实例化如: serverSocket=new ServerSocket(port),在这个类中我们需要创建一个start();方法作为服务器启动的一个方法。在start方法中,我们里面需要到调用一个processConnection方法,这个方法是主要处理一次链接的请求,也可以理解为一个客户端链接请求。

讲完了TcpServer里该有的基本成员,那么我们就讲讲具体的方法是如何实现的:

在start中我们先是建了一个线程池用来服务多用户一起访问这个服务器,发出请求。再使用一个while循环,不断的执行ServerSocket的accept方法进行获取Socket对象,每个线程执行processConnection方法,并将accept获取的Socket对象传给processConnection方法,开始处理一个客户端的请求并响应。

再然后就到了我们的processConnection方法,在这个方法中我们可以先是打印一个客户端上线,然后通过socket对象建立输出,输入流对象,然后可以选择用Scanner和PrintWrite进行套壳使用,然后我们通过scanner读取到来自客户端得到的请求,然后再通过一个process方法进行处理,转化返回对应得响应(这里我们主要是简单的回响服务器,即简单再返回请求的字符串即可),然后我们再使用套壳的PrintWrite对象进行给客户端响应即里的println方法,响应给服务端即可。注意的是write在执行完println后需要在执行flush方法,将写入减缓冲区的响应彻底清除响应给客户端。以及最后记得在finally中关闭Socket对象。与UDP不同,主要看生命周期。

通过主要的这三步,我们基本可以构造一个简单的回响服务端,如果想要更新成更加高级的服务器,那么我们只需要改动一下process方法即可,把里面的逻辑改一下,不在是回响请求即可。

客户端:

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 TcpClient {private Socket socketClient=null;public TcpClient(String ip,int port) throws IOException {socketClient=new Socket(ip,port);}public void start(){Scanner sc=new Scanner(System.in);try(InputStream inputStream=socketClient.getInputStream();OutputStream outputStream=socketClient.getOutputStream()){PrintWriter writer=new PrintWriter(outputStream);Scanner scanner=new Scanner(inputStream);while(true){System.out.println("请输入你的需求");String request=sc.next();writer.println(request);writer.flush();String respond=scanner.next();System.out.println(respond);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpClient tcpClient=new TcpClient("127.0.0.1",9090);tcpClient.start();}}

客户端:

  1. 客户端我们还是构造一个一个类TcpClient,然后里面先是初始化一个null的Socket对象,然后在带参的构造方法中进行实例化,其中参数分别是服务端的ip和端口号。
  2. 那么还是老样子,定义一个start方法,进行启动客户端,在里面我们还是先创建出输出流和输入流 在进行Scanner和PrintWrite进行套壳,其中我们还要额外创建一个scanner对象里面传入的是System.in进行读取控制台输入的请求,再由输入流传给服务端,然后输出流PrintWrite进行接受响应,注意write这里不要使用print,因为在服务端中我们使用了!sc.hasNext(),如果我们的请求使用的print而不是println则会使服务端读取请求时出现一些小bug。

相关文章:

  • [CSS3]百分比布局
  • 第十三届蓝桥杯国赛PythonA题解
  • epoll_wait未触发的小Bug
  • Axure应用交互设计:动态面板嵌套实现超强体验感菜单表头
  • DL00987-基于深度学习YOLOv11的红外鸟类目标检测含完整数据集
  • Java 项目管理工具:Maven 与 Gradle 的深度对比与选择
  • Spring Boot 登录实现:JWT 与 Session 全面对比与实战讲解
  • Qt初识.
  • qt---命名规范
  • 目标检测 RT-DETR(2023)详细解读
  • 嵌入式开发学习日志(linux系统编程--文件读写函数(2))Day24
  • wd软件安装
  • 百度Q1财报:总营收325亿元超预期 智能云同比增速达42%
  • QRsim:一款快速验证控制算法和无缝迁移到实物平台的无人系统3D仿真平台
  • C++ 前缀和数组
  • JQuery的基本使用
  • GitHub SSH Key 配置详细教程(适合初学者,Windows版)-学习记录4
  • 设计模式1 ——单例模式
  • 2025年保姆级教程:Powershell命令补全、主题美化、文件夹美化及Git扩展
  • 大模型知识
  • 公司在网站做广告怎么做分录/怎样下载优化大师
  • WordPress整站搬家插件/中文搜索引擎有哪些平台
  • 公司注册网站官网/2023年的新闻十条
  • 九江网站设计服务机构哪家好/创建网站的流程
  • 网站建设网站定制/百度手机助手官网