当前位置: 首页 > 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.dtcms.com/a/316844.html

相关文章:

  • 十六、请求响应-响应:三层架构-分层解耦
  • 信息安全的概述
  • RabbitMQ延时队列的两种实现方式
  • C++算法竞赛篇(九)字符数组题型讲解
  • 坚鹏:AI智能体软件是知行学成为AI智能体创新应用引领者的抓手
  • uvm-register-backdoor-access
  • SpringBoot AI心理学训练实战
  • 更改CodeBuddy的默认terminal为Git Bash
  • 随机森林算法详解:从集成学习原理到代码实现
  • Java技术栈/面试题合集(11)-设计模式篇
  • java web 未完成项目,本来想做个超市管理系统,前端技术还没学。前端是个简单的html。后端接口比较完善。
  • MySQL内外连接详解
  • 学习笔记-相似度匹配改进2
  • 机器学习——随机森林
  • Python高级编程与实践:Python高级数据结构与编程技巧
  • 【C++】Stack and Queue and Functor
  • C++二级考试核心知识点【内附操作题真题及解析】
  • Juc高级篇:可见性,有序性,cas,不可变,设计模式
  • SpringMVC(一)
  • Design Compiler:布图规划探索(ICC)
  • 《失落王国》v1.2.8中文版,单人或联机冒险的低多边形迷宫寻宝游戏
  • Modbus tcp 批量写线圈状态
  • centos7上如何安装Mysql5.5数据库?
  • 跨域场景下的Iframe事件监听
  • 【机器学习深度学习】模型量化
  • OSPF作业
  • Linux 基础
  • vue3 计算方式
  • GPS信号捕获尝试(上)
  • 【android bluetooth 协议分析 01】【HCI 层介绍 30】【hci_event和le_meta_event如何上报到btu层】