网络编程
1. 网络编程概述
1. 1 什么是网络编程
网络编程,是指 在不同进程之间、不同计算机设备之间进行数据交互与通信的编程技术。
网络通信的基本架构主要有两种形式:
-
一种是CS架构(Client 客户端/Server服务端)
游戏,微信,QQ
-
一种是BS架构(Brower 浏览器/Server服务端)
京东,淘宝,百度等等所有通过浏览器访问的web应用,都是BS架构
- CS架构的特点: CS架构需要用户在自己的电脑或者手机上安装客户端软件,然后由客户端软件通过网络连接服务器程序,由服务器把数据发给客户端,客户端就可以在页面上看到各种数据了。
- BS架构的特点: BS架构不需要开发客户端软件,用户只需要通过浏览器输入网址就可以直接从服务器获取数据,并由服务器将数据返回给浏览器,用户在页面上就可以看到各种数据了。
这两种结构不管是CS、还是BS都是需要用到网络编程的相关技术。我们学习Java的程序员,以后从事的工作方向主要还是BS架构的。
1.2 为什么要学习网络编程
各种高级编程语言几乎都提供了网络编程技术,Java也不例外,相关的类和API都在java.net包下。Java的很多框架,特别是微服务相关的构架,底层都大量使用到了网络编程技术。
所以,我们学习网络编程技术的目的是什么?
其实是开发工作中,几乎是完全不需要我们进行网络编程。但是网络编程作为一项基础技术,掌握它的概念和作用,对于我们将来的学习是有支撑和帮助的,利于快速掌握微服务相关的技术。
学习网络编程的要求:
- 理解网络编程的作用
- 理解网络网络编程相关的技术(开发中不需要我们写相关代码)
2. 网络编程三要素★
如果.实现网络通信,必须要满足以下三要素:
- IP地址:用于在网络中定位到某一台计算机设备
- 端口:用于在计算机设备中,与指定程序进行通信的出入口
- 协议:计算机设备之间进行 连接和数据传输 的规则
2.1 IP地址
IP(Ineternet Protocol)全称互联网协议地址,是分配给网络设备的唯一标识。
IP地址分为:
- IPV4地址:目前应用最为广泛
- IPV6地址:正在推广,以期望将来替代掉IPv4
2.1.1 IPv4
IPv4介绍
IPV4地址由32个比特位组成,将每8位看成一组,把每一组用十进制表示(叫做点分十进制表示法)
所以就有了我们经常看到的IP地址形式,如:192.168.1.66
如何查看本机IPv4地址
如果想查看本机的IP地址,可以在命令行窗口,输入ipconfig命令查看
:bulb: Mac或Linux系统里,在终端里执行命令
ifconfig可查看ip地址
IP地址枯竭问题
IPv4由32bit组成,所以只有2^32^个可用地址,大概是42亿个。
但是随着互联网的不断发展,现在越来越多的设备需要联网,IPV4地址已经不够用了,所以就有了IPv6地址。
2.1.2 IPv6
介绍
IPV6采用128位二进制数据来表示(16个字节),可用地址约有3.4E+38个,号称可以为地球上的每一粒沙子编一个IP地址。
IPV6比较长,为了方便阅读,每16位编成一组,每组采用十六进制数据表示,然后用冒号隔开(称为冒分十六进制表示法),如下图所示
如何查看本机IPv6地址
我们在命令行窗口输入ipconfig命令,同样可以看到ipv6地址,如下图所示
现在的网络设备,一般IPV4和IPV6地址都是支持的。
2.1.3 域名
什么是域名
当我们需要访问一个网址时,可以根据ip地址进行访问。由于IP地址不方便记忆,并且不能显示地址组织的名称和性质,人们设计出了域名。
比如百度,域名是www.baidu.com,ip地址是110.242.68.4。 很明显,域名比ip更好记:
- 通过ip地址访问:http://110.242.68.4 (md文档里,按住ctrl点击链接,可以直接在浏览器里打开网址)
- 通过域名访问:www.baidu.com
域名解析原理
域名和IP其实是一一对应的,由运营商来管理域名和IP的对应关系。我们在浏览器上敲一个域名时,首先由运营商的域名解析服务,把域名转换为ip地址,再通过IP地址去访问对应的服务器设备。
2.1.4 ping命令
本机ip:127.0.0.1
本机域名:localhost
测试网络命令:ping 域名或ip ping命令出现以下的提示,说明网络是通过的
2.1.5 InetAddress类
Java按照面向对象的思想,提供了一个类封装了IP地址相关的方法,这个类是java.net.InetAddress。常用的API如下:
示例代码
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class DemoInetAddress {
public static void main(String[] args) throws IOException {
//要求1:获取本机的ip信息
InetAddress local = InetAddress.getLocalHost();
String localHostName = local.getHostName();
System.out.println("本机的主机名 :" + localHostName);
String localHostAddress = local.getHostAddress();
System.out.println("本机的ip地址 :" + localHostAddress);
//要求2:获取www.baidu.com的ip信息
InetAddress baidu = InetAddress.getByName("www.baidu.com");
String baiduHostName = baidu.getHostName();
System.out.println("baidu的域名 : " + baiduHostName);
String baiduHostAddress = baidu.getHostAddress();
System.out.println("baidu的ip地址:" + baiduHostAddress);
//要求3:ping一下www.bai.com,验证能否ping通
boolean reachable = InetAddress.getByName("www.bai.com").isReachable(200);
System.out.println("reachable = " + reachable);
}
}
2.2 端口号
端口:port,是计算机里的程序与外界通信交流的出入口,总共有65536个,范围0~65535。任何应用程序如果想要通过网络与外界交互,就必须占用一个端口。
注意:端口是抢占性资源,哪个程序先占用端口,其它程序就不能用这个端口了,否则会出现端口冲突。
端口分类:了解
- 周知端口:0~1023,被预先定义的知名应用程序占用(如:HTTP占用80,FTP占用21,HTTPS占用443等等)
- 注册端口:1024~49151,分配给用户经常或者某些应用程序。应用程序通常使用这些端口
- 动态端口:49152~65536,之所以称为动态端口,是因为它一般不固定分配给某进程,而是动态分配的。
2.3 协议
网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
为了让世界上各种上网设备能够互联互通,肯定需要有一个组织出来,指定一个规则,大家都遵守这个规则,才能进行数据通信。
2.3.1 TCP/IP网络模型
只要按照OSI网络参考模型制造的设备,就可以在国际互联网上互联互通。其中传输层有两个协议,是我们今天会接触到的(UDP协议、TCP协议)
2.3.2 UDP协议
UDP:User Datagram Protocol,用户数据报协议。为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。效率高但不可靠
特点:
- 无连接:双向通信的时候不需要建立连接
- 不可靠:通信中可能丢失数据包
- 通信效率高
适用场景:
- 视频直播、游戏等等
2.3.3 TCP协议
TCP:Transmission Control Protocol,传输控制协议。是一种面向连接的、可靠的、基于字节流的传输层通信协议。
特点:
- 面向连接:通信双方必须建立连接,才可能通信
- 可靠通信:通信时会校验数据完整性,底层会尽一切可能保证数据的完整可靠;如果数据实在是不完整,报错
- 通信效率略低
适用场景:
- BS架构的基本都是用TCP。淘宝、百度、京东等等
2.3.3.1 TCP建立连接:三次握手
2.3.3.2 TCP断开连接:四次挥手
-
三次握手:建立连接
第一次:客户端向服务端发送请求,尝试建立 (客户端->服务端) 连接
第二次:服务端收到请求后确认可连接;并向客户端发数据,尝试建立(服务端->客户端)连接
第三次:客户端收到请求后确认可连接
最终:双向都确认已建立连接
-
四次挥手:断开连接
第一次:客户端向服务端发,尝试断开(客户端->服务端的)连接
第二次:服务端给客户端返回确认; 客户端->服务端方向成功断开
第三次:服务端向客户端发,尝试断开(服务端->客户端)连接
第四次:客户端给服务端返回确认; 服务端->客户端成功断开
3. UDP通信(入门案例)
Java提供了一个类叫java.net.DatagramSocket来完成基于UDP协议的收发数据。使用DatagramSocket收发数据时,数据要以数据包的形式体现,一个数据包限制在64KB以内。
下面我们看一个案例,需要有两个程序,一个表示客户端程序,一个表示服务端程序。
需求:发送者程序发一个字符串数据给 接收者,接收者程序接收数据并打印。
3.1 思路分析
3.1.1 API介绍
DatagramPacket
UDP的数据包对象,相当于一个盒子。UDP通信的双方必须借助这个对象进行数据的包装与拆解:
- 发送者:把数据封装成DatagramPacket对象,才能把数据发出去
- 接收者:接收到的数据会放到DatagramPacket对象里,我们才能成功获取数据
构造方法:
常用方法:
DatagramSocket
构造方法
常用方法:
3.1.2 实现过程
UDP发送数据的步骤:
1. 创建`DatagramSocket`对象:`new DatagramSocket()`
2. 准备数据包:`new DatagramPacket(byte[] 数据, int 数据长度, InetAddress.getLocalHost(), int 端口)`
3. 发送数据包:使用`DatagramSocket`的`send(DatagramPacket对象)`
4. 释放资源:关闭`DatagramSocket`对象,调用close()方法
UDP接收数据的步骤:
1. 创建`DatagramSocket`对象,监听端口:`new DatagramSocket(端口)`
2. 准备空的数据包:`new DatagramPacket(byte[] 数据, int 数据长度)`
3. 接收数据包:使用`DatagramSocket`的`receive(DatagramPacket对象)`,接收到的数据被封装到了数据包里
4. 从数据包里获取数据:`new String(数据包.getData(), 数据包.getOffset(), 数据包.getLength())`
5. 释放资源:关闭`DatagramSocket`对象,调用close()方法
3.2. 示例代码
3.2.1 发送数据
public class Sender {
public static void main(String[] args) throws IOException {
//创建udp套接字对象:这个对象可以帮我们发、收 udp数据包
DatagramSocket datagramSocket = new DatagramSocket();
//准备发送数据:new DatagramPacket(数据字节数组,数据长度,目标ip地址,目标端口)
byte[] data = "hello".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 7777)
datagramSocket.send(packet);
//关闭
datagramSocket.close();
}
}
3.3.2 接收数据
public class Receiver {
public static void main(String[] args) throws IOException {
//创建udp套接字
DatagramSocket datagramSocket = new DatagramSocket(7777);
//准备接收数据
byte[] buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
datagramSocket.receive(packet);
//解析收到的数据
String data = new String(packet.getData(), packet.getOffset(), packet.getLength());
String senderAddr = packet.getAddress().getHostAddress();
System.out.println("接收到" + senderAddr + "发送的数据:" + data);
}
}
4. UDP通信(持续通信)
刚才的案例,我们只能客户端发一次,服务端接收一次就结束了。下面我们想把这个代码改进一下,
需求:实现客户端不断的发数据,而服务端能不断的接收数据,客户端发送exit时客户端程序退出。
4.1 思路分析
4.2 示例代码
4.2.1 发送数据
public class Sender {
public static void main(String[] args) throws IOException {
//创建udp套接字对象:这个对象可以帮我们发、收 udp数据包
DatagramSocket datagramSocket = new DatagramSocket();
//准备发送数据
Scanner scanner = new Scanner(System.in);
while (true) {
String str = scanner.next();
if ("bye".equals(str)) {
break;
}
byte[] data = str.getBytes();
datagramSocket.send(new DatagramPacket(data, data.length, InetAddress.getLocalHost(), 7777));
}
//关闭
datagramSocket.close();
}
}
4.2.2 接收数据
public class Receiver {
public static void main(String[] args) throws IOException {
//创建udp套接字
DatagramSocket datagramSocket = new DatagramSocket(7777);
//准备接收数据
byte[] buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
while (true) {
datagramSocket.receive(packet);
//解析收到的数据
String data = new String(packet.getData(), packet.getOffset(), packet.getLength());
String senderAddr = packet.getAddress().getHostAddress();
System.out.println(senderAddr + "发送了数据:" + data);
}
}
}
5.TCP通信(入门案例)【掌握】
Java提供了java.net.Socket类和java.net.ServerSocket类来完成TCP通信。
ServerSocket:服务端套接字对象。用于监听端口、获取Socket连接对象Socket:相当于连接对象。我们可以从这个连接对象里接收数据,或者通过这个连接对象输出数据
5.1 TCP思路分析
5.1.1 API介绍
Socket
国内通常把Socket称为“套接字”,这个翻译很拉胯。我们可以形象的把它理解为一个管子,管子的一方是当前程序自己,另一方是通信的目标;通过这个管子可以传输数据;而且这个管子是双筒的,可以双向同时传输数据。
| 方法 | 返回值 | 说明 |
|---|---|---|
new Socket(String host, int port) | Socket | 连接目标主机的目标端口 |
getInputStream() | InputStream | 获取输入流 通过这个输入流可以从Socket里读取数据 |
getOutputStream() | OutputStream | 获取输出流 通过这个输出流可以向Socket里输出数据 |
close() | void | 关闭连接 |
ServerSocket
用于监听指定端口,等待客户端连接的服务端对象
| 方法 | 返回值 | 说明 |
|---|---|---|
new ServerSocket(int port) | ServerSocket | 开始监听指定端口号 |
accept() | Socket | 获取连接进来的Socket对象 |
close() | void | 关闭服务器 |
5.1.2 实现过程
TCP通信的步骤:
客户端:
1. 先创建Socket对象,连接目标服务器的目标端口:`new Socket("目标ip", 目标端口号)`
2. 通过Socket向服务端发数据:`socket.getOutputStream()`,使用这个流输出数据,数据最终流向服务端
3. 通过Socket从服务端接收数据:`socket.getInputStream()`,使用这个流读取数据,读取的是服务端返回的数据
4. 关闭流、关闭socket
服务端
1. 开始监听某一端口:`new ServerSocket(端口号)`
2. 接受客户端的连接:`Socket socket = serverSocket.accept()`
3. 通过Socket接收客户端发过来的数据:`socket.getInputStream()`
4. 通过Socket向客户端返回数据:`socket.getOutputStream()`
5. 关闭流、释放资源
5.2 TCP入门示例
5.2.1 TCP客户端
下面我们写一个客户端,用来往服务端发数据。由于原始的字节流不是很好用,这里根据我的经验,将原始的OutputStream包装为DataOutputStream是比较好用的。
/**
* 目标:完成TCP通信快速入门-客户端开发:实现1发1收。
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1、创建Socket对象,并同时请求与服务端程序的连接。
Socket socket = new Socket("127.0.0.1", 8888);
// 2、从socket通信管道中得到一个字节输出流,用来发数据给服务端程序。
OutputStream os = socket.getOutputStream();
// 3、把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
// 4、开始写数据出去了
dos.writeUTF("在一起,好吗?");
dos.close();
socket.close(); // 释放连接资源
}
}
5.2.2 TCP服务端
上面我们只是写了TCP客户端,还没有服务端,接下来我们把服务端写一下。这里的服务端用来接收客户端发过来的数据。
/**
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
*/
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("-----服务端启动成功-------");
// 1、创建ServerSocket的对象,同时为服务端注册端口。
ServerSocket serverSocket = new ServerSocket(8888);
// 2、使用serverSocket对象,调用一个accept方法,等待客户端的连接请求
Socket socket = serverSocket.accept();
// 3、从socket通信管道中得到一个字节输入流。
InputStream is = socket.getInputStream();
// 4、把原始的字节输入流包装成数据输入流
DataInputStream dis = new DataInputStream(is);
// 5、使用数据输入流读取客户端发送过来的消息
String rs = dis.readUTF();
System.out.println(rs);
// 其实我们也可以获取客户端的IP地址
System.out.println(socket.getRemoteSocketAddress());
dis.close();
socket.close();
}
}
6. TCP通信(持续通信)
到目前为止,我们已经完成了客户端发送消息、服务端接收消息,但是客户端只能发一次,服务端只能接收一次。现在我想要客户端能过一直发消息,服务端能够一直接收消息。
下面我们把客户端代码改写一下,采用键盘录入的方式发消息,为了让客户端能够一直发,我们只需要将发送消息的代码套一层循环就可以了,当用户输入exit时,客户端退出循环并结束客户端。
6.1 思路分析
6.2 示例代码
6.2.1 TCP客户端
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 7777);
Scanner scanner = new Scanner(System.in);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
DataInputStream dis = new DataInputStream(socket.getInputStream());
while (true) {
//读取键盘录入的数据
System.out.println("请输入:");
String data = scanner.nextLine();
//通过socket,把数据发送给服务端
dos.writeUTF(data);
if ("bye".equals(data)) {
break;
}
//通过socket,接收服务端返回的结果
String answer = dis.readUTF();
System.out.println("服务端响应: " + answer);
}
dis.close();
dos.close();
scanner.close();
socket.close();
}
}
6.2.2 TCP服务端
为了让服务端能够一直接收客户端发过来的消息,服务端代码也得改写一下。我们只需要将读取数据的代码加一个循环就可以了。
但是需要我们注意的时,如果客户端Socket退出之后,就表示连接客户端与服务端的数据通道被关闭了,这时服务端就会出现异常。服务端可以通过出异常来判断客户端下线了,所以可以用try...catch把读取客户端数据的代码套一起来,catch捕获到异常后,打印客户端下线。
public class Server {
private static Set<Socket> sockets = new HashSet<>();
public static void main(String[] args) throws IOException {
//1. 创建服务端套接字对象,监听端口
ServerSocket serverSocket = new ServerSocket(7777);
//2. 获取连接对象
Socket socket = serverSocket.accept();
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//3. 不断尝试从socket连接里读取数据、并给客户端返回“收到”
while (true) {
//从管道里读取数据 并打印
String data = dis.readUTF();
System.out.println("data = " + data);
//向客户端返回数据
dos.writeUTF("收到");
}
}
}
7. TCP通信(多线程改进)
上一个案例中我们写的服务端程序只能和一个客户端通信,如果有多个客户端连接服务端,此时服务端是不支持的。
7.1 思路分析
为了让服务端能够支持多个客户端通信,就需要用到多线程技术。具体的实现思路如下图所示:每当有一个客户端连接服务端,在服务端这边就为Socket开启一条线程取执行读取数据的操作,来多少个客户端,就有多少条线程。按照这样的设计,服务端就可以支持多个客户端连接了。
按照上面的思路,只要改写服务端代码即可。
7.2 示例代码
7.2.1 服务端的工作线程
首先,我们需要写一个服务端的读取数据的线程类,代码如下
public class ServerWorkerThread extends Thread {
private Socket socket;
public ServerWorkThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
) {
//不断尝试从socket连接里读取数据、返回数据
while (true) {
//从管道里读取数据 并打印
String data = dis.readUTF();
System.out.println("data = " + data);
//向客户端返回数据
dos.writeUTF("收到");
}
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + "连接已断开");
}
}
}
7.2.2 服务端的主线程
接下来,再改写服务端的主程序代码,如下:
public class Server {
public static void main(String[] args) throws IOException {
//1. 创建服务端套接字对象,监听端口
ServerSocket serverSocket = new ServerSocket(7777);
//2. 开始获取连接:可能有多个客户端连接进来,每个连接都会有一个Socket对象。
while (true) {
//获取连接对象
Socket socket = serverSocket.accept();
//把连接对象交给一个工作线程进行处理。当前线程继续等待连接
new ServerWorkThread(socket).start();
}
}
}
8. BS架构程序(简易版Tomcat)
前面我们所写的代码都是基于CS架构的。我们说网络编程还可以编写BS架构的程序,为了让同学们体验一下BS架构通信,这里我们写一个简易版的程序。仅仅只是体验一下,方便后期理解web应用的运行原理。
8.1 思路分析
BS架构程序的实现原理,如下图所示:不需要开发客户端程序,此时浏览器就是客户端,此时我们只需要写服务端程序就可以了。
在BS结构的程序中,浏览器和服务器通信是基于HTTP协议来完成的,浏览器给客户端发送数据需要按照HTTP协议规定好的数据格式发给服务端,服务端返回数据时也需要按照HTTP协议规定好的数据给是发给浏览器,只有这两双方才能完成一次数据交互。
客户端程序不需要我们编写(浏览器就是),所以我们只需要写服务端就可以了。
服务端给客户端响应数据的数据格式(HTTP协议规定数据格式)如下图所示:左图是数据格式,右图是示例。
接下来,我们写一个服务端程序按照右图示例的样子,给浏览器返回数据。注意:数据是由多行组成的,必须按照规定的格式来写。
8.2 示例代码
8.2.1 服务端程序
8.2.1.1 工作线程类
先写一个线程类,用于按照HTTP协议的格式返回数据
public class TomcatWorkerThread extends Thread{
private Socket socket;
public TomcatWorkerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
//向浏览器返回数据头:这些都是浏览器要求返回的内容
os.write("HTTP/1.1 200\r\n".getBytes());
os.write("Content-Type: text/html; charset=utf-8\r\n".getBytes());
os.write("\r\n".getBytes());
//向浏览器返回数据内容:这些是要让浏览器显示出来的内容。我们把html文件内容返回给浏览器
FileInputStream is = new FileInputStream("demo03-net/src/main/java/d08_tomcat/hello.html");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//向客户端浏览器返回一个结束标志,否则浏览器收不到结束标志,就会一直转圈
socket.shutdownOutput();
} catch (IOException e) {
System.out.println("连接已断开");
}
}
}
8.2.1.2 主线程
public class TomcatServer {
public static void main(String[] args) throws IOException {
//1. 创建ServerSocket,监听8080端口
ServerSocket ss = new ServerSocket(8080);
//2. 不断获取客户端连接,每个连接都交给一个工作线程进行处理
while (true) {
//获取连接
Socket socket = ss.accept();
//把连接交给一个线程进行处理。主线程继续等待下一个连接
new TomcatWorkerThread(socket).start();
}
}
}
8.2.2 使用线程池改进
为了避免服务端创建太多的线程,可以把服务端用线程池改进,提高服务端的性能。
先写一个给浏览器响应数据的线程任务
public class TomcatWorkerRunnable implements Runnable{
private Socket socket;
public TomcatWorkerRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
//向浏览器返回数据头:这些都是浏览器要求返回的内容
os.write("HTTP/1.1 200\r\n".getBytes());
os.write("Content-Type: text/html; charset=utf-8\r\n".getBytes());
os.write("\r\n".getBytes());
//向浏览器返回数据内容:这些是要让浏览器显示出来的内容。我们把html文件内容返回给浏览器
FileInputStream is = new FileInputStream("demo03-net/src/main/java/d08_tomcat/hello.html");
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
//向客户端浏览器返回一个结束标志,否则浏览器收不到结束标志,就会一直转圈
socket.shutdownOutput();
} catch (IOException e) {
System.out.println("连接已断开");
}
}
}
再改写服务端的主程序,使用ThreadPoolExecutor创建一个线程池,每次接收到一个Socket就往线程池中提交任务就行。
public class TomcatServer {
public static void main(String[] args) throws IOException {
//1. 创建ServerSocket,监听8080端口
ServerSocket ss = new ServerSocket(8080);
//2. 不断获取客户端连接,每个连接都交给一个工作线程进行处理
//2.1 准备一个线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 6, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), new ThreadPoolExecutor.CallerRunsPolicy());
//2.2 获取连接,交给线程池进行处理
while (true) {
//获取连接
Socket socket = ss.accept();
//把连接交给一个线程进行处理。主线程继续等待下一个连接
executor.execute(new TomcatWorkerRunnable(socket));
}
}
}