为什么说UDP基于数据报,TCP基于流传输?
目录
在一些八股当中,经常会见到TCP和UDP的不同,其中一点就是“TCP基于流,而UDP基于数据报”,此篇就用java socket来解释为什么会这样
一、UDP为什么基于数据报?
我们都知道socket的底层就是TCP/UDP,在我们编写UDP相关代码时,会有如下代码:
//创建一个socket
DatagramSocket socket=new DatagramSocket();
//重要:创建一个报文段
DatagramPacket packet=new DatagramPacket(new byte[255],0,255);
//发送报文段
socket.send(packet);
//接收报文段
socket.receive(packet);
由以上代码可以得出UDP是以数据报的形式传输的。
1.UDP具有接收缓冲区
UDP是有接收缓冲区的,当我们去写一个服务端socket去接收数据时,我们会有如下代码:
//创建一个socket
DatagramSocket socket=new DatagramSocket();
//创建接收缓冲区
byte[] buf=new byte[255];
//创建一个报文段
DatagramPacket packet=new DatagramPacket(buf,0,buf.length);
//接收数据
//重要:接收报文段时,会将数据放在buf数组中
socket.receive(packet);
这时我们只能看到用户层面,感觉确实好像是有一个byte数组当作缓冲区,那到底有没有使用呢?看一下receive()方法的源码:
通过以上两幅源码图可以看到,在recive()方法中,会通过byte数组去接收,最终到了一个本地方法中。
2.UDP没有发送缓冲区
UDP是没有发送缓冲区的,同样由代码说明:
//创建一个socket
DatagramSocket socket=new DatagramSocket();
//创建发送的数据
String response="response data";
//创建一个报文段
//参数分别是:相应内容的字节数组,响应内容字节数组大小,目标IP地址和端口号
DatagramPacket packet=new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
//接收数据
//重要:接收报文段时,会将数据放在buf数组中
socket.send(packet);
可以从上述代码中看到,在创建DatagramPacket的时候,并没有传入byte数组作为缓冲区,现在进入send()方法的源码中:
以上两张源码图中可以看到,send()方法内调用实现类的send()方法时,直接将我们在用户层面的传入的DatagramPacket传进入了send()方法中,并没有使用缓冲区。
二、TCP为什么是基于流传输?
在socket编程中,TCP编程所需要的socket和UDP是不同的,UDP使用DatagramPacket,而TCP使用serverSocket(服务器)和Socket,在TCP中通过socket想要获取数据,只能通过socket.getInputStream()方法,即通过流的方法进行传输,如以下代码是服务端进行读取数据和响应的一个过程,服务端先得需要获取输入输出流,通过流来读取数据:
public void process(Socket serverSocket) throws IOException {
//1.获取输入输出流
InputStream inputStream = serverSocket.getInputStream();
OutputStream outputStream = serverSocket.getOutputStream();
try{
while(true){
//2.读取请求数据,并解析
Scanner sc=new Scanner(inputStream);
if(!sc.hasNext()){
//如果没有了数据,则关闭
System.out.println("客户端已下线"+",ip:"+serverSocket.getInetAddress()+",port:"+serverSocket.getPort()+"客户端已下线");
break;
}
//读取请求数据
String requestData=sc.next();
//3.查询词典,返回响应
String responseData=analysis(requestData);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println(responseData);
printWriter.flush();
//4.打印响应请求
System.out.println("[ip:"+serverSocket.getInetAddress()+",port:"
+serverSocket.getPort()+"]"+"-->"+"req:"+requestData+",resp:"+responseData);
}
} finally {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、基于UDP的简易回显服务器
1.客户端
package com.drl.socket.socket_udp.udp2;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/**
* @Description:客户端
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/13 21:23
**/
public class Client {
private DatagramSocket client =null;
private String serverIp;
private int serverPort;
/**
* 创建客户端socket
* @param serverIp 服务器Ip
* @param serverPort 服务器端口号
*/
public Client(String serverIp,int serverPort) throws Exception{
this.serverIp=serverIp;
this.serverPort=serverPort;
//客户端在创建socket对象时,不能把端口号传入进去,在构造方法中传入的端口是自己的端口,并不是发送目的端口
//当不指定端口的话,就相当于操作系统会分配一个空闲的端口号
client=new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//1.从键盘读取数据
System.out.println("->");
//输入一行请求数据
String requestData = scanner.nextLine();
if("exit".equals(requestData)){
System.out.println("bye bye ~");
return;
}
//2.把数据创建成一个UDP请求
DatagramPacket datagramPacket = new DatagramPacket(requestData.getBytes(),requestData.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
//3.发送数据
client.send(datagramPacket);
//4.尝试从服务器读取响应
byte[] responseBuf=new byte[4099];
DatagramPacket responsePacket = new DatagramPacket(responseBuf, 0, responseBuf.length);
client.receive(responsePacket);
//5.显示读取的结果
String responseData=new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println("收到的响应:"+responseData);
}
}
public static void main(String[] args) throws Exception{
Client client = new Client("127.0.0.1",8888);
client.start();
}
}
2.服务端
package com.drl.socket.socket_udp.udp2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @Description:服务器
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/13 21:29
**/
public class Server {
private DatagramSocket server=null;
public Server(int port) throws Exception{
server=new DatagramSocket(port);
}
/**
* 启动服务器
*/
public void start() throws Exception{
System.out.println("服务器已启动...");
//服务器一般都是持续运行的,当前服务器也不知道客户端什么时候发送请求,所以会在这块阻塞住,直到接收到数据
while(true){
//1.读取请求
byte[] requestBuf=new byte[4096];
DatagramPacket requestPacket = new DatagramPacket(requestBuf,0,requestBuf.length);
server.receive(requestPacket);
//拿到请求数据
String requestData=new String(requestPacket.getData(),0,requestPacket.getLength());
System.out.println("收到的请求:"+requestData);
//2.针对请求来计算响应
String responseData=process(requestData);
//3.返回响应,也要构造一个DatagramPacket
//直接传入响应的数据二进制数组,注意:设置长度时,要设置byte数组的长度,而不是响应数据字符串的长度
//DatagramPacket()有三个参数,分别是:要传入数据的二进制数组,二进制数组的长度,目标IP地址和端口号(被封装到SocketAddress中了)
DatagramPacket responsePacket = new DatagramPacket(responseData.getBytes(),responseData.getBytes().length,requestPacket.getSocketAddress());
//发送数据
server.send(responsePacket);
//4.日志打印
System.out.println("已向:"+requestPacket.getSocketAddress()+"响应数据");
}
}
/**
* 根据请求内容返回响应
* @param requestData
* @return
*/
public String process(String requestData){
return requestData;
}
public static void main(String[] args) throws Exception {
Server server = new Server(8888);
server.start();
}
}
四、基于UDP的简易查找字典服务器
1.客户端
package com.drl.socket.socket_udp.udp3;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
/**
* @Description:
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/14 18:45
**/
public class UDPDictClient {
private DatagramSocket client=null;
private String serverIp;
private int serverPort;
public UDPDictClient(String serverIp,int serverPort) throws Exception{
client=new DatagramSocket();
this.serverIp=serverIp;
this.serverPort=serverPort;
}
public void start() throws IOException {
//1.创建发送请求
Scanner sc = new Scanner(System.in);
while(true){
String requestData = sc.next();
if ("exit".equals(requestData)) {
System.out.println("bye bye ~");
return;
}
//创建发送数据报
DatagramPacket requestPacket = new DatagramPacket(requestData.getBytes(),0,
requestData.getBytes().length,InetAddress.getByName(serverIp),serverPort);
client.send(requestPacket);
//2.接收返回的响应
byte[] buf=new byte[4096];
DatagramPacket responsePacket = new DatagramPacket(buf,0,buf.length);
client.receive(responsePacket);
String responseData = new String(responsePacket.getData(), 0, responsePacket.getLength());
//3.打印日志
System.out.println(requestData+"-->"+responseData);
}
}
public static void main(String[] args) throws Exception{
UDPDictClient client = new UDPDictClient("127.0.0.1", 8888);
client.start();
}
}
2.服务器
package com.drl.socket.socket_udp.udp3;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.HashMap;
/**
* @Description:
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/14 18:45
**/
public class UDPDictServer {
private DatagramSocket server=null;
private HashMap<String,String> dict=new HashMap<>();
public UDPDictServer(int port) throws Exception{
server=new DatagramSocket(port);
//初始化dict
dict.put("hello","你好");
dict.put("cat","小猫");
dict.put("dog","小狗");
}
public void start() throws IOException {
System.out.println("服务器启动");
while (true){
//1.读取请求并解析
byte[] buf=new byte[4096];
DatagramPacket requestPacket = new DatagramPacket(buf,0,buf.length);
server.receive(requestPacket);
String requestData=new String(requestPacket.getData(),0,requestPacket.getLength());
//2.根据请求计算响应
String responseData=process(requestData);
//3.将响应数据封装成数据报,响应到客户端
DatagramPacket responsePacket = new DatagramPacket(responseData.getBytes(),
0,responseData.getBytes().length);
//发送响应的目的ip和端口号
responsePacket.setSocketAddress(requestPacket.getSocketAddress());
server.send(responsePacket);
System.out.println("reqIp:"+requestPacket.getAddress().getHostAddress()+",reqPort:"+requestPacket.getPort()+",responseData:"+responseData);
}
}
/**
* 根据请求获取响应
* @param request
* @return
*/
public String process(String request){
return dict.getOrDefault(request,"没有找到哦");
}
public static void main(String[] args) throws Exception {
UDPDictServer server = new UDPDictServer(8888);
server.start();
}
}
五、基于TCP的简易回显服务器
1.客户端
package com.drl.socket.socket_tcp.tcp2;
import javax.swing.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* @Description: tcp回显服务器客户端
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/14 19:44
**/
public class TcpEchoClient {
private Socket client=null;
private String serverIp;
private int serverPort;
public TcpEchoClient(String serverIp,int serverPort) throws IOException {
this.serverIp=serverIp;
this.serverPort=serverPort;
//让socket创建的同时,就和服务器尝试创建连接
client=new Socket(serverIp,serverPort);
}
public void start() throws IOException {
Scanner sc = new Scanner(System.in);
try{
OutputStream outputStream = client.getOutputStream();
InputStream inputStream = client.getInputStream();
while (true){
//1.从键盘读取用户输入的数据
System.out.print("-->");
String requestData = sc.next();
if("exit".equals(requestData)){
break;
}
//2.把输入的数据构造成请求,发送给服务器
PrintWriter writer = new PrintWriter(outputStream);
//注意:使用PrintWriter发送数据时,服务器想要读入,则需要以空白行为标志,所以需要使用println
writer.println(requestData);
//注意:在写完之后,只是将数据写入了缓冲区,需要使用flush()方法刷新缓冲区
writer.flush();
//3.从服务器读取响应并解析
Scanner respScanner = new Scanner(inputStream);
String responseData=respScanner.next();
//4.将结果显示在界面上
System.out.println("发送请求:"+requestData+",响应了:"+responseData);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
client.close();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",8888);
client.start();
}
}
2.服务器
package com.drl.socket.socket_tcp.tcp2;
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;
/**
* @Description: tcp回显服务器服务器
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/14 19:44
**/
public class TcpEchoServer {
private ServerSocket listenSocket=null;
/**
*
* @param port
* @throws IOException
*/
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动...");
while (true){
//TCP是有连接的,所以先需要使用accept()来进行连接
// 1.如果客户端没有建立连接,accept()就是阻塞等待
// 2.如果有客户端建立连接,accept()就是返回一个socket对象
//进一步的服务器和客户端之间的交互,就交给clientSocket来完成了
Socket clientSocket = listenSocket.accept();
processConnection(clientSocket);
}
}
public void processConnection(Socket clientSocket) throws IOException {
//打印日志
System.out.println("客户端上线了,ip:"+clientSocket.getInetAddress()+
",port:"+clientSocket.getPort());
try{
//获取输入输出流
InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream();
while(true){
//1.读取请求并解析
Scanner sc = new Scanner(inputStream);
//如果没有数据了,则结束循环
if(!sc.hasNext()){
System.out.println("客户端已下线...");
break;
}
String requestData = sc.next();
//2.根据请求计算响应
String responseData=process(requestData);
//3.将响应写回给客户端
PrintWriter writer = new PrintWriter(outputStream);
writer.println(responseData);
writer.flush();
//打印响应信息
System.out.println("client:ip:"+clientSocket.getInetAddress().toString()
+",port:"+clientSocket.getPort()+",responseData:"+responseData);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭
clientSocket.close();
}
}
/**
* 回显服务器响应处理
* @param requestData
* @return
*/
public String process(String requestData){
return requestData;
}
public static void main(String[] args) throws Exception{
TcpEchoServer server = new TcpEchoServer(8888);
server.start();
}
}
六、基于TCP的简易回显服务器(多线程版)
使用多线程主要是解决了多个客户端连接服务器的问题,服务器可以处理多个客户端的请求。
1.客户端
package com.drl.socket.socket_tcp.tcp2;
import javax.swing.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* @Description: tcp回显服务器客户端
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/14 19:44
**/
public class TcpEchoClient {
private Socket client=null;
private String serverIp;
private int serverPort;
public TcpEchoClient(String serverIp,int serverPort) throws IOException {
this.serverIp=serverIp;
this.serverPort=serverPort;
//让socket创建的同时,就和服务器尝试创建连接
client=new Socket(serverIp,serverPort);
}
public void start() throws IOException {
Scanner sc = new Scanner(System.in);
try{
OutputStream outputStream = client.getOutputStream();
InputStream inputStream = client.getInputStream();
while (true){
//1.从键盘读取用户输入的数据
System.out.print("-->");
String requestData = sc.next();
if("exit".equals(requestData)){
break;
}
//2.把输入的数据构造成请求,发送给服务器
PrintWriter writer = new PrintWriter(outputStream);
//注意:使用PrintWriter发送数据时,服务器想要读入,则需要以空白行为标志,所以需要使用println
writer.println(requestData);
//注意:在写完之后,只是将数据写入了缓冲区,需要使用flush()方法刷新缓冲区
writer.flush();
//3.从服务器读取响应并解析
Scanner respScanner = new Scanner(inputStream);
String responseData=respScanner.next();
//4.将结果显示在界面上
System.out.println("发送请求:"+requestData+",响应了:"+responseData);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
client.close();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient client = new TcpEchoClient("127.0.0.1",8888);
client.start();
}
}
2.服务器
package com.drl.socket.socket_tcp.tcp2;
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;
/**
* @Description: tcp回显服务器服务器
* @Author:dongRuiLong
* @Date 创建时间: 2022/5/14 19:44
**/
public class TcpEchoServer {
private ServerSocket listenSocket=null;
/**
*
* @param port
* @throws IOException
*/
public TcpEchoServer(int port) throws IOException {
listenSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动...");
while (true){
//TCP是有连接的,所以先需要使用accept()来进行连接
// 1.如果客户端没有建立连接,accept()就是阻塞等待
// 2.如果有客户端建立连接,accept()就是返回一个socket对象
//进一步的服务器和客户端之间的交互,就交给clientSocket来完成了
Socket clientSocket = listenSocket.accept();
//使用多线程实现多个客户端连接服务器
if(clientSocket!=null){
new Thread(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
public void processConnection(Socket clientSocket) throws IOException {
//打印日志
System.out.println("客户端上线了,ip:"+clientSocket.getInetAddress()+
",port:"+clientSocket.getPort());
try{
//获取输入输出流
InputStream inputStream=clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream();
while(true){
//1.读取请求并解析
Scanner sc = new Scanner(inputStream);
//如果没有数据了,则结束循环
if(!sc.hasNext()){
System.out.println("客户端已下线...");
break;
}
String requestData = sc.next();
//2.根据请求计算响应
String responseData=process(requestData);
//3.将响应写回给客户端
PrintWriter writer = new PrintWriter(outputStream);
writer.println(responseData);
writer.flush();
//打印响应信息
System.out.println("client:ip:"+clientSocket.getInetAddress().toString()
+",port:"+clientSocket.getPort()+",responseData:"+responseData);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭
clientSocket.close();
}
}
/**
* 回显服务器响应处理
* @param requestData
* @return
*/
public String process(String requestData){
return requestData;
}
public static void main(String[] args) throws Exception{
TcpEchoServer server = new TcpEchoServer(8888);
server.start();
}
}