一、网络编程三要素
1.1、IP
设备在网络中的地址,是设备在网络中的唯一标识。目前,被广泛采用的 IP 地址形式有两种: IPv4 、 IPv6 。
1.1.1、IPv4
IPv4 是 Internet Protocol version 4 的缩写,它使用 32 位地址,通常以点分十进制表示。

1.1.2、IPv6
Pv6 是 Internet Protocol version 6 的缩写,它使用 128 位地址,号称可以为地球上的每一粒沙子编号。
IPv6 分成 8 段,每段每四位编码成一个十六进制位表示, 每段之间用冒号( : )分开,将这种方式称为冒分十六进制。

1.1.3、IP 域名( Domain Name )
用于在互联网上识别和定位网站的人类可读的名称。
例如:www.baidu.com、www.itheima.com
1.1.4、DNS 域名解析( Domain Name System )
是互联网中用于将域名转换为对应 IP 地址的分布式命名系统。它充当了互联网的"电话簿",将易记的域名映射到数字化的IP地址,使得用户可以通过域名来访问网站和其他网络资源。

1.1.5、公网 IP、内网 IP
公网 IP :是可以连接到互联网的 IP 地址;
内网 IP :也叫局域网 IP ,是只能组织机构内部使用的 IP 地址;例如, 192.168. 开头的就是常见的局域网地址,范围为 192.168.0.0--192.168.255.255 ,专门为组织机构内部使用。
本机IP:127.0.0.1 、 localhost :代表本机 IP ,只会寻找当前程序所在的主机。
IP常用命令:ipconfig---查看本机IP地址。、pingIP地址---检查网络是否连通。
1.1.6、InetAddress 的常用方法
InetAddress代表IP

1.1.7、示例
java
import java.net.InetAddress;
public class InetAddressDemo1 {
public static void main(String[] args) {
// 目标:认识InetAddress获取本机IP对象和对方IP对象。
try {
// 1.获取本机IP对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
// 2、获取对方IP对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());
System.out.println(ip2.getHostAddress());
// 3、判断本机与对方主机是否互通
System.out.println(ip2.isReachable(5000)); // false ping
}catch (Exception e){
e.printStackTrace();
}
}
}
1.2、端口
应用程序在设备中的唯一标识。用来标记标记正在计算机设备上运行的应用程序,被规定为一个 16 位的二进制,范围是 0~65535 。
1.2.1、端口分类
周知端口: 0~1023 ,被预先定义的知名应用占用(如: HTTP 占用 80 , FTP 占用 21 )
注册端口: 1024~49151 ,分配给用户进程或某些应用程序。
动态端口: 49152 到 65535 ,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则报错。
1.3、协议
连接和数据在网络中传输的规则。网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。为了让全球所有上网设备都能互联互通,需要指定一套统一的标准
1.3.1、开放式网络互联标准: OSI 网络参考模型
OSI 网络参考模型:全球网络互联标准。TCP/IP 网络模型:事实上的国际标准。

1.3.2、传输层的 2 个通信协议
UDP(User Datagram Protocol) :用户数据报协议。TCP(Transmission Control Protocol) :传输控制协议。
1.3.2.1、UDP 协议
1、应用:通信效率高、视频直播、语音通话
2、特点:无连接、不可靠通信。
不事先建立连接,数据按照包发,一包数据包含:自己的 IP 、端口、目的地 IP 、端口和数据(限制在 64KB 内)等。
发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的 。
1.3.2.2、TCP 协议
1、应用
通信效率相对不高、可靠性更高、网页、文件下载、支付
2、特点:面向连接、可靠通信。
TCP 的最终目的:要保证在不可靠的信道上实现可靠的数据传输。
TCP 主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接。
3、三次握手建立可靠连接
可靠连接:确保通信的双方收发消息都是没问题的(全双工)
传输数据会进行确认,以保证数据传输的可靠性

4、四次挥手断开连接
目的: 确保通信的双方收发消息都已经完成

二、UDP通信
2.1、UDP通信的实现
Java 提供了一个 java.net.DatagramSocket 类来实现 UDP 通信。
2.1.1、DatagramSocket:用于创建客户端、服务端

2.1.2、DatagramPacket :创建数据包

2.2、一发一收 UDP实现步骤
2.2.1、客户端实现步骤
1、创建DatagramSocket对象(客户端对象)
2、创建DatagramPacket对象封装需要发送的数据(数据包对象)
3、使用DatagramSocket对象的send方法,传入DatagramPacket对象
4、释放资源
2.2.2、服务端实现步骤
1、创建DatagramSocket对象并指定端口(服务端对象)
2、创建DatagramPacket对象接收数据(数据包对象)
3、使用DatagramSocket对象的receive方法,传入DatagramPacket对象
4、释放资源
2.2.3、示例代码
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClientDemo1 {
public static void main(String[] args) throws Exception {
// 目标:完成UDP通信一发一收:客户端开发
System.out.println("===客户端启动==");
// 1、创建发送端对象
DatagramSocket socket = new DatagramSocket(); // 随机端口
// 2、创建数据包对象封装要发送的数据。
byte[] bytes = "我是客户端,约你今晚啤酒、龙虾、小烧烤".getBytes();
/**
* public DatagramPacket(byte[] buf, int length,
* InetAddress address, int port)
* 参数一:发送的数据,字节数组。
* 参数二:发送的字节长度。
* 参数三:目的地的IP地址。
* 参数四:服务端程序的端口号
*/
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(), 8080);
// 3、让发送端对象发送数据包的数据
socket.send(packet);
socket.close();
}
}
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServerDemo2 {
public static void main(String[] args) throws Exception {
// 目标:完成UDP通信一发一收:服务端开发。
System.out.println("==服务端启动了===");
// 1、创建接收端对象,注册端口。
DatagramSocket socket = new DatagramSocket(8080);
// 2、创建一个数据包对象负责接收数据。
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
// 3、接收数据,将数据封装到数据包对象的字节数组中去
socket.receive(packet);
// 4、看看数据是否收到了
int len = packet.getLength(); // 获取当前收到的数据长度。
String data = new String(buf, 0 , len);
System.out.println("服务端收到了:" + data);
// 获取对方的ip对象和程序端口
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方ip:" + ip + " 对方端口:" + port);
socket.close();
}
}
2.3、多发多收 UDP实现步骤
2.2.1、客户端实现步骤
1、创建DatagramSocket对象(客户端对象)
2、 使用 while 死循环不断的接收用户的数据输入,如果用户输入的 exit 则退出程序
3、 如果用户输入的不是 exit, 把数据封装成 DatagramPacket
4、使用DatagramSocket对象的send方法,传入DatagramPacket对象
5、释放资源
2.2.2、服务端实现步骤
1、创建DatagramSocket对象并指定端口(服务端对象)
2、创建DatagramPacket对象接收数据(数据包对象)
3、使用DatagramSocket对象的receive方法,传入DatagramPacket对象
4、使用 while 死循环不断的进行第 3 步
2.2.3、示例代码
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
// 都听的懂,但是记不住!
public class UDPClientDemo1 {
public static void main(String[] args) throws Exception {
// 目标:完成UDP通信多发多收:客户端开发
System.out.println("===客户端启动==");
// 1、创建发送端对象(代表抛韭菜的人)
DatagramSocket socket = new DatagramSocket(); // 随机端口
Scanner sc = new Scanner(System.in);
while (true) {
// 2、创建数据包对象封装要发送的数据。
System.out.println("请说:");
String msg = sc.nextLine();
// 如果用户输入的是 exit,则退出
if ("exit".equals(msg)) {
System.out.println("===客户端退出==");
socket.close();
break;
}
byte[] bytes = msg.getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length,
InetAddress.getLocalHost(), 8080);
// 3、让发送端对象发送数据包的数据
socket.send(packet);
}
}
}
java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServerDemo2 {
public static void main(String[] args) throws Exception {
// 目标:完成UDP通信多发多收:服务端开发。
System.out.println("==服务端启动了===");
// 1、创建接收端对象,注册端口。
DatagramSocket socket = new DatagramSocket(8080);
// 2、创建一个数据包对象负责接收数据。
byte[] buf = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
while (true) {
// 3、接收数据,将数据封装到数据包对象的字节数组中去
socket.receive(packet); // 等待式接收数据。
// 4、看看数据是否收到了
int len = packet.getLength(); // 获取当前收到的数据长度。
String data = new String(buf, 0 , len);
System.out.println("服务端收到了:" + data);
// 获取对方的ip对象和程序端口
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方ip:" + ip + " 对方端口:" + port);
System.out.println("----------------------------------------------");
}
}
}
三、TCP通信
3.1、TCP通信的实现
Java 提供了一个 java.net.Socket 类来实现 TCP 通信。

3.1.1、客户端开发
java.net.Socket

3.1.2、服务端开发
java.net.ServerSocket

3.2、一发一收 TCP实现步骤
客户端实现步骤
1、创建客户端的Socket对象,请求与服务端的连接。
2、使用socket对象调用getoutputStream()方法得到字节输出流。
3、使用字节输出流完成数据的发送。
4、释放资源:关闭socket管道。
服务端实现步骤
1、创建ServerSocket对象,注册服务端端口。
2、调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象。
3、通过Socket对象调用getInputStream()方法得到字节输入流、完成数据的接收。
4、释放资源:关闭socket管道
java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
// 目标:实现TCP通信下一发一收:客户端开发。
System.out.println("客户端启动....");
// 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
Socket socket = new Socket("127.0.0.1", 9999);
// 2、从socket通信管道中得到一个字节输出流。
OutputStream os = socket.getOutputStream();
// 3、特殊数据流。
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(1);
dos.writeUTF("我想你了,你在哪儿?");
// 4、关闭资源。
socket.close();
}
}
java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws Exception {
// 目标:实现TCP通信下一发一收:服务端开发。
System.out.println("服务端启动了...");
// 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(9999);
// 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
Socket socket = ss.accept();
// 3、获取输入流,读取客户端发送的数据
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成特殊数据输入流
DataInputStream dis = new DataInputStream(is);
// 5、读取数据
int id = dis.readInt();
String msg = dis.readUTF();
System.out.println("id=" + id + ",收到的客户端msg=" + msg);
// 6、客户端的ip和端口(谁给我发的)
System.out.println("客户端的ip=" + socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口=" + socket.getPort());
}
}
3.3、多发多收 TCP实现步骤
1、客户端使用死循环,让用户不断输入消息。
2、服务端也使用死循环,控制服务端程序收完消息后,继续去接收下一个消息。
java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
// 目标:实现TCP通信下多发多收:客户端开发。
System.out.println("客户端启动....");
// 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
Socket socket = new Socket("127.0.0.1", 9999);
// 2、从socket通信管道中得到一个字节输出流。
OutputStream os = socket.getOutputStream();
// 3、特殊数据流。
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if ("exit".equals(msg)) {
System.out.println("退出成功!");
dos.close(); // 关闭输出流
socket.close(); // 关闭socket
break;
}
dos.writeUTF(msg); // 发送数据
dos.flush();
}
}
}
java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws Exception {
// 目标:实现TCP通信下多发多收:服务端开发。
System.out.println("服务端启动了...");
// 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(9999);
// 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
Socket socket = ss.accept();
// 3、获取输入流,读取客户端发送的数据
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成特殊数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
// 5、读取数据
String msg = dis.readUTF(); // 等待读取客户端发送的数据
System.out.println("收到的客户端msg=" + msg);
// 6、客户端的ip和端口(谁给我发的)
System.out.println("客户端的ip=" + socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口=" + socket.getPort());
System.out.println("--------------------------------------------------");
}
}
}
3.4、多发多收,支持多个客户端开发 TCP实现步骤

java
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws Exception {
// 目标:实现TCP通信下多发多收:支持多个客户端开发。
System.out.println("客户端启动....");
// 1、常见Socket管道对象,请求与服务端的Socket链接。可靠链接
Socket socket = new Socket("127.0.0.1", 9999);
// 2、从socket通信管道中得到一个字节输出流。
OutputStream os = socket.getOutputStream();
// 3、特殊数据流。
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请说:");
String msg = sc.nextLine();
if ("exit".equals(msg)) {
System.out.println("退出成功!");
dos.close(); // 关闭输出流
socket.close(); // 关闭socket
break;
}
dos.writeUTF(msg); // 发送数据
dos.flush();
}
}
}
java
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws Exception {
// 目标:实现TCP通信下多发多收:服务端开发。支持多个客户端开发。
System.out.println("服务端启动了...");
// 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(9999);
while (true) {
// 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
Socket socket = ss.accept();
System.out.println("一个客户端上线了:" + socket.getInetAddress().getHostAddress());
// 3、把这个客户端管道交给一个独立的子线程专门负责接收这个管道的消息。
new ServerReader(socket).start();
}
}
}
java
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;
public class ServerReader extends Thread{
private Socket socket;
public ServerReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 读取管道的消息
// 3、获取输入流,读取客户端发送的数据
InputStream is = socket.getInputStream();
// 4、把字节输入流包装成特殊数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
// 5、读取数据
String msg = dis.readUTF(); // 等待读取客户端发送的数据
System.out.println("收到的客户端msg=" + msg);
// 6、客户端的ip和端口(谁给我发的)
System.out.println("客户端的ip=" + socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口=" + socket.getPort());
System.out.println("--------------------------------------------------");
}
} catch (Exception e) {
System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
}
}
}
3.5、使用线程池优化


HTTP 协议规定:响应给浏览器的数据格式必须满足如下格式

java
//import com.heima.net.demo6tcp3.ServerReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;
public class ServerDemo {
public static void main(String[] args) throws Exception {
// 目标:BS架构的原理理解
System.out.println("服务端启动了...");
// 1、创建服务端ServerSocket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(8080);
// 创建线程池
ExecutorService pool = new ThreadPoolExecutor(3, 10, 10, TimeUnit.SECONDS
, new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
while (true) {
// 2、调用accept方法,阻塞等待客户端连接,一旦有客户端链接会返回一个Socket对象
Socket socket = ss.accept();
System.out.println("一个客户端上线了:" + socket.getInetAddress().getHostAddress());
// 3、把这个客户端管道包装成一个任务交给线程池处理
pool.execute(new ServerReaderRunnable(socket));
}
}
}
java
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 给当前对应的浏览器管道响应一个网页数据回去。
OutputStream os = socket.getOutputStream();
// 通过字节输出流包装写出去数据给浏览器
// 把字节输出流包装成打印流。
PrintStream ps = new PrintStream(os);
// 写响应的网页数据出去
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type:text/html;charset=utf-8");
ps.println(); // 必须换一行
ps.println("<html>");
ps.println("<head>");
ps.println("<meta charset='utf-8'>");
ps.println("<title>");
ps.println("黑马Java磊哥的视频");
ps.println("</title>");
ps.println("</head>");
ps.println("<body>");
ps.println("<h1 style='color:red;font-size=20px'>听黑马Java磊哥的视频</h1>");
// 响应一个黑马程序员的log展示
ps.println("<img src='https://www.itheima.com/images/logo.png'>");
ps.println("</body>");
ps.println("</html>");
ps.close();
socket.close();
} catch (Exception e) {
System.out.println("客户端下线了:"+ socket.getInetAddress().getHostAddress());
}
}
}
四、综合案例
暂待....