8/24 Java学习博客
网络编程
网络编程就是通过网络,让两个主机之间能够进行通信,基于这样的通信来完成一定的功能。
我们在进行网络编程的时候,需要操作系统给咱们提供一组API,通过这些API才能完成编程。
网络编程里面我们就使用SocketAPI来完成不同主机之间,不同系统之间的网络通信。
API可以认为是应用层和传输层之间交互的路径
在传输层中 提供了主要的两个网络协议 TCP和UDP
这两个协议的特性(工作原理)差异很大,导致我们使用这两种协议进行网络编程,也存在一定的差异,系统就分别提供了两套API。
一、那首先就是对TCP和UDP之间的差异进行个认识
1.TCP是有连接的,UDP是无连接的
(连接是一种抽象的概念)此处连接的本质就是建立连接的双方,各自保存对方的信息。
TCP想要通信,就需要先建立连接,做完之后才能进行后续通信(如果A想和B建立连接,B拒绝了,则通信无法完成。)
UDP想要通信,就直接发送数据即可,不需要征得对方的同意,UDP自身也不会保存对方的信息。
UDP不知道,但是写程序的人得知道,UDP自己不保存,但是我们调用UDP的socketAPI时候要把对方的位置啥的个传过去。
2.TCP是可靠传输,UDP是不可靠传输
不过这里的可靠不是说100%传达 !
而是说 可靠传输 是一种退而求其次
A——>B发消息时,消息有没有到达B这里,A是能知道的,进一步的就可以再发送失败时候采取一定措施(尝试重传之类的)
那这里就说 不是用可靠传输更好吗
可靠传输有两个缺点:(1)机制更复杂(2)传输效率更低
3.TCP是面向字节流的,UDP是面向数据报的
此处说的字节流,跟文件操作里面的字节流是一个意思!
TCP也是和文件操作一样,以字节位单位来进行传输
UDP则是按照数据报为单位,来进行传输。 UDP数据报也是有严格的格式的。
4.TCP和UDP都是全双工的
一个信道,允许双向通信,就是全双工
一个信道,只能单向通信。就是半双工
代码中就是使用一个socket对象,就可以发送数据也可以接受数据。
什么是Socket
课上老师说是网卡抽象出来的文件,那实际上,我们应该把它理解成系统提供给程序的”网络通信端点“,而不是网卡抽象成文件。
其实Socket 和网卡实际的链路更像
Java 程序
↓
DatagramSocket
↓
操作系统 Socket
↓
UDP
↓
IP
↓
网卡驱动
↓
网卡
↓
网络
“抽象成文件”这个想法,其实来源于 Unix/Linux 的“一切皆文件”思想。
在 Linux中,Socket 对象可以通过文件描述符 fd 来访问,所以使用方式有点像文件。
所以 Socket 抽象的是网络通信接口/通信端点,而不是网卡本身。
二、学习UDP的socketAPI如何使用
主要用到两个
(1)DatagramSocket
使用这个类来表示一个 UDP 通信端点,主要负责 UDP 数据报的发送和接收。
UDP 是无连接的,因此通常直接通过 send() 和 receive() 进行通信。
(2)**DatagramPacket **
使用这一个类,来表示一个UDP数据报,UDP是面向数据报的,所以每次进行传输都要以UDP数据报为基本单位。
我们就写一个简单的UDP的客户端/服务器通信的程序.这个程序没有啥业务逻辑,只是单纯的调用socket api.
让客户端给服务器发送一个请求,请求就是一个从控制台输入的字符串.
服务器收到字符串之后,也就会把这个字符串原封不动的返回给客户端,客户端再显示出来.
也就是回显服务器
首先我们进行编写
服务端UdpEchoServer
开始肯定是要创建 DatagramSocket对象 也是后续进网络传输的基础
private DatagramSocket socket = null;
接着写下构造方法 需要注意的是 服务器的socket一般要显式的指定一个端口号.
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
然后接着写启动方法
服务器启动后 第一步就是读取请求并解析 读取请求我们就是用到receive()
那这个receive方法要求里面有一个参数DatagramPacket p 那这里的参数也是一个输出型参数 跟文件内容操作里的read()方法里的char[] cbuf 数组是一样的,都是输出型参数
一个服务器程序,经常是要长时间运行的(7*24)也不知道客户端啥时候来.先构造一个空着的对象,然后传递到方法内部.由receive 内部对这个数据进行填充
那我们肯定就是需要初始化个DatagramPacket p
这里我们就命名为 requestPacket
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
这个对象用来承载从这边读到的数据.收到数据的时候,需要搞一个内存空间来保存这个数据DatagramPacket 内部不能自行分配内存空间因此就需要程序员手动把空间创建好,交给 DatagramPacket进行处理~然后再 socket.receive(requestPacket);
服务器一旦启动,就会立即执行到这里的receive方法.此时,客户端的请求可能还没来呢
这种情况也没关系.receive就会直接阻塞.就会一直阻塞到真正客户端把请求发过来为止.
完成receive后数据以二进制的形式存储到DatagramPacket底下,但是要想能够把这里的数据给显示出来,我们还需要把这个二进制的数据转化成字符串
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
这个getLength得到的这个结果是收到的数据的真实长度.
0, requestPacket.getLength() 取这个区间内的字节,构造成一个String
转换完后我们还需要根据请求计算响应,由于我们这里是回显服务器所以就直接返回一样的就可以了
String response = process(request);
public String process(String request) {
return request;
}
然后把响应写回客户端,搞一个响应对象 DatagramPacket,往里面构造数据通过send返回
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
requestPacket.getSocketAddress());
UDP无连接的(UDP自身不会保存数据要发给谁),就需要每次发送的时候,重新指定,数据要发到哪里去!!我们就构造这个数据报,需要指定数据内容,也指定一下数据报要发给谁发给的就是requestPacket.getSocketAddress()里获取的地址
把这个代码换成response.getBytes().length
response.length()是否可以?
本质上是跟字符集有关系,如果你这个学符串里内容都是英文学符,此时字节和字符个数是一样的.如果包含中文就不一样了。
在进行网络传输的时候,肯定是要使用字节的!!
然后
socket.send(responsePacket);
那为了不断去接受数据报和响应我们再在外面做一个while循环即可
最后再写一个main方法启动下就可以了
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
最后完整的服务端
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//创建一个DatagramSocket对象 后续操作网卡的基础
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], 4096);
socket.receive(requestPacket);
//完成receive后数据以二进制的形式存储到DatagramPacket底下
//要想能够把这里的数据给显示出来,还需要把这个二进制的数据转化成字符串
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
//2.根据请求计算响应(一般服务器都会经历的过程)
//由于此处是回显服务器,请求是啥样,响应就是啥样
String response = process(request);
//3.把响应写回客户端
//搞一个响应对象 DatagramPacket
//往里面构造数据通过send返回
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
//4.打印日志
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 server = new UdpEchoServer(9090);
server.start();
}
}
接着实现客户端
那开始我们也是先创建DatagramSocket对象 不过此外我们还要先初始化下IP和端口
private DatagramSocket socket = null;
private String serverIp = "";
private int serverPort = 0;
接着再来构造方法
public UdpEchoClient(String ip , int port) throws SocketException {
socket = new DatagramSocket();
serverIp= ip;
serverPort = port;
}
创建这个对象不能手动指定端口,我们这ip和port都是对端的信息,由于UDP本身不持有对端信息,所以要在应用程序里面记录下来。
接着写下start方法
我们要从控制台读取数据作为请求,那就先
Scanner sc = new Scanner(System.in);
String request = sc.next();
然后就是把请求内容给构造成一个DatagramPacket对象 发给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
这里面就包含了 先把请求内容转换成字节getBytes() ,然后长度,还有目标IP和目标端口
InetAddress.getByName(serverIp) 的作用是:
把一个字符串形式的 IP 地址或主机名,解析成 Java 的 InetAddress 对象。
然后就
socket.send(requestPacket);
接着就尝试读取服务器返回的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4069], 4069);
socket.receive(responsePacket);
就跟服务端一样也是转换成字符串并显示出来
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
最后在写一个mian方法就好了 由于在本机运行 ip地址就是127.0.0.1 端口就填服务器指定的9090
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
客户端的完整代码:
package network;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp = "";
private int serverPort = 0;
public UdpEchoClient(String ip , int port) throws SocketException {
//创建这个对象不能手动指定端口
socket = new DatagramSocket();
//由于UDP本身不持有对端信息,所以要在应用程序里面把对端的情况记录下来
serverIp= ip;
serverPort = port;
}
public void start() throws IOException {
//客户端启动
System.out.println("客户端启动");
Scanner sc = new Scanner(System.in);
while(true){
//1.从控制台读取数据,作为请求
System.out.println("->");
String request = sc.next();
//2.把请求内容构造成一个DatagramPacket对象 发给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
//3.尝试读取服务器返回的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4069], 4069);
socket.receive(responsePacket);
//4.把响应转换成字符串并显示出来
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
那这边就会有一个疑问 为什么那个服务器的socket一般要显式的指定一个端口号.
而客户端的socket一般不能显式指定()(不显式指定,此时系统会自动分配一个随机的端口)
其实就跟餐馆一样,餐馆就是服务端需要确定端口号,客户端就像客人,客人每次来就餐的位置可以都不一样,客户端就端口就不需要固定,直接让系统分配一个空闲即可,如果手动固定了还可能和其他程序的端口号冲突。
服务器这边手动指定端口,就不会出现冲突嘛?为啥客户端在意这个冲突,而服务器不在意呢?
服务器是在程序员手里的.一个服务器上都有哪些程序,都使用哪些端口,都是可控的!!!
程序员写代码的时候,就可以指定一个空闲的端口,给当前的服务器使用即可.
但是客户端就不可控.客户端是在用户的电脑上.
一方面,每个用户电脑上装的程序都不一样,占用的端口也不一样.
交给系统分配比较稳妥.系统能保证肯定分配一个空闲的端口
注意!
close() 不是“文件专属的方法”,它更准确的含义是:
释放这个对象占用的系统资源。
Socket 虽然不是文件,但它同样会占用操作系统资源,所以也需要 close()。
那为什么上述写的代码中,为啥没写close??
首先要知道 文件描述符,是在pcb上的.(跟随进程的)
这个socket在整个程序运行过程中都是需要使用的(不能提前关闭)当socket不需要使用的时候,意味着程序就要结束了,进程结束,此时随之文件描述符表就会销毁了(PCB都销毁了).随着销毁的过程,被系统自动回收了~~
啥时候才会出现泄露?
代码中频繁的打开文件,但是不关闭.
在一个进程的运行过程中,不断积累打开的文件,逐渐消耗掉文件描述符表里的内容
最终就消耗殆尽了.
但是如果进程的生命周期很短,打开一下没多久就关闭了.谈不上泄露.
所以看到这里又会想到 我靠这个socket到底是不是文件
GPT回答:
这里的 socket 是一个 Java 对象,它封装了底层操作系统提供的网络 Socket 资源。
Java DatagramSocket 对象
↓
JDK 底层 native 实现
↓
操作系统 Socket
↓
Linux 下可能对应一个文件描述符 fd
↓
内核 Socket 对象
所以在 Java 这一层,通常不会直接看到:
fd = 5
而是操作:
socket.receive(...)
socket.send(...)
socket.close()
之前看到 close() 也不是因为 Java 把它当文件,而是因为这个对象占用了底层系统资源,需要释放。
甚至 Java 专门有个统一接口叫:
AutoCloseable
很多完全不是文件的资源都可以 close(),比如数据库连接:
最后再补充一嘴 如果想要做一个翻译对照的话,就在这个服务端上做一个继承,写一个HashMap来做一个对照关系,再重写下process方法即可
总结下这个服务器就是
1.服务器先启动.服务器启动之后,就会进入循环,执行到receive这里并阻塞(此时还没有客户端过来呢)
2.客户端开始启动,也会先进入while循环,执行 scanner.next.并且也在这里阻塞当用户在控制台输入字符串之后,next就会返回,从而构造请求数据并发送出来~~
3.客户端发送出数据之后
服务器:就会从receive中返回,进一步的执行解析请求为字符串,执行process操作,执行send操作
客户端:继续往下执行,执行到receive,等待服务器的响应
4.客户端收到从服务器返回的数据之后,就会从receive中返回.
执行这里的打印操作,也就把响应给显示出来了.
5.服务器这边完成一次循环之后,又执行到receive这里.客户端这边完成一次循环之后,又执行到scanner.next这里.双双进入阻塞
TCP的socketAPI如何使用
TCP socket api.和 UDP的socket api 差异又很大~~但是和前面讲的文件操作,有密切联系的.
连接:通信双方是否会记录保存对端的信息.
UDP来说,每次发送数据都得手动在send方法中指定目标的地址.(UDP自身没有存储这个信息)TCP来说,则不需要.前提是需要先把连接给建立上.连接如何建立,不需要代码干预,是系统内核自动负责完成的.
对于应用程序来说
客户端这边,主要是要发起"建立连接”动作.
服务器这边,主要是要把建立好的连接从内核中拿到应用程序里~~
我们用到的两个关键的类.
1.ServerSocket(给服务器使用的类,使用这个类来绑定端口号)
2.Socket(既会给服务器用,又会给客户端用)
TCP是字节流的.传输的基本单位,是byte.
那这两个的使用简单理解就是
ServerSocket 负责“等人来连接”,Socket 负责“连接建立后真正收发数据”。
ServerSocket
↓
负责等新的客户端
Socket
↓
负责和已经连接的客户端通信
比如服务器要接收客户端消息:
服务器:
ServerSocket(8080)
↓
accept()
↓
得到 Socket
↓
Socket.getInputStream()
↓
读取 hello
会发现:
ServerSocket 本身不负责 getInputStream()、getOutputStream() 去通信。
开局都是一样
private ServerSocket serverSocket=null;
public TcpEchoServer(int port) throws IOException {
serverSocket=new ServerSocket(port);
}
start方法
public void start() throws IOException {
System.out.println("服务器启动");
ExecutorService executorService= Executors.newCachedThreadPool();
while(true){
//通过accept方法把内核中已经建立好的连接拿到应用程序
//建立连接的细节流程都是自动完成的,应用程序只需要捡现成的
Socket clientSocket=serverSocket.accept();
//此处不应该直接调用processConnection 会导致服务器不能处理多个服务器
/* Thread t = new Thread(()->{
processConnection(clientSocket);
});
t.start();*/
//线程池
executorService.submit(()->{
processConnection(clientSocket);
});
}
}
处理连接方法:
InputStream 和 OutputStream 就是字节流!!!就可以借助这俩对象,完成数据的"发送"和"接收”通过 InputStream进行 read操作,就是"接收"
通过OutputStream 进行 write 操作,就是“发送”
//通过这个方法处理当前连接
private void processConnection(Socket clientSocket) {
//进入方法,打印日志,表示当前有客户端连上了
System.out.printf("[%s:%d] 客户端上线",clientSocket.getInetAddress(),clientSocket.getPort());
//接下来进行数据的交互
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
//使用try()方式,避免后续用完了,流对象忘记关闭。
//由于客户端发来的数据可能是多条数据,所以我们循环处理
while(true){
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){
//连接断开了,循环应该结束
System.out.printf("[%s:%d] 客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//1.读取请求并解析,此处以next作为读取请求的方式,next规则就是:读到“空白符“就返回
/*TCP是字节流通信方式.每次传输多少个字节,每次读取多少个字节都是非常灵活的.但是灵活往往是坏事~~~代码就不知道该咋办了~~往往会手动约定出,从哪里到哪里是一个完整的数据报.每次循环一次,就处理一个数据报即可。
iscanner.next 来完成请求的读取过程
上述这里就是约定了使用\n作为数据报的结束标记.就正好可以搭配*/
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.把响应写回到客户端
// 可以把String转换为字节数组 写入到OutPutStream
// 也可以用PrintWriter 把OutputStream 包裹下 写入字符串
PrintWriter printWriter = new PrintWriter(outputStream);
//此处println不是打印到控制台了,而是写入到outputStream对应的流对象中了,也就是写入到clientSocket里面
//自然这个数据也就通过网络发送出去了
//此处使用println带有\n也是为了后续客户端 可以使用scanner.next来读取数据
printWriter.println(response);
//还要刷新缓存区,保证写入
printWriter.flush();
//打印这次请求交互过程的内容
System.out.printf("[%s:%d] req =%s , resp=%s \n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
}
}catch (IOException e){
e.printStackTrace();
} finally{
try {
//在这个地方,进行clientSocket的关闭
//processConnection就是在处理一个连接。这个方法执行完毕,这个连接也就处理完了
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public String process(String request) {
return request;
}
最后补一个main方法:
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
前面写过的 DatagramSocket, ServerSocket 都没写close,但是我们说这个东西都没关系.
但是clientSocket 如果不关闭,就会真的泄露了!!!
DatagramSocket 和ServerSocket,都是在程序中,只有这么一个对象. 周期,都是贯穿整个程序的.
ClientSocket 则是在循环中,每次有一个新的客户端来建立连接,都会创建出新的clientSocket
此时,如果有很多客户端都来建立连接~~
此时,就意味着每个连接都会创建clientSocket.当连接断开
clientSocket 就失去作用了.但是如果没有手动close
此时这个socket 对象就会占据着文件描述符表的位置.
try
(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getoutputStream())
这里的关闭,只是关闭了clientSocket上自带的流对象,并没有关闭socket本身.
所以我们在最后finally里面再手动的close
客户端写法:
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(String severIp,int serverPort) throws IOException {
//需要在创建Socket同时,和服务器建立连接,此时就要告诉socket服务器在哪里
//具体建立连接的细节,不需要手动代码干预
//当我们new这个对象时候,操作系统内核,就开始三次握手具体细节,完成了建立连接的过程
socket = new Socket(severIp,serverPort);
}
public void start(){
//tcp客户端和udp客户端差不多
Scanner sc=new Scanner(System.in);
try(InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
PrintWriter writer = new PrintWriter(outputStream);) {
Scanner scannerNetwork=new Scanner(inputStream);
while (true) {
//1.从控制台读取用户输入的内容
System.out.println("->");
String request = sc.next();
//2.把字符串作为请求,发给服务器
//此处使用println是为了请求后面带上\n
//也就是和服务器读取请求 scanner.next相呼应
writer.println(request);
writer.flush();
//3.读取服务器返回的响应
String response = scannerNetwork.next();
//4.在界面上显示内容
System.out.println(response);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
tcpEchoClient.start();
}
}
服务端写法
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 executorService= Executors.newCachedThreadPool();
while(true){
//通过accept方法把内核中已经建立好的连接拿到应用程序
//建立连接的细节流程都是自动完成的,应用程序只需要捡现成的
Socket clientSocket=serverSocket.accept();
//此处不应该直接调用processConnection 会导致服务器不能处理多个服务器
/* Thread t = new Thread(()->{
processConnection(clientSocket);
});
t.start();*/
//线程池
executorService.submit(()->{
processConnection(clientSocket);
});
}
}
//通过这个方法处理当前连接
private void processConnection(Socket clientSocket) {
//进入方法,打印日志,表示当前有客户端连上了
System.out.printf("[%s:%d] 客户端上线",clientSocket.getInetAddress(),clientSocket.getPort());
//接下来进行数据的交互
try(InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream()){
//使用try()方式,避免后续用完了,流对象忘记关闭。
//由于客户端发来的数据可能是多条数据,所以我们循环处理
while(true){
Scanner scanner = new Scanner(inputStream);
if(!scanner.hasNext()){
//连接断开了,循环应该结束
System.out.printf("[%s:%d] 客户端下线",clientSocket.getInetAddress(),clientSocket.getPort());
break;
}
//1.读取请求并解析,此处以next作为读取请求的方式,next规则就是:读到“空白符“就返回
String request = scanner.next();
//2.根据请求计算响应
String response = process(request);
//3.把响应写回到客户端
// 可以把String转换为字节数组 写入到OutPutStream
// 也可以用PrintWriter 把OutputStream 包裹下 写入字符串
PrintWriter printWriter = new PrintWriter(outputStream);
//此处println不是打印到控制台了,而是写入到outputStream对应的流对象中了,也就是写入到clientSocket里面
//自然这个数据也就通过网络发送出去了
//此处使用println带有\n也是为了后续客户端 可以使用scanner.next来读取数据
printWriter.println(response);
//还要刷新缓存区,保证写入
printWriter.flush();
//打印这次请求交互过程的内容
System.out.printf("[%s:%d] req =%s , resp=%s \n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
}
}catch (IOException e){
e.printStackTrace();
} finally{
try {
//在这个地方,进行clientSocket的关闭
//processConnection就是在处理一个连接。这个方法执行完毕,这个连接也就处理完了
clientSocket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
tcpEchoServer.start();
}
}
提一嘴回调
普通调用:
我主动调用你
回调:
我先把方法/任务交给你
以后由你主动调用我提供的方法
Thread t = new Thread(() -> {
// 这里相当于回调
});
我把一段“以后要执行的代码”交给别人,等某个时机到了,由别人反过来调用它。
匿名内部类是在“创建一个临时的类对象”,Lambda 更像是在“给函数式接口的唯一抽象方法提供一段实现代码”。
比如:
new Thread(() -> {
System.out.println("执行任务");
});
你可以理解成:
Thread需要一个 Runnable
↓
Runnable只有一个 run()
↓
Lambda告诉 Java:
“这个 run() 方法就执行这段代码”
这就是 Lambda 最核心的用途。
Lambda 只能用于函数式接口
函数式接口就是:
只有一个抽象方法的接口
TCP的那个代码内容经过多版优化所以不好讲出怎么一步一步得出的,所以 只能通过看代码注释理解

浙公网安备 33010602011771号