总结
蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。
经历这次面试我还通过一些渠道发现了需要大厂真实面试主要有:蚂蚁金服、拼多多、阿里云、百度、唯品会、携程、丰巢科技、乐信、软通动力、OPPO、银盛支付、中国平安等初,中级,高级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,直接传输数据,不需要双方建立连接。
可靠传输 和 不可靠传输
可靠传输:发送方 知道 接收方 有没有接收到数据
注意!不要理解错了。
可靠传输,不是说数据发送之后,对方100% 就能收到。
你代码写得再好,也刚不住挖掘机把你家网线挖断了。
网线都断了,你能把数据发出去才有鬼。
可靠传输,不是说传输数据百分百成功,关键还得看这里面是否能感知到 传输数据成功了。
关于可靠传输,还有一种错误理解。
可靠传输,就是“安全传输”。这种说法也是一个典型的错误。
可靠 和 安全 是 两码事!!!!
安全,指的是 数据在传输过程,不容易被黑客窃取,不容易被篡改。
可靠,指的是 数据发给对方,发送方能知道接收方有没有收到数据。
不可靠传输:发送方 不知道 接收方有没有接收到数据。
总得来说:
可靠,就是我们对于自己发送的信息,心里有点数。
心里没底,就是不可靠。
面向字节流 与 面向数据报
面向字节流:数据是以字节为单位,进行传输的。
这个就非常类似于 文件操作中的文件内容相关的操作中的字节流。
网络传输也是一样!
假设,现有100个字节的数据。
我们可以一直发完。
也可以 一次发 10个字节,发送十次。
也可以 一次发 2 个字节,发送50次。
…
面向数据报:
以数据报为单位,进行传输。
一个数据报都会明确大小。
一次 发送/接收 必须是 一个 完整的数据报。
不能是半个,也不能是一个半,必须是整数个。
在代码中,这两者的区别是非常明显的!
全双工
全双工 对应的是 半双工。
全双工:一条链路,双向通信。
举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和他认识,知道彼此身份,并且有相互联系的方式。
他是xxx,联系方式xxxxxx。所以别再打我,作用不大,因为我都会说。
半双工:一条链路,单向通信。
举个例子:间谍
通常抓到一个间谍,都会对其进行拷问。
说:你的上级是谁?平时是怎么联系的?
间谍:我和上级是单向通信的,他联系到我,我联系不到他。所以别再打我,作用不大。
TCP 和 UDP 都是全双工。
半双工理解即可。
以上,是 TCP 和 UDP 直观上的区别。
细节上海域很多很多的东西。
这个后面的博文中都会讲到。
【就是说:网络这一块,你们需要连接看,这几篇博客耦合性极强!】
===========================================================================
UDP socket 比 TCP 更简单。
我们先从简单的开始。
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 和 UDP 的差别很大!
在 TCP API 中,也是涉及到两个核心的类
服务器实现
构造一个 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)**