目录
TCP%E5%92%8CUDP-toc" name="tableOfContents" style="margin-left:0px">⏳初始TCP和UDP
TCP%E5%92%8CUDP%E7%9A%84%E5%8C%BA%E5%88%AB-toc" name="tableOfContents" style="margin-left:40px">📝面经:TCP和UDP的区别
UDP%E5%92%8CTCP%20API%E7%9A%84%E4%BD%BF%E7%94%A8-toc" name="tableOfContents" style="margin-left:0px">🧸UDP和TCP API的使用
Socket对象
客户端 vs 服务端
常⻅的客⼾端服务端模型
请求 vs 响应
UDP%E6%95%B0%E6%8D%AE%E6%8A%A5%E5%A5%97%E6%8E%A5%E5%AD%97%E7%BC%96%E7%A8%8B-toc" name="tableOfContents" style="margin-left:40px">UDP数据报套接字编程
API介绍
DatagramSocket
DatagramPacket
👁🗨代码示例(超详细注释)
UDP%20Echo%20Server-toc" name="tableOfContents" style="margin-left:120px">服务器回显服务器UDP Echo Server
UDP%20Echo%20Client-toc" name="tableOfContents" style="margin-left:120px">客户端UDP Echo Client
整体执行流程梳理
编写一个英译汉的服务器
TCP%E5%92%8CUDP" style="background-color:transparent">⏳初始TCP和UDP
操作系统给应用程序(传输层给应用层)提供的APl,起了个名字,就叫做Socket API。
接下来学习的就是操作系统提供的Socket API(Java版本的,JDK把这些系统相关的操作,都封装好了)。Socket API提供了两组不同的API,UDP有一套,TCP也有一套,因为UDP和TCP差别有点大。
☃️有连接 vs 无连接
此处谈到的连接,是"抽象"的连接,通信双方,如果保存了通信对端的信息,就相当于是"有连接",如果不保存对端的信息,就是"无连接"。
☔️可靠传输 vs 不可靠传输
此处谈到的"可靠"不是指100%能到达对方,而是"尽可能”。网络环境非常复杂,存在很多的不确定因素。再牛逼的技术,也顶不过挖掘机一铲子,前些年有一次支付宝,突然挂了,当时修路,挖掘机把光纤给铲断了,(去年也有一次,不是挖掘机搞的)。相对来说,不可靠,就是完全不考虑,数据是否能够到达对方。
TCP内置了一些机制,能够保证可靠传输.
1)感知到对方是不是收到了
2)重传机制,在对方没收到的时候进行重试
UDP则没有可靠性机制,UDP完全不管发出去的数据是否顺利到达对方
直观感觉,可靠传输,比不可靠传输更好?
可靠传输要付出代价,TCP协议,设计就要比UDP复杂很多,也会损失一些传输数据的效率
❄️面向字节流 vs 面向数据报
TCP是面向字节流的,TCP的传输过程就和文件流/水流是一样的特点
从文件读写100个字节
1)一次读写100字节
2)2次,每次读写50字节
3)10次,每次读写10字节
4)100次,每次读写1字节
从TCP读写100个字节
1)一次读写100字节
2)2次,每次读写50字节
3)10次每次读写10字节
4)100次,每次读写1字节
UDP面向数据报,传输数据的基本单位,不是字节了,而是"UDP数据报",一次发送/接收必须发送/接收完整的UDP数据报
这两种方式会直接影响到代码的写法
☀️全双工 vs 半双工
全双工:一个通信链路,可以发送数据,也可以接受数据(双向通信)
半双工:一个通信链路,只能发送/只能接收(单向通信)
全双工这个事情,在物理层面上,并非是只有一根线在连接,一根网线里,有8根铜线4、4一组
咱们这边写的代码,都是全双工的,不必考虑半双工
TCP%E5%92%8CUDP%E7%9A%84%E5%8C%BA%E5%88%AB" name="%F0%9F%93%9D%E9%9D%A2%E7%BB%8F%3ATCP%E5%92%8CUDP%E7%9A%84%E5%8C%BA%E5%88%AB" style="background-color:transparent">📝面经:TCP和UDP的区别
TCP有连接,可靠传输,面向字节流,全双工
UDP无连接,不可靠传输,面向数据报,全双工
UDP%E5%92%8CTCP%20API%E7%9A%84%E4%BD%BF%E7%94%A8" name="%F0%9F%A7%B8UDP%E5%92%8CTCP%20API%E7%9A%84%E4%BD%BF%E7%94%A8" style="background-color:transparent">🧸UDP和TCP API的使用
API是一组函数/一组类
Socket对象
操作系统的概念,Socket就可以认为是操作系统中,广义的文件下,里面的一种文件类型,
这样的文件,就是网卡这种硬件设备,抽象的表示形式。通过代码直接操作网卡,不好操作(网卡有很多种不同的型号,之间提供的API都会有差别)。操作系统就把网卡概念封装成Socket,应用程序员不必关注硬件的差异和细节,统一去操作Socket对象,就能间接的操作网卡,Socket就像遥控器一样。
客户端 vs 服务端
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。主动发起通信的一方。
客户端:获取服务的一方进程,称为客户端。被动接受的一方。
📮📮📮说明:下面的代码示例写的是一个最简单的客户端服务器程序,不涉及到业务流程只是对于API的用法做演示。我们称为"回显服务器"(echo server),即客户端发啥样的请求,服务器就返回啥样的响应,没有任何业务逻辑进行任何计算或者处理。
常⻅的客⼾端服务端模型
1. 客⼾端先发送请求到服务端
2. 服务端根据请求数据,执⾏相应的业务处理
3. 服务端返回响应:发送业务处理结果
4. 客⼾端根据响应数据,展⽰处理结果(展⽰获取的资源,或提⽰保存资源的处理结果)
请求 vs 响应
请求(request):客户端主动给服务器发起的数据
响应(response):服务器给客户端返回的数据
UDP%E6%95%B0%E6%8D%AE%E6%8A%A5%E5%A5%97%E6%8E%A5%E5%AD%97%E7%BC%96%E7%A8%8B" name="UDP%E6%95%B0%E6%8D%AE%E6%8A%A5%E5%A5%97%E6%8E%A5%E5%AD%97%E7%BC%96%E7%A8%8B" style="background-color:transparent">UDP数据报套接字编程
API介绍
DatagramSocket
代表一个Socket对象,⽤于发送和接收UDP数据报
DatagramSocket 构造⽅法:
DatagramSocket ⽅法:
DatagramPacket
代表一个UDP数据报,即是UDP Socket发送和接收的数据报
DatagramPacket 构造⽅法:
DatagramPacket ⽅法:
👁🗨代码示例(超详细注释)
UDP%20Echo%20Server" name="%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%9B%9E%E6%98%BE%E6%9C%8D%E5%8A%A1%E5%99%A8UDP%20Echo%20Server">服务器回显服务器UDP Echo Server
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UdpEchoServer {//网络编程必须要操作网卡,就需要用到socket对象private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {//这个异常是IOException的子类//对于服务器这一端来说,需要在socket对象创建的时候就指定一个端口号,作为构造方法的参数//后续服务器开始运行之后,操作系统就会把端口号和该进程关联起来.socket = new DatagramSocket(port);//在调用这个构造方法的过程中,JVM就会调用系统的Socket API,完成“端口号-进程”之间的关联动作//“绑定端口号”(系统原生API名字就叫做bind)//端口号就是为了区分进程,收到数据之后能明确这个数据要给谁//所以,对于一个系统来说,同一时刻,一个端口号只能被一个进程绑定//但是一个进程可以绑定多个端口号(通过创建多个socket对象来完成)//相当于一个人可以有多个手机号,但一个手机号只能属于一个人//所以如果有多个进程尝试绑定一个端口号,后来的进程就会绑定失败//socket.close();//此处代码中,socket生命周期,应该是跟随整个进程的//进程结束了,socket才需要关闭//此时,就算代码中没有close,进程关闭,也就会释放文件描述符表里的所有内容,也就是相当于close了.}// 通过 start 启动服务器的核心流程public void start() throws IOException {System.out.println("服务器启动!");//对于服务器来说,主要的工作,就是不停的处理客户端发来的请求//由于客户端啥时候来请求,服务器也无法预测.服务器只能时刻准备好,随时有客户端来了,就随时立即处理while (true) {// 此处通过“死循环”不停的处理客户端的请求// 1.读取客户端的请求并解析DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);//指定字节数组及长度//DatagramPacket自身需要存储数据,但是存储数据的空间具体多大,需要外部来定义的,自身不负责//这个数据没啥讲究,确保能够存储下你通讯的一个数据包即可:socket.receive(requestPacket);//此处receive也是通过"输出型参数"获取到网卡上收到的数据的//receive是从网卡上读取数据,但是调用receive的时候,网卡上可不一定就有数据//如果网卡上收到数据了,receive立即返回,获取到收到的数据//如果网卡上没有收到数据,receive就会阻塞等待,一直等待到真正收到数据为止//上述收到的数据,是二进制byte[]的形式体现的,后续代码如果要进行打印之类的处理操作//需要转成字符串才好处理String request = new String(requestPacket.getData(), 0, requestPacket.getLength());//构造String对象,是可以基于byte[]来构造的// 2.根据请求计算响应,由于此处是回显服务器,响应就是请求String response = process(request);// 3.把响应写回到客户端DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());//针对返回响应操作,不能够使用空的数组来构造Packet对象//String可以基于字节数组来构造的.也可以随时取出里面的字节数组socket.send(requestPacket);//UDP有一个特点,无连接//所谓的连接,就是通信双方,保存对方的信息(IP+端口)//DatagramSocket这个对象中,不持有对方(客户端)的IP和端口的//进行send的时候,就需要在send的数据包里,把要发给谁这样的信息写进去,才能够正确的把数据进行返回//所以在上面还要传一个参数requestPacket.getSocketAddress()客户端的IP和端口就包含在这个信息中// 4.打印日志System.out.printf("[%s:%d] req=%s,resp=%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();}
}
UDP%20Echo%20Client" name="%E5%AE%A2%E6%88%B7%E7%AB%AFUDP%20Echo%20Client">客户端UDP Echo Client
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {DatagramSocket socket = null;private String serverIP;private int serverPort;public UdpEchoClient(String serverIp, int serverPort) throws SocketException {socket = new DatagramSocket();//服务器这边,创建socket,一定要指定端口号//服务器必须是指定了端口号,客户端主动发起的时候,才能找到服务器//客户端这边,创建socket,最好不要指定端口号//客户端是主动的一方,不需要让服务器来找他,客户端就不需要指定端口号了//(不指定,不代表没有,客户端这边的端口号是系统自动分配了一个端口)//一次通信过程中是需要源IP,源端口,目的IP,目的端口的//这里还有一个重要的原因,如果在客户端这里指定了端口之后,//由于客户端是在用户的电脑上运行的,天知道,用户的电脑上都有哪些程序,都已经占用了哪些端口了//万一你代码指定的端口和用户电脑上运行的其他的程序的端口冲突就出bug了// 用户也不懂这些技术内容,用户只会把bug算到你的头上//让系统自动分配一个端口,就能确保是分配一个无人使用的空闲端口//服务器指定固定端口,就不怕和别的程序冲突吗//不怕的!服务器程序,运行在服务器主机上,等于是在程序员手里this.serverIP = serverIp;this.serverPort = serverPort;}public void start() throws IOException {System.out.println("启动客户端");Scanner sc = new Scanner(System.in);while (true) {// 1. 从控制台读取到用户的输入System.out.println("->");String request = sc.next();// 2. 构造出一个 UDP 请求,发送给服务器DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(this.serverIP), this.serverPort);//此处是给服务器发送数据,发送数据的时候UDP数据报里就需要带有目标的IP和端口//接收数据的时候,构造的UDP数据报,就是一个空的数据报socket.send(requestPacket);// 3. 从服务器读取到响应DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);String response = new String(responsePacket.getData(), 0, responsePacket.getLength());// 4.把响应打印到控制台上System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);//这个是特殊的IP,环回IP,这个IP就代表本机//如果客户端和服务器在同一个主机上,就使用这个IPclient.start();}
}
整体执行流程梳理
对于UDP协议来说,具有⽆连接,⾯向数据报的特征,即每次都是没有建⽴连接,并且⼀次发送全部数据报,⼀次接收全部的数据报。
Java中使⽤UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使⽤ DatagramPacket 作为发送或接收的UDP数据报。对于⼀次发送及接收UDP数据报的流程如下:
以上只是⼀次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请求,没有响应。对于⼀个服务端来说,重要的是提供多个客⼾端的请求处理及响应,流程如下:
此处的通信,是本机上的客户端和服务器通信,如果搞两个主机,能够跨主机通信嘛?
如果我把客户端代码发给你,你能通过你的客户端访问到我的这个服务器嘛?
能,也不能。IPV4协议的锅
如果我就把服务器代码,运行在我自己的这个电脑上,
此时,你是无法访问到我这个服务器的,除非你连上和我一样的wifi。
但如果把我写的服务器代码,放到"云服务器”上,云服务器拥有公网IP,此时就是可以的。
我自己的电脑,没有公网IP。
什么是云服务器
也是一个电脑,租来的电脑,咱们通常租来的这个云服务器,硬件配置和性能,相比于咱们自己的台式机/笔记本弱很多,但是有一点,是云服务器具备的优势,带有公网IP(个人电脑,很难获取到的),云服务器当然也有配置高的,贵,不差钱的公司,才能买得起
编写一个英译汉的服务器
只需要重写 process
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);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(requestPacket);// 4.打印日志System.out.printf("[%s:%d] req=%s,resp=%s\n", requestPacket.getAddress(), 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();}
}
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;public class UdpDictServer extends UdpEchoServer {private Map<String, String> dict = new HashMap<>();public UdpDictServer(int port) throws SocketException {super(port);dict.put("cat", "小猫");dict.put("dog", "小狗");dict.put("pig", "小猪");dict.put("sheep", "小羊");// 真实的词典服务器需要很多很多的单词,可能是上万个}@Overridepublic String process(String request) {return dict.getOrDefault(request, "未知单词");}public static void main(String[] args) throws IOException {UdpDictServer server = new UdpDictServer(9090);server.start();}
}
import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UdpEchoClient {DatagramSocket socket = null;private String serverIP;private int serverPort;public UdpEchoClient(String serverIp, int serverPort) throws SocketException {socket = new DatagramSocket();this.serverIP = serverIp;this.serverPort = serverPort;}public void start() throws IOException {System.out.println("启动客户端");Scanner sc = new Scanner(System.in);while (true) {// 1. 从控制台读取到用户的输入System.out.println("->");String request = sc.next();// 2. 构造出一个 UDP 请求,发送给服务器DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,InetAddress.getByName(this.serverIP), this.serverPort);socket.send(requestPacket);// 3. 从服务器读取到响应DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);String response = new String(responsePacket.getData(), 0, responsePacket.getLength());// 4.把响应打印到控制台上System.out.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();}
}
TCP流套接字编程将会在下篇文章🥰🥰🥰