IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> TCP UDP协议要点整理 -> 正文阅读

[网络协议]TCP UDP协议要点整理

概述

? ? ? ? TCP和UDP是传输层中的两个非常重要的协议,这两个协议对应的网络通信api(socket api)也有较大差异.

? ? ? ? 简单来说,TCP的特点是:有连接,可靠传输,面向字节流,全双工.而UDP的特点是:无连接,不可靠传输,面向数据报,全双工.

? ? ? ? 有无连接:如果通信的前提是要先建立连接(如打电话),我们称之为有连接;反之(如发微信),我们称之为无连接.

? ? ? ? 是否可靠传输:如果数据发送方能够知道接收方是否受到了其发送的数据,我们称之为可靠传输;反之,称之为不可靠传输.值得一提的是,在网络传输中,是无法保证百分之百的可靠传输的,极端情况下,假如网线断了,这就从物理层面阻止了网络传输.

? ? ? ? 面向字节流/数据报:发送/接受数据以字节为单位,我们称之为面向字节流;以数据报为单位则称之为面向数据报.

UDP部分

? ? ? ? 基本类

? ? ? ? ? ? ? ? UDP协议下主要有两个常用的Socket API.

? ? ? ? ? ? ? ? 1.DatagramSocket

? ? ? ? ? ? ? ? ? ? ? ? 这里需要明确的是,socket类,本质上也是"文件",打开一个socket文件同样会占用进程文件描述符表里的一个位置.socket文件对应到网卡设备.构造一个DatagramSocket对象,就相当于是打开了一个内核中的socket文件.

? ? ? ? ? ? ? ? ? ? ? ? 打开socket的文件之后,我们就可以借此实现端与端之间的数据传输了.有以下三个基本方法:

? ? ? ? ? ? ? ? ? ? ? ? send():发送数据.

? ? ? ? ? ? ? ? ? ? ? ? receive():接收数据.

? ? ? ? ? ? ? ? ? ? ? ? close():关闭socket文件资源.

? ? ? ? ? ? ? ? 2.DatagramPacket

? ? ? ? ? ? ? ? ? ? ? ? DatagramPacket表示一个UDP数据报.UDP协议传输数据就是以这个作为基本单位.

? ? ? ? 回显服务器(echo server)

? ? ? ? ? ? ? ? 服务器????????????????????????

package network;

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){
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            String response = process(request);
            DatagramPacket responsePacket = new DatagramPacket (response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
            socket.send(responsePacket);
            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 udpEchoServer = new UdpEchoServer(8000);
        udpEchoServer.start();
    }
}

? ? ? ? ? ? ? ? 要点详解:

? ? ? ? ? ? ? ? ? ? ? ? 1.

private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
    socket = new DatagramSocket(port);
}

? ? ? ? ? ? ? ? ? ? ? ? 此处手动给服务器绑定一个端口号(port),方便客户端去访问.

? ? ? ? ? ? ? ? ? ? ? ? 一个操作系统上,有很多端口号,范围为0-65535.程序如果需要进行网络通信,就需要获取到一个端口号.端口号相当于网络中用于区分不同进程之间的标识符.(操作系统收到网卡的数据,就可以根据网络数据报中的端口号,来确定要把数据发送给哪个进程).

? ? ? ? ? ? ? ? ? ? ? ? 一个端口在通常情况下只能绑定一个进程,而一个进程可以同时绑定多个端口.如果尝试将一个进程与一个已经被占用的端口绑定,会直接抛出异常.

? ? ? ? ? ? ? ? ? ? ? ? 2.

DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);

? ? ? ? ? ? ? ? ? ? ? ? 此部分是UDP服务器接收数据的逻辑.由于UDP协议是面向数据报的协议,而客户端发送过来的数据通常是字符串.所以我们需要人为把客户端发送过来的数据封装成一个数据报.上述代码就是构造了空的数据报,用于将接收过来的字符串封装成一个UDP数据报(我们称这种参数为输出型参数).观察此处构造函数中的参数可以看出,DatagramPacket本质上就是一个字节数组.

? ? ? ? ? ? ? ? ? ? ? ? 此外,若服务器在启动后始终没有接收到客户端发来的数据,那么socket.receive()方法将会阻塞等待.

? ? ? ? ? ? ? ? ? ? ? ? 3.

String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);
DatagramPacket responsePacket = new DatagramPacket (response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);

? ? ? ? ? ? ? ? ? ? ? ? ? ?这部分是数据的解析与发送.

? ? ? ? ? ? ? ? ? ? ? ? ? ?服务器利用receive中获得的数据报.可以重构回客户端发送的字符串数据(即getData()与getLength()).然后通过process()解析函数得到需要发送回给客户端的字符串数据.

? ? ? ? ? ? ? ? ? ? ? ? ? ?然后就到了封装UDP数据报的部分.数据报的内容和长度我们可以通过String类中的方法确定(即getBytes()和getBytes().length).然后我们还需要确定这个数据报需要发送给谁.这部分有关于客户端的信息可以通过UDP数据报的getSocketAddress()方法获得(注意是客户端的).由此,我们就构造好了responsePacket.将其通过send()方法发送出去即可.

????????????????????????? ? 此处还需要注意的一点是,由于是回显服务器(echo server).服务器不需要对客户端发送的请求做任何处理,原本返回即可.

? ? ? ? ? ? ? ? ? ? ? ? 4.

public static void main(String[] args) throws IOException {
    UdpEchoServer udpEchoServer = new UdpEchoServer(8000);
    udpEchoServer.start();
}

? ? ? ? ? ? ? ? ? ? ? ? ? ?这部分代码是在进程中创建UDP服务器的实例.值得一提的是,我们不推荐给服务器分配号码数小于1024的端口号,因为小于1024的端口号通常是操作系统保留用做他用的.

? ? ? ? ? ? ? ? 客户端? ? ? ? ?

package network;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;
    public UdpEchoClient() throws SocketException {
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while  (true){
            System.out.print("<<");
            String request = scanner.next();
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName("127.0.0.1"), 8000);
            socket.send(requestPacket);
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0, responsePacket.getLength());
            System.out.printf("req: %s; resp: %s\n",request,response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient udpEchoClient = new UdpEchoClient();
        udpEchoClient.start();
    }
}

? ? ? ? ? ? ? ? ? ? ? ? 要点详解:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1.

private DatagramSocket socket = null;
public UdpEchoClient() throws SocketException {
    socket = new DatagramSocket();
}

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?注意到客户端DatagramSocket的构造不需要手动传入端口号,而是选择让用户的操作系统自行分配.这是因为作为开发者,我们无法知道用户的机器上有哪些端口是空闲的,让操作系统自行分配可以避免异常.?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.

String request = scanner.next();
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName("127.0.0.1"), 8000);
socket.send(requestPacket);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 此处是客户端的数据封装与发送.我们可以注意到此处UDP数据报的构造方式与服务器部分的不同:客户端在构造数据报时,需要带上自己的IP地址和服务器的端口号.这两者相当于快递的寄件方和收件方.而服务器在构造数据报时,只需要明确接收方即可.(此处客户端本地的地址获取调用了InetAddress类中的getByName()方法,"127.0.0.1"是自己主机的IP地址)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 然后我们通过send()方法将数据报发送给服务器即可.

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 3.接收数据部分的逻辑与服务器一致,在此不再赘述.

TCP部分

? ? ? ? 基本类

? ? ? ? ? ? ? ? 1.ServerSocket:服务器使用的socket

? ? ? ? ? ? ? ? ?2.Socket服务器和客户端都会使用的socket? ? ?

? ? ? ? ? ? ? ? ?这两者的应用在后面会讲到.

? ? ? ? 回显服务器(echo server)

? ? ? ? ? ? ? ? 服务器???

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("启动服务器");
        ExecutorService service = Executors.newCachedThreadPool();
        while(true){
            Socket socket = serverSocket.accept();
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    public void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);

            PrintWriter printWriter = new PrintWriter(outputStream);

            while(true){
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                String response = process(request);
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s; resp: %s\n",
                        clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }
    }

    public String process(String resp){
        return resp;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(8000);
        tcpEchoServer.start();
    }
}

? ? ? ? ? ? ? ? ? ? 要点介绍:

? ? ? ? ? ? ? ? ? ? ? ? 1.TCP服务器的构造与UDP类似,都需要手动指定端口号,方便客户端访问.

? ? ? ? ? ? ? ? ? ? ? ? 2.

ExecutorService service = Executors.newCachedThreadPool();
while(true){
      Socket socket = serverSocket.accept();
      service.submit(new Runnable() {
           @Override
           public void run() {
                try {
                    processConnection(socket);
                 } catch (IOException e) {
                   e.printStackTrace();
              }
           }
      });
}

? ? ? ? ? ? ? ? ? ? ? ? ? 这部分与UDP服务器的差异较大.在UDP服务器中,服务器无需与客户端建立连接,只需要不断尝试接收客户端可能会发送的信息即可.

? ? ? ? ? ? ? ? ? ? ? ? ? 而在TCP服务器中,客户端和服务器需要建立连接,在建立连接之后才能继续数据通信.这个部分依赖于先前提到的Serversocket类中的accept()方法.该方法相当于获取到了客户端的socket文件,然后直接通过客户端的socket文件进行进一步操作.

? ? ? ? ? ? ? ? ? ? ? ? 3.? ??

try(InputStream inputStream = clientSocket.getInputStream();
     OutputStream outputStream = clientSocket.getOutputStream()) {
     Scanner scanner = new Scanner(inputStream);
     PrintWriter printWriter = new PrintWriter(outputStream);
     while(true){
          if(!scanner.hasNext()){
               System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
               break;
          }
          String request = scanner.next();
          String response = process(request);
          printWriter.println(response);
          printWriter.flush();
          System.out.printf("[%s:%d] req: %s; resp: %s\n",
                        clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);
      }
}

? ? ? ? ? ? ? ? ? ? ? ? 这部分是通过获取的客户端的socket文件,进一步获得客户端传输过来的数据.我们前面提到过TCP协议是通过字节流来传输信息的,所以在对应的socket文件里,我们可以获得对应的输入输出流(即getInputStream()和getOutputStream()方法).

? ? ? ? ? ? ? ? ? ? ? ? 为了方便读写数据,我们可以把输入输出流用Scanner和PrintWriter进一步封装.然后,我们可以通过scanner.next()方法将客户端socket文件中的数据读取出来,解析后使用printWriter.println()方法将结果写回给客户端.

? ? ? ? ? ? ? ? ? ? ? ? 此处使用flush()清空缓冲区是防止返回的数据进入了缓冲区,没有及时发送给客户端.

? ? ? ? ? ? ? ? 客户端? ? ? ? ? ? ? ? ? ? ?

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() throws IOException {
        socket = new Socket("127.0.0.1",8000);
    }

    public void start(){
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {
            Scanner scannerNet = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while(true){
                String request = scanner.next();
                printWriter.println(request);
                printWriter.flush();
                String response = scannerNet.next();
                System.out.printf("req: %s; resp: %s\n", request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient();
        tcpEchoClient.start();
    }
}

? ? ? ? ? ? ? ??要点介绍:

? ? ? ? ? ? ? ? ? ? ? ? 1.客户端方面,我们可以发现客户端的收发逻辑和服务器几乎完全一致,这也是TCP协议和UDP协议的区别之一.

一些补充

? ? ? ? ? ?端口进程查找

? ? ? ? ? ? ? ? ? ?当我们发现想使用的某个端口被占用而我们不想换端口号时,我们可以在cmd下使用netstat -ano?| findstr 8000 命令(这是一个管道复合命令)查找出占用了对应端口号的进程id,示例如图:

? ? ? ? ? ? ? ? ? ? 如上图所示,我们可以查找出当前主机下,占用了8000端口进程的进程id是12032.

? ? ? ? ? ? ? 多线程的使用

? ? ? ? ? ? ? ? ? ? ? ? 可以注意到在TCP服务器的实现中,我使用了线程池.这是由TCP服务器的实现逻辑决定的.可以看到在TCP服务器的processConnection()方法中,有一个死循环,这个死循环会不断读取客户端发送过来的数据(数据可能是一次大数据,也可能是多次小数据),直到读到EOF(如连接中断时就会读到).

? ? ? ? ? ? ? ? ? ? ? ? 这就导致如果在单线程的情况下,一旦一个客户端-服务器连接建立且不中断,服务器会永远阻塞在processConnection()方法中,无法与其他的客户端建立连接,这显然是无法接受的.对此,我们引入多线程:即每来一个客户端,我们就另起一个线程执行processConnection()方法.这就把accept()方法和后续的数据处理隔绝开来,实现了多客户端.采用线程池的原因是考虑到可能会有较大规模的线程的创建和销毁,线程池能节约系统资源.

? ? ? ? ? ? ? ? ? ? ? ? ?实际上,我们也可以人为的约定数据的传输格式,手动在客户端中调用close()方法(如每发送一个字符串,中断一次)?,这样可以绕开多线程.

? ? ? ? ? ? ? ? ? ? ? ? ?至于UDP协议,由于UDP协议下,传输数据不需要建立连接,所以也就不会存在"服务器等待客户端传输数据从而导致阻塞"这个问题,UDP服务器只需要不断接受数据即可,而服务器处理数据的速度是极快的,基本上可以看成是并发执行,也就没必要使用多线程.? ? ? ?? ? ??

?

? ? ? ? ? ??

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-09-30 01:21:54  更:2022-09-30 01:23:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年3日历 -2024/3/29 16:43:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码