Java最全网络编程(TCP 与 UDP协议) - JavaEE初阶 - 细节狂魔,惊喜

总结

蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。


经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级Java面试题集合,附带超详细答案,希望能帮助到大家。

蚂蚁金服5面,总结了49个面试题,遇到的面试官都是P7级别以上

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

    *   [效果图](about:blank#_639)

    *   *   [细节拓展:多个客户端 与 服务器建立连接 - 其实为了指出上述代码的缺陷。](about:blank#_____643)

        *   *   [TCP 服务器优化版 - 多线程版本](about:blank#TCP____651)

        *   [效果图](about:blank#_753)

        *   [拓展1:TCP 服务器 - 线程池版本](about:blank#1TCP____758)

        *   [拓展2:TCP 线程池版本服务器 - 带业务:字典](about:blank#2TCP____832)

        *   [拓展3:细节问题](about:blank#3_885)

网络编程套接字

======================================================================

网络编程套接字,是操作系统给应用程序提供的一组API。

这组API,叫做 socket API。

在这里插入图片描述

socket 可以视为是应用层 和 传输层 的 桥梁.

应用层 和 传输层 之间,交互数据就是靠的 socket API。


在上篇网络初识博文中,我们在讲封装的时候,就提到过 传输层的协议有两种 TCP 和 UDP 。

故,socket API 也有对应的两组。

由于我们的 TCP 和 UDP 协议,差别非常很大。

因此,这两组 API 差别也很大。


在正式介绍 这些 API 之前,我们先来了解 TCP 与 UDP 的 区别。


TCP 与 UDP 的 区别


在这里插入图片描述


有连接 与 无连接

可以怎么去理解:

有链接:像打电话

比如说:现在我们要打电话给某个朋友。

输入号码,按下手机拨号键。

手机开始发出 嘟嘟嘟 声音,开始等待对方接听,

而且,我们拨号之后,并不是马上就能接通的!

必须要等待 对方接听之后,我们才能与其交流。

之所以说:有链接 就像 打电话一样,是因为 打电话,必须要接通了之后,才能交流;没有接通,双方就无法交流。

有连接的意思:就是在两者确认建立联系后,就可以开始交互了。


无连接:发微信

不需要接通,直接就能发数据。

发微信,我们都知道:发送信息的时候,是不需要对方在线或者回复,按下回车,立马就能加个信息发送出去,不过 对方 看没看见这条消息,我们是不确定的 。

这种情况,就叫做无连接。

所以 TCP,就是要求双发先建立连接,连接好了,才能进行传数据。

而 UDP,直接传输数据,不需要双方建立连接。


可靠传输 和 不可靠传输

可靠传输:发送方 知道 接收方 有没有接收到数据

注意!不要理解错了。

可靠传输,不是说数据发送之后,对方100% 就能收到。

你代码写得再好,也刚不住挖掘机把你家网线挖断了。

网线都断了,你能把数据发出去才有鬼。

可靠传输,不是说传输数据百分百成功,关键还得看这里面是否能感知到 传输数据成功了。


关于可靠传输,还有一种错误理解。

可靠传输,就是“安全传输”。这种说法也是一个典型的错误。

可靠 和 安全 是 两码事!!!!

安全,指的是 数据在传输过程,不容易被黑客窃取,不容易被篡改。

可靠,指的是 数据发给对方,发送方能知道接收方有没有收到数据。

在这里插入图片描述


不可靠传输:发送方 不知道 接收方有没有接收到数据。

在这里插入图片描述


总得来说:

可靠,就是我们对于自己发送的信息,心里有点数。

心里没底,就是不可靠。


面向字节流 与 面向数据报

面向字节流:数据是以字节为单位,进行传输的。

这个就非常类似于 文件操作中的文件内容相关的操作中的字节流。

网络传输也是一样!

假设,现有100个字节的数据。

我们可以一直发完。

也可以 一次发 10个字节,发送十次。

也可以 一次发 2 个字节,发送50次。


面向数据报:

以数据报为单位,进行传输。

一个数据报都会明确大小。

一次 发送/接收 必须是 一个 完整的数据报。

不能是半个,也不能是一个半,必须是整数个。


在代码中,这两者的区别是非常明显的!


全双工

全双工 对应的是 半双工。

全双工:一条链路,双向通信。

举个例子:间谍

通常抓到一个间谍,都会对其进行拷问。

说:你的上级是谁?平时是怎么联系的?

间谍:我和他认识,知道彼此身份,并且有相互联系的方式。

他是xxx,联系方式xxxxxx。所以别再打我,作用不大,因为我都会说。

半双工:一条链路,单向通信。

举个例子:间谍

通常抓到一个间谍,都会对其进行拷问。

说:你的上级是谁?平时是怎么联系的?

间谍:我和上级是单向通信的,他联系到我,我联系不到他。所以别再打我,作用不大。

TCP 和 UDP 都是全双工。

半双工理解即可。


小结


以上,是 TCP 和 UDP 直观上的区别。

细节上海域很多很多的东西。

这个后面的博文中都会讲到。

【就是说:网络这一块,你们需要连接看,这几篇博客耦合性极强!】


UDP数据报 套接字编程

===========================================================================

UDP socket 比 TCP 更简单。

我们先从简单的开始。


UDP Socket(套接字)编程中,主要涉及 两个类。


1、DatagramSocket(数据报套接字)

2、DatagramPacket(数据报 的数据包)

TCP 和 UDP 协议中,只有 UDP 是面向数据报的。

那么 DatagramScoket 和 DatagramPacket 这两类,从名字就能看出来(Datagram-数据报),是关于UDP协议的类。

在这里插入图片描述


UDP协议 - DatagramSocket 的核心方法

| 方法签名 | 方法说明 |

| — | — |

| void receive(DatagramPacket p) | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |

| void send(DatagramPacketp) | 从此套接字发送数据报包(不会阻塞等待,直接发送) |

| void close() | 关闭此数据报套接字 |


实战 : 写一个最简单的客户端服务器程序【回显服务】


什么是回显服务?

回显服务 - Echo Server

简单来说,我说什么,你回什么。

在这里插入图片描述

就像回声,重复着我们说过话。

回显服务,就是这样的。

我们发送什么样子的数据,它就给我们返回一个同样的数据。

也就是说:根据我们请求的内容数据,来返回一个具有相同数据的响应。


这样的程序属于最简单的网络编程中的程序。

因为不涉及到任何的业务逻辑,就只是通过 socket API 进行单纯的数据转发。

我通过这个程序,来向大家演示 API 的使用。


服务器部分

准备工作,在项目中创建一个 package 包。

在这里插入图片描述

在所创建的包底下,创建两个类,分别是 UdpEchoServer(udp 回显服务器),UdpEchoClient(udp 回显客户端)

在这里插入图片描述

回到,服务器类这一边,下面开始实现了。


构造一个 socket 对象

在这里插入图片描述

需要注意的是:端口号可以是自己创建的,也可以是系统分配的。

当前这个写法,就属于自己分配的。

至于系统分配的,到后面也会跟你们讲的。


启动服务器

知识点:关于服务器 和 客户端的定义,在MySQL的 初步认识中 的 细谈MySQL 中讲了。

知识点:文件操作:输出型参数 在 文件内容相关的操作中讲到了。

在这里插入图片描述


回显服务器总程序


package network;



import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.SocketException;

//“五元组”

//站在服务器的角度:

//源IP:服务器程序所在主机的IP

//源端口:手动指定的端口【服务器绑定的端口】

//目的IP:包含在收到的数据报中【客户端的IP】

//目的端口:包含在收到的数据报中【客户端的端口】

//协议类型:UDP



public class UdpEchoServer {

    // 进行网络编程时,第一步就需要先准备好 socket 实例。

    // 这是进行网络编程的大前提。

    private DatagramSocket socket = null;



    // port 是 服务器的端口号

    public UdpEchoServer(int port) throws SocketException {

        socket = new DatagramSocket(port);

    }



    // 启动服务器

    public void start() throws IOException {

        System.out.println("启动服务器");

        // UDP 是不需要建立连接的,直接 接收客户端发来的数据 即可。

        while(true){

            //1、读取客户端发来的请求

            DatagramPacket datagramPacket= new DatagramPacket(new byte[1024],1024);

            socket.receive(datagramPacket);//为了接收数据,需要先准备好一个空的DatagramPacket对象,有receive来填充数据

            // 将 datagramPacket 解析成一个 String

            String request = new String(datagramPacket.getData(),0,datagramPacket.getLength(),"UTF-8");



            //2、根据请求计算响应(由于咱们这是一个回显服务,这一步就可以省略了)

            String response = process(request);

            //3、把响应写回客户端

            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,

                    datagramPacket.getSocketAddress());

            socket.send(responsePacket);



            // 打印具体 IP、端口、请求、响应

            System.out.printf("[%s:%d] request: %s,response: %s\n",

                    datagramPacket.getAddress().toString(),// 客户端IP

                    datagramPacket.getPort(),// 客户端端口号

                    request,//请求

                    response);// 响应

        }

    }

    // 由于是回显服务,所以响应就和请求一样

    //但是实际上,对于一个真实的服务器来说,这个过程(根据请求计算响应),是最复杂的!

    // 为了实现这个过程,可能需要几万行,甚至几十万行代码。。。

    private static String process(String request){

        return  request;

    }

    public static void main(String[] args) throws IOException {

        UdpEchoServer server = new UdpEchoServer(9090);

        server.start();

    }

}



在这里插入图片描述


客户端实现

构造一个 socket 对象

在这里插入图片描述


启动客户端

在这里插入图片描述


回显客户端总程序


package network;



import java.io.IOException;

import java.net.DatagramPacket;

import java.net.DatagramSocket;

import java.net.InetAddress;

import java.net.SocketException;

import java.util.Scanner;



//站在客户端的角度:

//源IP:本机IP

//源端口:系统分配的端口

//目的IP:服务器IP

//目的端口:服务器的端口

//协议类型:UDP

public class UdpEchoClient {

    private DatagramSocket socket = null;

    private String serverIP;

    private int serverPort;

    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {

        //此处的 serverPort 是服务器的端口

        // 服务器启动的时候,不需要 socket来指定窗口,客户端自己的端口是系统随机分配的。

        socket = new DatagramSocket();

        this.serverIP = serverIP;

        this.serverPort = serverPort;

    }



    // 启动客户端

    public void start() throws IOException {

        Scanner sc = new Scanner(System.in);

        while(true){

            //1、先从控制台读取用户输入的字符串

            System.out.println("->");

            String request = sc.next();

            //2、把这个用户输入的内容,构成一个 UDP 请求,并发送给服务器

            //构造的请求里面包含两个信息:1、数据的内容(request 字符串);2、数据要发给谁,服务器的 IP + 端口

            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,

                    InetAddress.getByName(serverIP),serverPort);

            socket.send(requestPacket);

            //3、从服务器读取响应数据,并解析

            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);

            socket.receive(responsePacket);

            String response = new String(responsePacket.getData(),0,responsePacket.getLength(),"utf-8");

            //4、把响应效果显示到控制台上

            System.out.printf("[%s:%d] request: %s,response: %s\n",

                    serverIP,// 服务器IP

                    serverPort,// 服务器端口

                    request,//请求

                    response);// 响应

        }

    }

    public static void main(String[] args) throws IOException {

        //由于服务器 和 客户端 在同一个机器上,所以使用的 IP,仍然是 127.0.0.1,如果是在不同的机器上,这里IP就需要更改了。

        UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);

        client.start();

    }

}






服务器 和 客户端的交互效果图


在这里插入图片描述

这才是我们服务器正常的运行状态,同时处理多个客户端发送的请求。

拓展:

通常情况下,一个服务器,是要同时给多个客户端提供服务的。

但是也有特殊情况下,一个服务器只给一个客户端提供服务。

典型例子就是在分布式系统中,两个节点之间的交互。

一个节点视为服务器

另一个及诶单视为客户端

这种就属于是一个专属的情况,它不能给其他人随便乱请求。

给大家讲一个真实案例

清华大学知道吧?

娃哈哈知道吧?

娃哈哈老板的女儿当时就在清华大学读书。

为了让自己的女儿吃上家乡菜,专门就在清华门口开了个饭店。。。

这才是壕无人性的 一对一服务。

当然现在其他人也可以吃。


再来写一个简单程序:就是上面代码的基础上,带上点业务逻辑


写一个翻译程序(英译汉)

请求是一些简单的英文单词。

响应是 英文单词 对应的 中文翻译。

客户端不变,把服务器代码进行调整。

主要是调整 process 方法。

其他步骤都是一样的。

关键的逻辑就是“根据想求来处理响应”

在这里插入图片描述


字典服务器总程序

知识点:多态,继承


import network.UdpEchoServer;



import java.io.IOException;

import java.net.SocketException;

import java.util.HashMap;



// 创建一个类 UdpDictionaryServer 来表示  字典服务器

// 因为代码的逻辑几乎是一样,且所有的办法都是public的

// 所以我们在这里就直接继承,就可以使用内部所有的方法,并且可以进行重写操作。

public class UdpDictionaryServer extends UdpEchoServer {

    HashMap<String,String> map = new HashMap<>();//利用HashMap 来构建词库

    public UdpDictionaryServer(int port) throws SocketException {

        super(port);

        map.put("cat","小猫");

        map.put("dog","小狗");

        map.put("pig","佩奇");

    }



    @Override

    public String process(String request) {

        // 如果查询的单词在“词库”中存在,就返回其 键值对/对应的中文,

        //反之,如果查询的单词在 “词库”中 不存在,返回 没有对应的词。

        return map.getOrDefault(request,"没有对应的词义");

    }



    public static void main(String[] args) throws IOException {

        UdpDictionaryServer dictionaryServer = new UdpDictionaryServer(9090);

        dictionaryServer.start();

    }

}






效果图

在这里插入图片描述


总结


一个服务器,最关键的逻辑就是“根据想求来处理响应”!

什么样的请求,得到什么样的响应。

这是我们一个服务器要完成的一个最最关键的事情。

通过这个东西,才能让我们的程序真正帮我们解决一些实际问题。

这一点,大家要体会我们 服务器-客户端 的交互过程。

之所以,网络编程 是一个 服务器-客户端的结构,是因为 有些工作,我们希望让服务器完成一些工作,既然要完成这样的工作,就得有输入(请求),也有输出(响应)。

从输入到输出,从请求到响应的这个过程,这就是服务器要完成的基本工作。

MySQL 也是 服务器-客户端,这样的结构。

输入/请求:SQL语句

输出/响应:可能是一个临时表,可能是 返回一个影响的行数、

再比如:打开一个网页,输入/请求 一个 奥特曼

在这里插入图片描述


TCP流套接字编程

========================================================================

TCP 和 UDP 的差别很大!

在 TCP API 中,也是涉及到两个核心的类

在这里插入图片描述


实战:回显服务 - TCP版本


服务器实现


构造一个 ServerSoceket 对象

这个和前面是TCP是一样,就不讲了。


package network;



import java.io.IOException;

import java.net.ServerSocket;



public class TcpEchoServer {

    // listen 的 中文意思是 监听

    // 但是,在Java socket 中是体现不出来 “监听”的含义

    // 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen

    // 而 ServerSocket 确实起到了一个监听的效果

    // 所以,取个 listenSocket 的名字

    private ServerSocket listenSocket = null;



    public TcpEchoServer(int port) throws IOException {

        listenSocket = new ServerSocket(port);

    }

}




启动服务器程序 - 与 UDP 差别,尽体现于此部分

知识点:文件操作

知识点:多线程中讲过进程是系统分配资源的单位,我忘记在那张图里讲过了。。

你们直接把 多线程的文章都看了吧。

在这里插入图片描述


TCP服务器总程序


package network;



import java.io.*;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Scanner;



public class TcpEchoServer {

    // listen 的 中文意思是 监听

    // 但是,在Java socket 中是体现不出来 “监听”的含义

    // 之所以这么叫,其实是 操作系统原生的 API 里有一个操作叫做 listen

    // 而 ServerSocket 确实起到了一个监听的效果

    // 所以,取个 listenSocket 的名字

    private ServerSocket listenSocket = null;



    public TcpEchoServer(int port) throws IOException {

        listenSocket = new ServerSocket(port);

    }

    // 启动服务器

    public void start() throws IOException {

        System.out.println("服务器启动!");

        while(true){

            //由于 TCP 是有连接的、因此,不能一上来就读取数据,需要先建立连接

            // accept 就是在“接电话”,接电话的前提是:有人给你打电话【有客户端发送请求】

            Socket clientSocket = listenSocket.accept();

            processConnection(clientSocket);// 处理连接成功的客户端请求

        }

    }



    private void processConnection(Socket clientSocket) throws IOException {

        System.out.printf("[%s,%d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),// 获取客户端IP地址

                clientSocket.getPort());//获取客户端端口

        //接下来,就可以来处理请求 和 响应

        // 这里的针对 TCP socket 的读写  和 文件操作 的读取一模一样!

        try(InputStream inputStream = clientSocket.getInputStream()){

            try(OutputStream outputStream = clientSocket.getOutputStream()){

                Scanner sc = new Scanner(inputStream);

                // 循环处理每个请求,分别返回响应

                while(true){

                    //1、读取请求

                    // 如果没有下一个结果,直接结束循环。

                    if(!sc.hasNext()){

                        System.out.printf("[%s:%d] 客户端断开连接!\n",clientSocket.getInetAddress().toString(),

                                clientSocket.getPort());

                        break;

                    }

                    //此处使用 Scanner 更方便

                    // 如果不用 Scanner,而使用原生的 InputStream 的 read 也是可以的。

                    // 但是很麻烦!它需要构造一个 字节数组 来存储 read 读取的数据。

                    //  read 还会返回字节的个数,如果为-1,即为没有后续数据了,读完了。

                    String request = sc.next();



                    //2、根据请求,计算响应

                    String response = process(request);



                    //3、将响应返回给客户端

                    //为了方便起见,可以使用 PrintWriter 把 OutputStream 包裹一下

                    PrintWriter printWriter =new PrintWriter(outputStream);

                    printWriter.println(response);

                    printWriter.flush();// 刷新缓冲区。

                    // 如果没有这个 flush,可能 客户端就不能第一时间看到响应的结果



                    System.out.printf("[%s:%d] request:%s,response:%s\n",clientSocket.getInetAddress(),// IP

                            clientSocket.getPort(),// 端口

                            request,//请求

                            response);// 响应

                }

            }

        } catch (IOException e) {

            e.printStackTrace();

        }finally {

            clientSocket.close();

        }

    }

    // 因为是回显服务,不涉及业务。只需要直接返货就可以了

    private String process(String request) {

        return request;

    }


# 最后

现在其实从大厂招聘需求可见,在招聘要求上有高并发经验优先,包括很多朋友之前都是做传统行业或者外包项目,一直在小公司,技术搞的比较简单,没有怎么搞过分布式系统,但是现在互联网公司一般都是做分布式系统。

所以说,如果你想进大厂,想脱离传统行业,这些技术知识都是你必备的,下面自己手打了一份Java并发体系思维导图,希望对你有所帮助。

![](https://img-blog.csdnimg.cn/img_convert/720b761b30539119038d93cac0d900a2.webp?x-oss-process=image/format,png)

> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**

**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

r 把 OutputStream 包裹一下

                    PrintWriter printWriter =new PrintWriter(outputStream);

                    printWriter.println(response);

                    printWriter.flush();// 刷新缓冲区。

                    // 如果没有这个 flush,可能 客户端就不能第一时间看到响应的结果



                    System.out.printf("[%s:%d] request:%s,response:%s\n",clientSocket.getInetAddress(),// IP

                            clientSocket.getPort(),// 端口

                            request,//请求

                            response);// 响应

                }

            }

        } catch (IOException e) {

            e.printStackTrace();

        }finally {

            clientSocket.close();

        }

    }

    // 因为是回显服务,不涉及业务。只需要直接返货就可以了

    private String process(String request) {

        return request;

    }


# 最后

现在其实从大厂招聘需求可见,在招聘要求上有高并发经验优先,包括很多朋友之前都是做传统行业或者外包项目,一直在小公司,技术搞的比较简单,没有怎么搞过分布式系统,但是现在互联网公司一般都是做分布式系统。

所以说,如果你想进大厂,想脱离传统行业,这些技术知识都是你必备的,下面自己手打了一份Java并发体系思维导图,希望对你有所帮助。

[外链图片转存中...(img-nkImBzZ5-1715355450051)]

> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)收录**

**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值