什么是网络编程?
网络通信基本模式:
关于网络编程同学们需要学会什么?
实现网络编程关键的三要素:
IP地址:设备在网络中的地址,是唯一的标识。
端口:应用程序在设备中唯一的标识。
协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议。
IP地址:
IPv4:
32bit(4字节)。
// 二进制
11000000 10101000 00000001 01000010
// 十进制
192.168.1.66
IPv4专用网络:
在IPv4所允许的大约四十亿地址中,三个地址块被保留作专用网络。这些地址块在专用网络之外不可路由,专用网络之内的主机也不能直接与公共网络通信。但通过网络地址转换(NAT),使用这些地址的主机可以像拥有共有地址的主机在互联网上通信。
名字 | 地址范围 | 地址数量 | 有类别的描述 | 最大的CIDR地址块 |
---|---|---|---|---|
24位块 | 10.0.0.0–10.255.255.255 | 16,777,216 | 一个A类 | 10.0.0.0/8 |
20位块 | 172.16.0.0–172.31.255.255 | 1,048,576 | 连续的16个B类 | 172.16.0.0/12 |
16位块 | 192.168.0.0–192.168.255.255 | 65,536 | 连续的256个C类 | 192.168.0.0/16 |
IPv6:
128位(16字节),号称可以为地址每一粒沙子编号。
IPv8分为8个整数,每个整数用四个十六进制表示,数之间用冒号分开。
// 二进制
1010101111001101 1110111100000001 10001101000101 110011110001001 1010101111001101 1110111100000001 10001101000101 110011110001001
// 十六进制
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789
IP常用命令:
特殊IP地址:
InetAddress的使用:
InetAddress API如下:
名称 | 说明 |
---|---|
public static InetAddress getLocalHost() | 返回本主机的地址对象。 |
public static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者IP地址。 |
public String getHostName() | 获取此IP地址的主机名。 |
publit Straing getHostAddress() | 返回IP地址字符串。 |
publit boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回ture。 |
Test.java
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test {
public static void main(String[] args) throws Exception {
// 获取本机地址对象
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println(inetAddress.getHostName());
System.out.println(inetAddress.getHostAddress());
// 获取域名IP对象
InetAddress inetAddress1 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress1.getHostName());
System.out.println(inetAddress1.getHostAddress());
// 获取公网IP对象
InetAddress inetAddress2 = InetAddress.getByName("14.215.177.39");
System.out.println(inetAddress2.getHostName());
System.out.println(inetAddress2.getHostAddress());
// 判断是否能通:ping 25秒内测试是否可通
System.out.println(inetAddress2.isReachable(25000));
}
}
端口号:
端口类型:
注意:我们自己开发的程序选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
按端口号可分为3大类:
(1)公认端口(Well Known Ports):从0到1023,它们紧密绑定(binding)于一些服务。通常这些端口的通讯明确表明了某种服务的协议。例如:80端口实际上总是HTTP通讯。
(2)注册端口(Registered Ports):从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的。例如:许多系统处理动态端口从1024左右开始。
(3)动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535。理论上,不应为服务分配这些端口。实际上,机器通常从1024起分配动态端口。但也有例外:SUN的RPC端口从32768开始。
通信协议:
网络通信协议有两套参考模型:
OSI参考模型 | TCP/IP参考模型 | 各层对应 | 面向操作 |
---|---|---|---|
应用层 | 应用层 | HTTP、FTP、DNS、SMTP… | 应用程序需要关注的浏览器,邮箱。 |
表示层 | |||
会话层 | |||
传输层 | 传输层 | TCP、UDP… | 选择使用的TCP、UDP协议。 |
网络层 | 网络层 | IP、ICMP… | 封装源和目标IP,进行路径选择。 |
数据链路层 | 数据链路层+物理层 | 物理寻址、比特流… | 物理设备中传输。 |
物理层 |
传输层的两个常见协议:
TCP协议特点:
TCP协议通信场景:
TCP三次握手:
TCP四次挥手断开连接:
UDP协议:
UDP协议通信场景:
UDP协议的特点:
DatagramPacket:数据包对象
构造器 | 说明 |
---|---|
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 创建发送端数据包对象。 buf:要发送的内容,字节数组。 length:要发送的内容的字节长度。 address:接收端的IP地址对象。 port:接收端的端口号。 |
public DatagramPacket(byte[] buf, int length) | 创建接收端的数据包对象。 buf:用来存储接受的内容。 length:能够接受内容的长度。 |
DatagramSocket:发送端和接收端对象。
构造器 | 说明 |
---|---|
public DatagramSocket() | 创建发送端的Socket对象,系统会随机分配一个端口号。 |
public DatagramSocket(int port) | 创建接收端的Socket对象并指定端口号。 |
DatagramSocket类成员方法:
方法 | 说明 |
---|---|
public void send(DatagramPacket dp) | 发送数据包。 |
public void receive(DatagramPacket p) | 接收数据包。 |
案例:使用UDP通信实现:发送消息、接收消息
Server.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 服务端
*/
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建服务端对象
DatagramSocket socket = new DatagramSocket(8888);
// 2.创建一个数据包对象接收对象
byte[] bytes = new byte[1024 * 64];
// 接收数据包 接收数据包对象
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 3.等待接收数据
socket.receive(packet);
// 4.取出数据
// 获取接收数据的长度
int len = packet.getLength();
String rs = new String(bytes, 0, len);
System.out.println(rs);
// 获取客户端IP和端口
String ip = packet.getSocketAddress().toString();
System.out.println("客户端IP:" + ip);
String port = String.valueOf(packet.getPort());
System.out.println("客户端端口:" + port);
// 5.关闭服务端
socket.close();
}
}
Client.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建客户端对象 自带默认端口
DatagramSocket socket = new DatagramSocket();
// 2.创建一个数据包对象封装数据
// 要发送的数据
byte[] bytes = "我是1号,收到请回答!".getBytes();
// 要发送的数据 发送数据的大小 服务端IP地址 服务端端口
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8888);
// 3.发送数据
socket.send(packet);
// 4.关闭客户端
socket.close();
}
}
需要先启动服务端再启动客户端。
案例:使用UDP通信实现:多发多收消息。
需求:
分析:
Server.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 服务端 多收
*/
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建服务端对象
DatagramSocket socket = new DatagramSocket(8888);
// 2.创建一个数据包对象接收对象
byte[] bytes = new byte[1024 * 64];
// 接收数据包 接收数据包对象
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
// 3.等待接收数据
socket.receive(packet);
// 4.取出数据
// 获取接收数据的长度
int len = packet.getLength();
// 获取客户端IP 端口 消息
String message = new String(bytes, 0, len);
String ip = packet.getSocketAddress().toString();
String port = String.valueOf(packet.getPort());
System.out.println("客户端IP:" + ip + "客户端端口:" + port + "消息:" + message);
}
// 5.关闭服务端
// socket.close();
}
}
Client.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
/**
* 客户端 多发
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建客户端对象 自带默认端口
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true) {
// 2.创建一个数据包对象封装数据
// 要发送的数据
System.out.println("请输入:");
String sendMessage = scanner.nextLine();
if ("exit".equals(sendMessage)) {
System.out.println("离线成功!");
socket.close();
break;
}
byte[] bytes = sendMessage.getBytes();
// 要发送的数据 发送数据的大小 服务端IP地址 服务端端口
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8888);
// 3.发送数据
socket.send(packet);
}
// 4.关闭客户端
socket.close();
}
}
执行结果:
客户端1
请输入:
111
请输入:
222
请输入:
333
请输入:
exit
离线成功!
客户端2
请输入:
aaa
请输入:
bbb
请输入:
ccc
请输入:
exit
离线成功!
服务端:
客户端IP:/127.0.0.1:54174客户端端口:54174消息:111
客户端IP:/127.0.0.1:50373客户端端口:50373消息:aaa
客户端IP:/127.0.0.1:54174客户端端口:54174消息:222
客户端IP:/127.0.0.1:50373客户端端口:50373消息:bbb
客户端IP:/127.0.0.1:54174客户端端口:54174消息:333
客户端IP:/127.0.0.1:50373客户端端口:50373消息:ccc
......
UDP的三种通信方式:
UDP如何实现广播:
Server.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* 服务端 多收
*/
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建服务端对象
DatagramSocket socket = new DatagramSocket(9999);
// 2.创建一个数据包对象接收对象
byte[] bytes = new byte[1024 * 64];
// 接收数据包 接收数据包对象
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
// 3.等待接收数据
socket.receive(packet);
// 4.取出数据
// 获取接收数据的长度
int len = packet.getLength();
// 获取客户端IP 端口 消息
String message = new String(bytes, 0, len);
String ip = packet.getSocketAddress().toString();
String port = String.valueOf(packet.getPort());
System.out.println("客户端IP:" + ip + "端口:" + port + "消息:" + message);
}
// 5.关闭服务端
// socket.close();
}
}
Client.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
/**
* 客户端 多发
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建客户端对象 自带默认端口
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true) {
// 2.创建一个数据包对象封装数据
// 要发送的数据
System.out.println("请输入:");
String sendMessage = scanner.nextLine();
if ("exit".equals(sendMessage)) {
System.out.println("离线成功!");
socket.close();
break;
}
byte[] bytes = sendMessage.getBytes();
// 要发送的数据 发送数据的大小 服务端IP地址 服务端端口
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("255.255.255.255"), 9999);
// 3.发送数据
socket.send(packet);
}
// 4.关闭客户端
socket.close();
}
}
执行结果:
发送端
请输入:
本条信息为广播信息。
请输入:
exit
离线成功!
接收端
客户端IP:/192.168.71.128:51198端口:51198消息:本条信息为广播信息。
UDP如何实现实现组播:
Server.java
import java.net.*;
/**
* 服务端 多收
*/
public class Server {
public static void main(String[] args) throws Exception {
// 1.创建服务端对象
MulticastSocket socket = new MulticastSocket(9999);
// 当前接收端加入到一个组播组中去:绑定对应的组播消息的IP
// 该方法已过时
//socket.joinGroup(InetAddress.getByName("224.0.1.1"));
socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1"), 9999), NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));
// 2.创建一个数据包对象接收对象
byte[] bytes = new byte[1024 * 64];
// 接收数据包 接收数据包对象
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
while (true) {
// 3.等待接收数据
socket.receive(packet);
// 4.取出数据
// 获取接收数据的长度
int len = packet.getLength();
// 获取客户端IP 端口 消息
String message = new String(bytes, 0, len);
String ip = packet.getSocketAddress().toString();
String port = String.valueOf(packet.getPort());
System.out.println("客户端IP:" + ip + "客户端端口:" + port + "消息:" + message);
}
// 5.关闭服务端
// socket.close();
}
}
Client.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
/**
* 客户端 多发
*/
public class Client {
public static void main(String[] args) throws Exception {
// 1.创建客户端对象 自带默认端口
DatagramSocket socket = new DatagramSocket();
Scanner scanner = new Scanner(System.in);
while (true) {
// 2.创建一个数据包对象封装数据
// 要发送的数据
System.out.println("请输入:");
String sendMessage = scanner.nextLine();
if ("exit".equals(sendMessage)) {
System.out.println("离线成功!");
socket.close();
break;
}
byte[] bytes = sendMessage.getBytes();
// 要发送的数据 发送数据的大小 服务端IP地址 服务端端口
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("224.0.1.1"), 9999);
// 3.发送数据
socket.send(packet);
}
// 4.关闭客户端
socket.close();
}
}
执行结果:
发送端
请输入:
本次发送的消息为组播消息。
请输入:
exit
离线成功!
接收端
客户端IP:/192.168.71.128:48519客户端端口:48519消息:本次发送的消息为组播消息。
总结:
TCP协议回顾:
Socket:
构造器 | 说明 |
---|---|
public Socket(String host,int port) | 创建发送端的Socket对象与服务端连接,参数为服务端程序的IP和端口。 |
注意:java.net.Socket类实现通信,底层使用TCP协议。
Socket类成员方法:
方法 | 说明 |
---|---|
OutputStream getOutputStream() | 获得字节输出流对象。 |
InputStream getInputStream() | 获得字节输入流对象。 |
客户端发送消息:
Client.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
/**
* 客户端
* 目标:完成Socket网络编程入门案例的客户端开发 实现一发一收
*/
public class Client {
public static void main(String[] args) {
try {
// 1.创建客户端的Socket对象 请求与服务端的连接
Socket socket = new Socket("127.0.0.1", 7777);
// 2.使用Socket对象调用getOutputStream()方法得到字节输出流
OutputStream outputStream = socket.getOutputStream();
// 3.把低级的字节流包装成打印流
PrintStream printStream = new PrintStream(outputStream);
// 4.发送消息
printStream.println("TCP客户端请求连接, 是否允许连接?");
// 5.关闭资源 一般不关闭
// socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ServerSocket:
构造器 | 说明 |
---|---|
public ServerSocket(int port) | 注册服务端端口。 |
ServerSocket类成员方法:
方法 | 说明 |
---|---|
public Socket accpet() | 等待接收客户端的Socket通信连接,连接成功后返回Socket对象与客户端建立端到端通信。 |
Server.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
* 目标:开发Socket网络编程入门的服务端 实现接收消息
*/
public class Server {
public static void main(String[] args) {
System.out.println("===服务端口启动===");
try {
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2.必须调用accept方法 等待接收客户端的Socket连接请求 建立Socket通信管道
Socket socket = serverSocket.accept();
// 3.从Socket通信管道中得到一个字节输入流
InputStream inputStream = socket.getInputStream();
// 4.把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 5.按照行读取消息
String message;
while ((message = bufferedReader.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + ":" + message);
}
// 客户端断开后 自动结束程序
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行结果:
===服务端口启动===
/127.0.0.1:39898:TCP客户端请求连接, 是否允许连接?
需求:使用TCP通信方式实现多发多收消息。
Server.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
* 目标:开发Socket网络编程入门的服务端 实现接收多收消息
*/
public class Server {
public static void main(String[] args) {
System.out.println("===服务端启动===");
try {
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// 2.必须调用accept方法 等待接收客户端的Socket连接请求 建立Socket通信管道
Socket socket = serverSocket.accept();
// 3.从Socket通信管道中得到一个字节输入流
InputStream inputStream = socket.getInputStream();
// 4.把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 5.按照行读取消息
String message;
while ((message = bufferedReader.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + ":" + message);
}
// 客户端断开后 自动结束程序
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
* 目标:完成Socket网络编程入门案例的客户端开发 实现多发消息
*/
public class Client {
public static void main(String[] args) {
System.out.println("===客户端启动===");
try {
// 1.创建客户端的Socket对象 请求与服务端的连接
Socket socket = new Socket("127.0.0.1", 7777);
// 2.使用Socket对象调用getOutputStream()方法得到字节输出流
OutputStream outputStream = socket.getOutputStream();
// 3.把低级的字节流包装成打印流
PrintStream printStream = new PrintStream(outputStream);
// 4.发送消息
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入:");
String message = scanner.nextLine();
if ("exit".equals(message)) {
System.out.println("离线成功!");
break;
}
printStream.println(message);
printStream.flush();
}
// 5.关闭资源 一般不关闭
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行结果:
===客户端启动===
请输入:
大弦嘈嘈如急雨
请输入:
小弦切切如私语
请输入:
exit
离线成功!
===服务端启动===
/127.0.0.1:43732:大弦嘈嘈如急雨
/127.0.0.1:43732:小弦切切如私语
本案例实现了多发多收,那么是否可以同时接收多个客户端的消息?
ServerReaderThread.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* Socket读取类
*/
public class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.从Socket通信管道中得到一个字节输入流
InputStream inputStream = socket.getInputStream();
// 4.把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 5.按照行读取消息
String message;
while ((message = bufferedReader.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + ":" + message);
}
// 客户端断开连接后 线程结束
System.out.println(socket.getRemoteSocketAddress() + "下线了");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Server.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端
* 目标:实现服务端可以同时处理多个客户端的消息
*/
public class Server {
public static void main(String[] args) {
System.out.println("===服务端启动===");
try {
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线负责不断的接收客户端的Socket连接
while (true) {
// 2.必须调用accept方法 等待接收客户端的Socket连接请求 建立Socket通信管道
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
// 创建Socket读取类对象并启动子线程
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
* 目标:完成Socket网络编程入门案例的客户端开发 实现多发消息
*/
public class Client {
public static void main(String[] args) {
System.out.println("===客户端启动===");
try {
// 1.创建客户端的Socket对象 请求与服务端的连接
Socket socket = new Socket("127.0.0.1", 7777);
// 2.使用Socket对象调用getOutputStream()方法得到字节输出流
OutputStream outputStream = socket.getOutputStream();
// 3.把低级的字节流包装成打印流
PrintStream printStream = new PrintStream(outputStream);
// 4.发送消息
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入:");
String message = scanner.nextLine();
if ("exit".equals(message)) {
System.out.println("离线成功!");
break;
}
printStream.println(message);
printStream.flush();
}
// 5.关闭资源 一般不关闭
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行结果:
客户端一
===客户端启动===
请输入:
你好呀!
请输入:
你还好吧?
请输入:
exit
离线成功!
客户端二
===客户端启动===
请输入:
hello!
请输入:
How are you!
请输入:
exit
离线成功!
服务端
===服务端启动===
/127.0.0.1:43012上线了
/127.0.0.1:44556上线了
/127.0.0.1:43012:你好呀!
/127.0.0.1:44556:hello!
/127.0.0.1:43012:你还好吧?
/127.0.0.1:44556:How are you!
/127.0.0.1:43012下线了
/127.0.0.1:44556下线了
总结:
引入线程池处理多个客户端消息。
ServerReaderRunnable.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* Socket读取线程池类
*/
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.从Socket通信管道中得到一个字节输入流
InputStream inputStream = socket.getInputStream();
// 4.把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 5.按照行读取消息
String message;
while ((message = bufferedReader.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + ":" + message);
}
// 客户端断开连接后 线程结束
System.out.println(socket.getRemoteSocketAddress() + "下线了");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Server.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
* 服务端
* 目标:使用线程池优化实现通信 实现服务端可以同时处理多个客户端的消息
*/
public class Server {
// 使用静态变量记住一个线程池对象
// 核心线程数3个
// 最多线程数5个
// 临时线程的最大存活时间6秒
// 任务队列大小为2
private static ExecutorService pool = new ThreadPoolExecutor(
3, 5,6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()
);
public static void main(String[] args) {
System.out.println("===服务端启动===");
try {
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线负责不断的接收客户端的Socket连接
while (true) {
// 2.必须调用accept方法 等待接收客户端的Socket连接请求 建立Socket通信管道
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
// 创建Socket读取线程池类对象
Runnable target = new ServerReaderRunnable(socket);
// 在线程池中执行
pool.execute(target);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client.java
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
* 目标:完成Socket网络编程入门案例的客户端开发 实现多发消息
*/
public class Client {
public static void main(String[] args) {
System.out.println("===客户端启动===");
try {
// 1.创建客户端的Socket对象 请求与服务端的连接
Socket socket = new Socket("127.0.0.1", 7777);
// 2.使用Socket对象调用getOutputStream()方法得到字节输出流
OutputStream outputStream = socket.getOutputStream();
// 3.把低级的字节流包装成打印流
PrintStream printStream = new PrintStream(outputStream);
// 4.发送消息
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入:");
String message = scanner.nextLine();
if ("exit".equals(message)) {
System.out.println("离线成功!");
break;
}
printStream.println(message);
printStream.flush();
}
// 5.关闭资源 一般不关闭
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
执行结果(依次启动八个客户端,轮询输入消息):
===客户端启动===
请输入:
大家好,我是甲。
请输入:
exit
离线成功!
===客户端启动===
请输入:
大家好,我是乙。
请输入:
exit
离线成功!
===客户端启动===
请输入:
大家好,我是丙。
请输入:
exit
离线成功!
===客户端启动===
请输入:
大家好,我是丁。大家能够收到我的消息吗?
请输入:
我是丁。
请输入:
exit
离线成功!
===客户端启动===
请输入:
大家好,我是戊。大家能够收到我的消息吗?
请输入:
我是戊。
请输入:
exit
离线成功!
===客户端启动===
请输入:
大家好,我是己。大家能够收到我的消息吗?
请输入:
exit
离线成功!
===客户端启动===
请输入:
大家好,我是庚。大家能够收到我的消息吗?
请输入:
exit
离线成功!
===客户端启动===
请输入:
大家好,我是辛。大家能够收到我的消息吗?
请输入:
exit
离线成功!
===服务端启动===
/127.0.0.1:54898上线了
/127.0.0.1:54900上线了
/127.0.0.1:42856上线了
/127.0.0.1:42862上线了
/127.0.0.1:54898:大家好,我是甲。
/127.0.0.1:54900:大家好,我是乙。
/127.0.0.1:42856:大家好,我是丙。
/127.0.0.1:59298上线了
/127.0.0.1:58358上线了
/127.0.0.1:58358:大家好,我是己。大家能够收到我的消息吗?
/127.0.0.1:45978上线了
/127.0.0.1:45978:大家好,我是庚。大家能够收到我的消息吗?
/127.0.0.1:51684上线了
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.javase.tcpdemo3.ServerReaderRunnable@1b28cdfa rejected from java.util.concurrent.ThreadPoolExecutor@eed1f14[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
at com.javase.tcpdemo3.Server.main(Server.java:37)
/127.0.0.1:54898下线了
/127.0.0.1:42862:大家好,我是丁。大家能够收到我的消息吗?
/127.0.0.1:54900下线了
/127.0.0.1:59298:大家好,我是戊。大家能够收到我的消息吗?
/127.0.0.1:42862:我是丁。
/127.0.0.1:59298:我是戊。
/127.0.0.1:42856下线了
/127.0.0.1:42862下线了
/127.0.0.1:59298下线了
/127.0.0.1:58358下线了
/127.0.0.1:45978下线了
注意:
总结:
Server.java
import java.awt.print.Printable;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
/**
* 服务端
* 目标:实现服务端可以同时处理多个客户端的消息
*/
public class Server {
// 定义一个静态List集合存储当前全部在线的Socket管道
public static List<Socket> allOlineSockets = new ArrayList<>();
public static void main(String[] args) {
System.out.println("===服务端启动===");
try {
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(7777);
// a.定义一个死循环由主线负责不断的接收客户端的Socket连接
while (true) {
// 2.必须调用accept方法 等待接收客户端的Socket连接请求 建立Socket通信管道
Socket socket = serverSocket.accept();
System.out.println(socket.getRemoteSocketAddress() + "上线了");
// 将Socket管道加入到集合中
allOlineSockets.add(socket);
// 创建Socket读取类对象并启动子线程
new ServerReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Socket读取线程类
*/
class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.从Socket通信管道中得到一个字节输入流
InputStream inputStream = socket.getInputStream();
// 4.把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 5.按照行读取消息
String message;
while ((message = bufferedReader.readLine()) != null) {
System.out.println(socket.getRemoteSocketAddress() + ":" + message);
// 把消息进行端口转发给全部客户端Socket管道
sendMsgToAll(message);
}
// 客户端断开连接后 线程结束
System.out.println(socket.getRemoteSocketAddress() + "下线了");
// 将Socket管道从到集合中移出
Server.allOlineSockets.remove(socket);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 发送消息给所有人
*
* @param message 消息
*/
private void sendMsgToAll(String message) throws Exception {
for (Socket olineSocket : Server.allOlineSockets) {
if (!socket.equals(olineSocket)) {
PrintStream printStream = new PrintStream(olineSocket.getOutputStream());
printStream.println(message);
printStream.flush();
}
}
}
}
Client.java
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端
* 1.客户端发送消息
* 2.客户端随时可能需要收到消息
*/
public class Client {
public static void main(String[] args) {
System.out.println("===客户端启动===");
try {
// 1.创建客户端的Socket对象 请求与服务端的连接
Socket socket = new Socket("127.0.0.1", 7777);
// a.创建一个独立线程专门负责客户端读消息
new ClientReaderThread(socket).start();
// 2.使用Socket对象调用getOutputStream()方法得到字节输出流
OutputStream outputStream = socket.getOutputStream();
// 3.把低级的字节流包装成打印流
PrintStream printStream = new PrintStream(outputStream);
// 4.发送消息
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("请输入:");
String message = scanner.nextLine();
if ("exit".equals(message)) {
System.out.println("离线成功!");
break;
}
printStream.println(message);
printStream.flush();
}
// 5.关闭资源 一般不关闭
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientReaderThread extends Thread {
private Socket socket;
public ClientReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 3.从Socket通信管道中得到一个字节输入流
InputStream inputStream = socket.getInputStream();
// 4.把字节输入流包装成缓冲字节输入流进行消息的接收
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 5.按照行读取消息
String message;
while ((message = bufferedReader.readLine()) != null) {
System.out.println("收到消息:" + message);
System.out.println("请输入:");
}
} catch (IOException e) {
System.out.println("服务端把你踢出来群聊!");
}
}
}
执行结果:
===客户端启动===
请输入:
大家好,我是甲!
请输入:
收到消息:大家好,我是乙!
请输入:
很高兴认识大家!
请输入:
收到消息:欢迎!
请输入:
exit
离线成功!
服务端把你踢出来群聊!
===客户端启动===
请输入:
收到消息:大家好,我是甲!
请输入:
大家好,我是乙!
请输入:
收到消息:很高兴认识大家!
请输入:
欢迎!
请输入:
exit
离线成功!
服务端把你踢出来群聊!
===服务端启动===
/127.0.0.1:48480上线了
/127.0.0.1:48494上线了
/127.0.0.1:48494:大家好,我是甲!
/127.0.0.1:48480:大家好,我是乙!
/127.0.0.1:48494:很高兴认识大家!
/127.0.0.1:48480:欢迎!
/127.0.0.1:48494下线了
/127.0.0.1:48480下线了
总结:
实现BS开发:
注意:服务器必须给浏览器响应HTTP协议格式的数据,否则浏览器不识别。
HTTP请求报文结构:
HTTP响应报文结构:
HTTP报文语法:
BrowserServer.java
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 多线程实现
*/
public class BrowserServer {
public static void main(String[] args) {
try {
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(8080);
// 2.循环接收多个客户端请求
while (true) {
Socket socket = serverSocket.accept();
// 3.交给一个独立线程处理请求
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ServerReaderThread extends Thread {
private Socket socket;
public ServerReaderThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 创建打印流对象
PrintStream printStream = new PrintStream(socket.getOutputStream());
// 响应头
printStream.println("HTTP/1.1 200 OK");
printStream.println("Content-Type:text/html;charset=UTF-8");
printStream.println();
// 正文
printStream.println("模拟BS系统
");
// 关闭流
printStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行程序后,在浏览器中访问:
http://127.0.0.1:8080/
模拟BS系统
BrowserServer.java
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
/**
* 线程池实现
*/
public class BrowserServer {
private static ExecutorService pool = new ThreadPoolExecutor(
3, 5,6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()
);
public static void main(String[] args) {
try {
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(8080);
// 2.循环接收多个客户端请求
while (true) {
Socket socket = serverSocket.accept();
// 创建Socket读取线程池类对象
Runnable target = new ServerReaderRunnable(socket);
// 在线程池中执行
pool.execute(target);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Socket读取线程池类
*/
class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 创建打印流对象
PrintStream printStream = new PrintStream(socket.getOutputStream());
// 响应头
printStream.println("HTTP/1.1 200 OK");
printStream.println("Content-Type:text/html;charset=UTF-8");
printStream.println();
// 正文
printStream.println("模拟BS系统
");
// 关闭流
printStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行程序后,在浏览器中访问:
http://127.0.0.1:8080/
模拟BS系统
总结: