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

TCP协议与UDP协议

目录

一、TCP与UDP的特点

(一)有连接VS无连接

 (二)可靠传输VS不可靠传输

(三)面向字节流VS面向数据报

(四)全双工VS半双工

二、UDP协议中的socket api

(一)DatagramSocket类

(二)DatagramPacket类

(三)InetSocketAddress类

三、UDP协议的回显服务器

(一)UdpEchoServer回显服务器

(二)UdpEchoClient客户端 

(三)拓展:英译汉服务器 

四、TCP协议中的socket api

(一)ServerSocket类

(二)Socket类

五、TCP协议的回显服务器

(一)TcpEchoServer回显服务器

(二)TcpEchoClient客户端


一、TCP与UDP的特点

TCP的特点:

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

UDP的特点:

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

(一)有连接VS无连接

这是抽象的概念,指的是虚拟的/逻辑上的连接。

  • 对于TCP来说,TCP协议中,就保存了对端的信息: A和B通信,A和B先建立连接,让A保存B的信息,B保存A的信息(彼此之间知道要连接的是哪个)。
  • 对于UDP来说,UDP协议本身,不保存对方的信息,就是无连接。

 (二)可靠传输VS不可靠传输

在网络上,数据是非常容易出现丢失的情况的(丢包),光信号/电信号都可能受到外界的干扰。

在进行通信时,不能指望一个数据包100%地到达对方。

  • 可靠传输指的是,虽然不能保证数据包100%到达,但是能尽可能提高传输成功的概率。
  • 不可靠传输只是把数据发了,就不管了。

(三)面向字节流VS面向数据报

  • 面向字节流指的是在读写数据时,以字节为单位。
  • 面向数据报指的是读写数据时,以数据报为单位。

(四)全双工VS半双工

  • 全双工指的是 一个通信链路中,支持双向通信(能读也能写)。
  • 半双工指的是 一个通信链路中,只支持单向通信(要么读,要么写)。

二、UDP协议中的socket api

计算机中的“文件”,是一个广义的概念,文件还能代指一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的)。

UDP协议是用来操作网卡的,将网卡抽象成socket文件,操作网卡的时候,流程和操作普通文件差不多。

(一)DatagramSocket类

DatagramSocket类是用来操作socket文件,发送和接收数据报的。

构造方法:

方法签名方法说明
DatagramSocket()创建一个UDP数据报套接字的Socket,绑定到主机的任意一个随机端口号(一般用于客户端)。
DatagramSocket(int port)创建一个UDP数据报套接字的Socket,绑定到主机的一个指定的端口号(一般用于服务端)。

成员方法: 

方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接受到数据报,该方法会阻塞等待)。
void send(DatagramPacket p)从此套接字发送数据报(不会阻塞等待,直接发送)。
void close()关闭此数据报套接字。

(二)DatagramPacket类

DatagramPacket就是UDP发送和接收的数据报。

构造方法:

方法签名方法说明
DatagramPacket(byte[]buf,int length)构造一个DatagramPacket用来接收数据报,接收的数据报保存在字节数组中(第一个参数buf),接收的指定长度(第二个参数length)。
DatagramPacket(byte[]buf,int offset,int length,SocketAddress address)构造一个DatagramPacket用来接收数据报,接收的数据报保存在字节数组中(第一个参数buf),指定起点(第二个参数offset),接收的指定长度(第三个参数length)。address指定目的主机的IP和端口号。

成员方法:

方法签名方法说明
InetAddress getAddress()从接收的数据报中,获取发送端的主机IP地址;或从发送的数据报中,获取接收端的主机IP地址。
int getPort()从接收的数据报中,获取发送端的主机的端口号;或从发送的数据报中,获取接收端的主机的端口号。
byte[] getData()获取数据报中的数据。

(三)InetSocketAddress类

构造UDP发送的数据报时,需要传入SocketAddress(父类),该对象可以使用InetSocketAddress(子类)来创建。

InetSocketAddress的构造方法:

方法签名方法说明
InetSocketAddress(InetAddress addr,int port)创建一个Socket地址,包含IP地址和端口号

三、UDP协议的回显服务器

Java数据报套接字通信模型:

(一)UdpEchoServer回显服务器

package NetWork;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;//UDP协议的回显服务器
//服务器端
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);//2.根据请求, 计算响应. (服务器最关键的逻辑)//把读取到的二进制数据, 转成字符串. 只是构造有效的部分.String request=new String(RequestPacket.getData(),0, RequestPacket.getLength());String response=process(request);//3.把响应返回给客户端//根据 response 构造 DatagramPacket, 发送给客户端.//此处不能使用 response.length(),因为这是String的长度而不是byte数组的长度DatagramPacket ResponsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,RequestPacket.getSocketAddress());//发送构建好的数据报socket.send(ResponsePacket);//4.打印日志System.out.printf("[%s:%d] req: %s, resp: %s\n", RequestPacket.getAddress().toString(), RequestPacket.getPort(), request, response);}}//服务器根据请求,处理业务public String process(String request){return request;}public static void main(String[] args) throws IOException {UdpEchoServer server=new UdpEchoServer(9090);server.start();}}

(二)UdpEchoClient客户端 

package NetWork;import java.io.IOException;
import java.net.*;
import java.util.Scanner;//UDP协议的回显服务器
//客户端
public class UdpEchoClient {DatagramSocket socket=null;// 客户端要给服务器发送数据报,首先得知道服务器的IP和端口号private String ServerIp;//目的IPprivate int ServerPort;//目的端口号// 和服务器不同, 此处的构造方法是要指定访问的服务器的地址.public UdpEchoClient(String serverIp, int serverPort) throws SocketException {this.ServerIp = serverIp;this.ServerPort = serverPort;socket = new DatagramSocket();}public void start() throws IOException {Scanner sc=new Scanner(System.in);while(true){// 1.读取用户输入的内容System.out.println("请输入要发送的内容:");if(!sc.hasNext()){break;}String request=sc.next();// 2. 把请求发送给服务器, 需要构造 DatagramPacket 对象.// 构造过程中, 不光要构造载荷, 还要设置服务器的 IP 和端口号DatagramPacket RequestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(ServerIp),ServerPort);// 3. 发送数据报socket.send(RequestPacket);// 4. 接收服务器的响应DatagramPacket ResponsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(ResponsePacket);// 5. 从服务器读取的数据进行解析, 打印出来.String response = new String(ResponsePacket.getData(), 0, ResponsePacket.getLength());System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();}
}

(三)拓展:英译汉服务器 

当我们需要实现另外一个简单的服务器时,例如英译汉服务器,只需要继承然后重写process方法就可以了。

package NetWork;import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;//英译汉服务器
public class UdpDictServer extends UdpEchoServer{private HashMap<String,String> dict=new HashMap<>();//要在子类的构造方法中调用父类的构造方法//构造方法初始化字典public UdpDictServer(int port) throws SocketException {super(port);dict.put("apple","苹果");dict.put("boy","男孩");dict.put("cat","小猫");dict.put("dog","小狗");}//重写process方法public String process(String request){return dict.getOrDefault(request,"没有找到该词汇");}public static void main(String[] args) throws IOException {UdpDictServer DictServer=new UdpDictServer(9090);DictServer.start();}
}

四、TCP协议中的socket api

(一)ServerSocket类

ServerSocket是创建TCP服务器端Socket的API。

构造方法:

方法签名方法说明
ServerSocket(int port)创建一个服务器端套接字Socket,并绑定到指定端口。

成员方法:

方法签名方法说明
Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务器端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待。
void close()关闭此套接字

(二)Socket类

Socket类是客户端socket,或服务器端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

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

构造方法:

方法签名方法说明
Socket(String host,int port)创建一个客户端套接字Socket,并对应IP的主机上对应端口的进程进行连接。

成员方法:

方法签名方法说明

InetAddress getInetAddress()

返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutPutStream()返回此套接字的输出流

五、TCP协议的回显服务器

(一)TcpEchoServer回显服务器

package NetWork;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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket serverSocket=null;public TcpEchoServer(int port) throws IOException {serverSocket=new ServerSocket(port);}public void start() throws IOException {System.out.println("启动服务器");// 这种情况一般不会使用 fixedThreadPool, 意味着同时处理的客户端连接数目就固定了.ExecutorService executorService = Executors.newCachedThreadPool();while (true) {// tcp 来说, 需要先处理客户端发来的连接.// 通过读写 clientSocket, 和客户端进行通信.// 如果没有客户端发起连接, 此时 accept 就会阻塞.// 主线程负责进行 accept, 每次 accept 到一个客户端, 就创建一个线程, 由新线程负责处理客户端的请求.Socket clientSocket = serverSocket.accept();// 使用多线程的方式来调整// Thread t = new Thread(() -> {// processConnection(clientSocket);// });// t.start();// 使用线程池来调整executorService.submit(() -> {processConnection(clientSocket);});}}private void processConnection(Socket clientSocket){//对clientSocket进行读写操作System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());try(InputStream inputStream=clientSocket.getInputStream();OutputStream outputStream=clientSocket.getOutputStream()){// 针对 InputStream 套了一层Scanner scanner = new Scanner(inputStream);// 针对 OutputStream 套了一层PrintWriter writer = new PrintWriter(outputStream);while(true){//因为输入流中的数据是持续读取的,要加上循环// 1. 读取请求并解析. 可以直接 read, 也可以借助 Scanner 来辅助完成.if (!scanner.hasNext()) {//scanner.hasNext():判断输入流中是否还有 “下一个令牌”(默认以空白字符分割,如空格、换行等)。// 连接断开了System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}// 2. 根据请求计算响应String request=scanner.next();String response=process(request);// 3. 返回响应到客户端// outputStream.write(response.getBytes());writer.println(response);//将缓存区中的数据都发送出去,避免残留writer.flush();// 打印日志System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request, response);}} catch (IOException e) {throw new RuntimeException(e);} finally {try {//服务器连接一个客户端就要创建一个clientSocket,使用完就要关闭.clientSocket.close();} catch (IOException e) {throw new RuntimeException(e);}}}private String process(String request){return request;}public static void main(String[] args) throws IOException {TcpEchoServer tcpEchoServer=new TcpEchoServer(9090);tcpEchoServer.start();}
}

(二)TcpEchoClient客户端

package NetWork;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 {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {// 直接把字符串的 IP 地址, 设置进来.// 127.0.0.1 这种字符串socket = new Socket(serverIp, serverPort);}public void start()throws IOException{Scanner scanner=new Scanner(System.in);try(InputStream inputStream=socket.getInputStream();OutputStream outputStream=socket.getOutputStream()){//给inPutStream套一层Scanner scannerNet= new Scanner(inputStream);//给outPutStream套一层PrintWriter writer=new PrintWriter(outputStream);while (true){//1.读取用户输入String request=scanner.next();//2.发送请求并刷新缓存区数据writer.println(request);writer.flush();//3.接收服务器的响应String response=scannerNet.next();//4.打印出响应System.out.println(response);}}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();}
}

注意点:

  • 在服务器中,采用多线程的方式来处理客户端的请求(使用线程池)。因为如果是单线程有多个客户端连接,当程序处理processConnection请求时,就可能阻塞在processConnection,而不能accpet。
  • 因为服务器中有scanner.hasNext来判断发来的请求,所以客户端发送的请求要以换行符/空白符号结束,因此发送时用writer.println。
  • 发送请求后记得使用flush刷新缓冲区的数据。
http://www.xdnf.cn/news/1246843.html

相关文章:

  • 智慧能源场景设备缺陷漏检率↓76%:陌讯多模态融合检测方案实战解析
  • Redis备份方案:持久化与外部工具全解析
  • JVM(Java Virtual Machine,Java 虚拟机)超详细总结
  • Spring之【详解FactoryBean】
  • C++ 网络编程入门:TCP 协议下的简易计算器项目
  • 数据结构04 栈和队列
  • 工业级 CAN 与以太网桥梁:串口服务器CAN通讯转换器深度解析(下)
  • Dot1x认证原理详解
  • ChatGPT以及ChatGPT强化学习步骤
  • 数据结构(三)双向链表
  • VSCode中使用Qt
  • 7、Redis队列Stream和单线程及多线程模型
  • Pandas query() 方法详解
  • SpringBoot3.x入门到精通系列:4.2 整合 Kafka 详解
  • 基于deepSeek的流式数据自动化规则清洗案例【数据治理领域AI带来的改变】
  • 2025-08-05Gitee + PicGo + Typora搭建免费图床
  • FPGA设计思想与验证方法学系列学习笔记003
  • springboot + maven 使用资源占位符实现动态加载配置文件
  • 【springcloud的配置文件不生效】
  • Linux 系统启动原理2
  • Occ3D: A Large-Scale 3D Occupancy Prediction Benchmark for Autonomous Driving
  • Unity开发者快速认识Unreal 的C++(四)Pawn和Actor
  • 智慧城市SaaS平台|市容环卫管理系统
  • Spring-rabbit使用实战六
  • Could not load the Qt platform plugin “xcb“ in “无法调试与显示Opencv
  • 类内部方法调用,自注入避免AOP失效
  • RK3568 Linux驱动学习——字符设备驱动开发
  • 森赛睿科技成为机器视觉产业联盟会员单位
  • C++ - 仿 RabbitMQ 实现消息队列--服务端核心模块实现(六)
  • Vue.js 教程