TCP/IP协议以及UDP(超详细,看这一篇就够了)

简介: TCP/IP协议以及UDP(超详细,看这一篇就够了)

前提概括

首先进入本章知识重点之前,我们要先了解清楚计算机在客户端以及服务端扮演的角色以及交流过程.

接收端和发送端

在⼀次网络数据传输时:

发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。

接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。

注意:发送端和接收端只是相对的,只是⼀次网络数据传输产生数据流向后的概念。

客户端和服务端

服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务。

客户端:获取服务的一方进程,称为客户端。

客户端和服务端交流过程

最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:

  1. 客户端先发送请求到服务端
  2. 服务端根据请求数据,执行相应的业务处理
  3. 服务端返回响应:发送业务处理结果
  4. 客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果).


一: TCP协议

传输层TCP协议

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

1.1:TCP协议的六大特性

  • 有连接
  • 双全工
  • 可靠传输
  • 面向字节流
  • 有接收缓冲区,也有发送缓冲区
  • 大小不限

对于字节流来说,可以简单的理解为,传输数据是基于IO流,流式数据的特征就是在IO流没有关闭的

情况下,是无边界的数据,可以多次发送,也可以分开多次接收

1.2:Socket

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

务端Socket。

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

的。

⽅法签名 ⽅法说明
Socket(String host,int port) 创建⼀个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

Socket 方法:

⽅法签名 ⽅法说明
InetAddress getInetAddress() 返回套接字所连接的地址
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

1.3:ServerSocket

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

ServerSocket 构造方法:

⽅法签名 ⽅法说明
ServerSocket(int port) 创建⼀个服务端流套接字Socket,并绑定到指定端口

ServerSocket 方法:

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

1.4:TCP的实现

  1. 客户端和服务端:开发时,经常是基于⼀个主机开启两个进程作为客户端和服务端,但真实的场
    景,⼀般都是不同主机。
  2. 注意目的IP和目的端口号,标识了⼀次数据传输时要发送数据的终点主机和进程
  3. Socket编程我们是使用流套接字和数据报套接字,基于传输层的TCP或UDP协议,但应用层协议,
    也需要考虑,这块我们在后续来说明如何设计应用层协议。
  4. 关于端口被占用的问题
  5. 如果⼀个进程A已经绑定了⼀个端口,再启动⼀个进程B绑定该端口,就会报错,这种情况也叫端口
    被占用。对于java进程来说,端口被占用的常见报错信息如下:
TCP Echo Server服务器
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;

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("服务器启动!");
        while (true) {
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }
    // 通过这个⽅法来处理⼀个连接的逻辑.
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客⼾端上线!\n", clientSocket.getInetAddress().t
// 接下来就可以读取请求, 根据请求计算响应, 返回响应三步⾛了.
// Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
// ⼀次连接中, 可能会涉及到多次请求/响应
            while (true) {
// 1. 读取请求并解析. 为了读取⽅便, 直接使⽤ Scanner.
               Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()) {
// 读取完毕, 客⼾端下线.
                    System.out.printf("[%s:%d] 客⼾端下线!\n", clientSocket.getIne
                    break;
                }
// 这个代码暗含⼀个约定, 客⼾端发过来的请求, 得是⽂本数据, 同时, 还得带有
                String request = scanner.next();
// 2. 根据请求计算响应
                String response = process(request);
// 3. 把响应写回给客⼾端. 把 OutputStream 使⽤ PrinterWriter 包裹⼀下
                PrintWriter writer = new PrintWriter(outputStream);
// 使⽤ PrintWriter 的 println ⽅法, 把响应返回给客⼾端.
// 此处⽤ println, ⽽不是 print 就是为了在结尾加上 \n . ⽅便客⼾端
                writer.println(response);
// 这⾥还需要加⼀个 "刷新缓冲区" 操作.
                writer.flush();
// ⽇志, 打印当前的请求详情.
                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.ge
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
// 在 finally 中加上 close 操作, 确保当前 socket 被及时关闭!!
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}
TCP Echo Client客服端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

public class TcpEchoClient {
    private Socket socket = null;
    // 要和服务器通信, 就需要先知道, 服务器所在的位置.
    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
// 这个 new 操作完成之后, 就完成了 tcp 连接的建⽴.
        socket = new Socket(serverIp, serverPort);
    }
    public void start() {
        System.out.println("客⼾端启动");
        Scanner scannerConsole = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
// 1. 从控制台输⼊字符串.
                System.out.print("-> ");
                String request = scannerConsole.next();
// 2. 把请求发送给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
// 使⽤ println 带上换⾏. 后续服务器读取请求, 就可以使⽤ scanner.n
                printWriter.println(request);
// 不要忘记 flush, 确保数据是真的发送出去了!!
                printWriter.flush();
// 3. 从服务器读取响应.
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();
// 4. 把响应打印出来
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}


二: UDP协议

传输层UDP协议

UDP,即User Datagram Protocol(用户数据报协议),传输层协议。

2.1:UDP协议的六大特特性

  • 无连接
  • 双全工
  • 不可靠传输
  • 面向数据报
  • 有接收缓冲区,无发送缓冲区
  • 大小受限:⼀次最多传输64k

对于数据报来说,可以简单的理解为,传输数据是⼀块⼀块的,发送⼀块数据假如100个字节,必须⼀

次发送,接收也必须⼀次接收100个字节,而不能分100次,每次接收1个字节。

2.2:DatagramSocket

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

DatagramSocket 构造方法

⽅法签名 ⽅法说明
DatagramSocket() 创建⼀个UDP数据报套接字的Socket,绑定到本机任意⼀个随机端⼝(⼀般⽤于客⼾端)
DatagramSocket(int port) 创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端⼝(⼀般⽤于服务端)

DatagramSocket 方法

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

2.3:DatagramPacket

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

DatagramPacket 构造方法:

⽅法签名 ⽅法说明
DatagramPacket(byte[]buf,int length) 构造⼀个DatagramPacket以⽤来接收数据报,接收的数据保存在字节数组(第⼀个参数buf)中,接收指定⻓度(第⼆个参数length)
DatagramPacket(byte[] buf,int offset,int length,SocketAddress address) 构造⼀个DatagramPacket以用来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定长度(第⼆个参数length)。address指定⽬的主机的IP和端⼝号

DatagramPacket 方法:

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

2.4:UDP的实现

对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且⼀次发送全部

数据报,⼀次接收全部的数据报。

java中使用UDP协议通信,主要基于 DatagramSocket 类来创建数据报套接字,并使用

DatagramPacket 作为发送或接收的UDP数据报。对于⼀次发送及接收UDP数据报的流程如下:

UDP Echo Server服务器
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], 40
                    socket.receive(requestPacket);
// 这样的转字符串的前提是, 后续客⼾端发的数据就是⼀个⽂本的字符串.
            String request = new String(requestPacket.getData(), 0, requestPacke
// 2. 根据请求, 计算出响应
                    String response = process(request);
// 3. 把响应写回给客⼾端
// 此时需要告知⽹卡, 要发的内容是啥, 要发给谁.
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes
                    requestPacket.getSocketAddress());
            socket.send(responsePacket);
// 记录⽇志, ⽅便观察程序执⾏效果.
          System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAd
                    request, response);
        }
    }
    // 根据请求计算响应. 由于是回显程序, 响应内容和请求完全⼀样.
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}
UDP Echo Client客服端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;
    // 服务器的 ip 和 服务器的端⼝.
    public UdpEchoClient(String ip, int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
// 这个 new 操作, 就不再指定端⼝了. 让系统⾃动分配⼀个空闲端⼝.
        socket = new DatagramSocket();
    }
    // 让这个客⼾端反复的从控制台读取⽤⼾输⼊的内容. 把这个内容构造成 UDP 请求, 发给服务器
// 最终再显⽰在客⼾端的屏幕上.
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("客⼾端启动!");
        while (true) {
// 1. 从控制台读取⽤⼾输⼊的内容
            System.out.print("-> "); // 命令提⽰符, 提⽰⽤⼾要输⼊字符串.
            String request = scanner.next();
// 2. 构造请求对象, 并发给服务器.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes()
                    InetAddress.getByName(serverIp), serverPort);
           socket.send(requestPacket);
// 3. 读取服务器的响应, 并解析出响应内容.
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4
                    socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePa
// 4. 显⽰到屏幕上.
                    System.out.println(response);
        }
    }
    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
// UdpEchoClient client = new UdpEchoClient("42.192.83.143", 9090);
        client.start();
    }
}

如果觉得文章不错,期待你的一键三连哦,你个鼓励是我创作的动力之源,让我们一起加油,顶峰相见!!!💓 💓 💓

相关文章
|
18天前
|
网络协议 算法 网络性能优化
|
6天前
|
网络协议 SEO
TCP连接管理与UDP协议IP协议与ethernet协议
TCP、UDP、IP和Ethernet协议是网络通信的基石,各自负责不同的功能和层次。TCP通过三次握手和四次挥手实现可靠的连接管理,适用于需要数据完整性的场景;UDP提供不可靠的传输服务,适用于低延迟要求的实时通信;IP协议负责数据包的寻址和路由,是网络层的重要协议;Ethernet协议定义了局域网的数据帧传输方式,广泛应用于局域网设备之间的通信。理解这些协议的工作原理和应用场景,有助于设计和维护高效可靠的网络系统。
16 4
|
12天前
|
缓存 负载均衡 网络协议
面试:TCP、UDP如何解决丢包问题
TCP、UDP如何解决丢包问题。TCP:基于数据块传输/数据分片、对失序数据包重新排序以及去重、流量控制(滑动窗口)、拥塞控制、自主重传ARQ;UDP:程序执行后马上开始监听、控制报文大小、每个分割块的长度小于MTU
|
28天前
|
网络协议 前端开发 物联网
TCP和UDP区别?
本文首发于微信公众号“前端徐徐”,详细介绍了TCP和UDP两种传输层协议的核心概念、连接性和握手过程、数据传输和可靠性、延迟和效率、应用场景及头部开销。TCP面向连接、可靠、有序,适用于网页浏览、文件传输等;UDP无连接、低延迟、高效,适用于实时音视频传输、在线游戏等。
39 1
TCP和UDP区别?
|
20天前
|
Web App开发 缓存 网络协议
不为人知的网络编程(十八):UDP比TCP高效?还真不一定!
熟悉网络编程的(尤其搞实时音视频聊天技术的)同学们都有个约定俗成的主观论调,一提起UDP和TCP,马上想到的是UDP没有TCP可靠,但UDP肯定比TCP高效。说到UDP比TCP高效,理由是什么呢?事实真是这样吗?跟着本文咱们一探究竟!
46 10
|
30天前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
28 1
|
1月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
35 0
Linux C/C++之TCP / UDP通信
|
1月前
|
网络协议 算法 数据格式
【TCP/IP】UDP协议数据格式和报文格式
【TCP/IP】UDP协议数据格式和报文格式
113 3
|
1月前
|
网络协议 网络性能优化
详解TCP/IP协议以及UDP
详解TCP/IP协议以及UDP
41 0
|
2月前
|
存储 网络协议 算法
UDP 协议和 TCP 协议
本文介绍了UDP和TCP协议的基本结构与特性。UDP协议具有简单的报文结构,包括报头和载荷,报头由源端口、目的端口、报文长度和校验和组成。UDP使用CRC校验和来检测传输错误。相比之下,TCP协议提供更可靠的传输服务,其结构复杂,包含序列号、确认序号和标志位等字段。TCP通过确认应答和超时重传来保证数据传输的可靠性,并采用三次握手建立连接,四次挥手断开连接,确保通信的稳定性和完整性。
86 1
UDP 协议和 TCP 协议