一、网络基础
网络通信
- 概念:两台设备之间通过网络实现数据传输
- 网络通信:将数据通过网络从一台设备传输到另一台设备
- java.net包下提供一系列的类或接口,供我们使用
IP地址
- 概念:用于唯一标识网络中的每台计算机/主机
- 查看ip地址:ipconfig
- ip地址的表现形式: 点分十进制 xx.xx.xx.xx
- 每一个十进制数的范围:0-255
- ip地址=网络地址+主机地址 比如: 192.168.16.69
- IPv4使用4个字节32位表示
- IPv6使用128位表示地址 16个字节 是IPv4的4倍
域名
- 概念:将ip地址映射成域名, HTTP协议
- 好处:为了方便记忆,解决记ip的困难
端口号
- 概念:用于标识计算机上某个特定的网络程序
- 表示形式:以整数形式,范围 0-65535
- 0-1024 已经被占用, ssh 22,ftp 21,smtp 25,http 80
- 常见网络程序的端口号: tomcat 8080,mysql 3306,oracle 1521,sqlserver 1433
分层
二、InetAddress
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @Author: mei_ming
* @DateTime: 2022/10/1 0:13
* @Description: 网络编程
* 一、网络编程中有两个主要的问题:
* 1. 如何准确地定位网络上一台或多台主机:定位主机上的特定的应用
* 2. 找到主机后,如何可靠高效的进行数据传输
*
* 二、网络编程中的两个要素:
* 1. 对应问题1,IP和端口号
* 2. 对应问题2,提供网络通信协议:TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层)
*
* 三、通信要素一:IP和端口号
* 1. IP:唯一的标识Internet上的计算机(通信实体)
* 2. 在Java中使用InetAddress类代表IP
* 3. IP分类:IPv4 和 IPv6、万维网 和 局域网
* 4. 域名(方便记忆)
* www.baidu.com
* 5. 回环地址 127.0.0.1
* 6. 实例化InetAddress:getByName(String host) / getLocalHost()
* 两个常用方法:getHostName() / getHostAddress()
* 7. 端口号:正在计算机上运行的进程
* 范围:被规定为一个16位的整数,0-65535
* 公认端口:0-1023 (HTTP: 80, FTP: 21, Telnet: 23)
* 注册端口:1024-49151 分配给用户进程或应用程序 (Tomcat: 8080, MySQL: 3306, Oracle: 1521)
* 动态/私有端口: 49152-65535
* 8. 端口号与IP地址的组合得出一个网络套接字 : Socket
*
* 四、通信要素二:网络通信协议
*
* 网络通信协议:计算机网络中实现通信必须有一些约定,
* 即通信协议,对速率、传输代码、代码结构。传输控制步骤、出错控制等制定标准。
*
* TCP/IP协议簇
* 传输层协议中有两个非常重要的协议:
* 传输控制协议TCP(Transmission Control Protocol)
* 用户数据报协议UDP(User Datagram Protocol)
* TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,
* 包括多个具有不同功能且互为关联的协议。
* IP(Internet Protocol) 协议是网络层的主要协议,支持网间互联的数据通信。
* TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层
*
* TCP 和 UDP
* 1. TCP协议
* 使用TCP协议前,须先建立TCP连接,形成传输数据通道
* 传输前,采用 ‘三次握手’ 方式,点对点通信,是可靠的
* TCP协议进行通信的两个应用进程: 客户端、服务端
* 在连接中可进行大数据量的传输
* 传输完毕,需释放已建立的连接(四次挥手),效率低
* 2. UDP协议
* 将数据、源、目的封装成数据包,不需要建立连接
* 每个数据包限制在64k内
* 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
* 可以广播发送
* 发送数据结束时无需释放资源,开销小,速度快
*
*
*/
public class InetAddressTest {
@Test
public void test1(){
try {
InetAddress inet1 = InetAddress.getByName("127.0.0.1");
System.out.println(inet1); // /127.0.0.1
InetAddress inet2 = InetAddress.getByName("www.baidu.com");
System.out.println(inet2); // www.baidu.com/220.181.38.150
System.out.println(inet2.getHostName()); // www.baidu.com
System.out.println(inet2.getHostAddress()); // 220.181.38.149
//快速获取本地IP
InetAddress inet3 = InetAddress.getLocalHost();
System.out.println(inet3); // MING/172.16.3.213 (主机名/IP)
InetAddress inet4 = InetAddress.getByName("MING");
System.out.println(inet4); // MING/172.16.3.213
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
三、Socket
基本介绍
- 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准
- 通信的两端都要有Socket,是两台机器间通信的端点
- 网络通信其实就是Socket间的通信
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输
- 一般主动发起通信的属客户端,等待通信请求的为服务端
有两种编程方式:
TCP编程(可靠)/UDP编程(不可靠)
TCP实现
案例一、客户端发送信息给服务端, 服务端将数据显示在控制台上
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPTest1 {
// 客户端
@Test
public void client(){
Socket socket = null;
OutputStream os = null;
try {
//1.创建Socket对象,指明服务端的ip和端口号
socket = new Socket(InetAddress.getLocalHost(), 8888);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.写操作
os.write("你好,服务端!我是客户端。".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭资源
if (os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 服务端
@Test
public void serve(){
ServerSocket ss = null;
Socket accept = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建ServerSocket对象,表明自己的端口
ss = new ServerSocket(8888);
//2.调用accept(),表示接收来自于客户端的socket
accept = ss.accept();
//3.获取输入流,以便控制台打印
is = accept.getInputStream();
//方式1、 (可能出现乱码,不推荐)
// byte[] buffer = new byte[5];
// int len;
// while((len=is.read(buffer))!=-1){
// System.out.println(new String(buffer,0,len));
// }
//方式2、
baos = new ByteArrayOutputStream();
byte[] bytes = new byte[5];
int len;
while((len=is.read(bytes))!=-1){
baos.write(bytes,0,len);
}
System.out.println(baos.toString());
System.out.println("来自于:"+accept.getInetAddress());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源
if (baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept!=null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss!=null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例二、客户端发送文件给服务端,服务端将文件保存在本地
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPTest2 {
// 客户端
@Test
public void client(){
Socket socket = null;
OutputStream os = null;
FileInputStream fis = null;
try {
//1.创建Socket对象,指明服务端的ip和端口号
socket = new Socket(InetAddress.getLocalHost(), 8888);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//2.2 创建文件传输流,写入文件
fis = new FileInputStream("101.jpg");
//3.写操作
byte[] bytes = new byte[5];
int len;
while((len=fis.read(bytes))!=-1){
os.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭资源
if (os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 服务端
@Test
public void serve(){
ServerSocket ss = null;
Socket accept = null;
InputStream is = null;
FileOutputStream fos = null;
try {
//1.创建ServerSocket对象,表明自己的端口
ss = new ServerSocket(8888);
//2.调用accept(),表示接收来自于客户端的socket
accept = ss.accept();
//3.获取输入流,以便控制台打印
is = accept.getInputStream();
fos = new FileOutputStream("101_download.jpg");
byte[] buffer1 = new byte[5];
int len1;
while((len1=is.read(buffer1))!=-1){
fos.write(buffer1,0,len1);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept!=null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss!=null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例三、客户端发送文件给服务端,服务端将文件保存到本地,并返回"发送成功"给客户端,并关闭相应的连接
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPTest3 {
// 客户端
@Test
public void client(){
Socket socket = null;
OutputStream os = null;
FileInputStream fis = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
//1.创建Socket对象,指明服务端的ip和端口号
socket = new Socket(InetAddress.getLocalHost(), 8888);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//2.2 创建文件传输流,写入文件
fis = new FileInputStream("101.jpg");
//3.写操作
byte[] bytes = new byte[5];
int len;
while((len=fis.read(bytes))!=-1){
os.write(bytes,0,len);
}
//关闭数据输出,
socket.shutdownOutput();
//接收来自服务端的数据,并显示到控制台
is = socket.getInputStream();
baos = new ByteArrayOutputStream();
byte[] bytes2 = new byte[5];
int len2;
while((len2=is.read(bytes2))!=-1){
baos.write(bytes2,0,len2);
}
System.out.println(baos.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4.关闭资源
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos!=null){
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 服务端
@Test
public void serve(){
ServerSocket ss = null;
Socket accept = null;
InputStream is = null;
FileOutputStream fos = null;
OutputStream os = null;
try {
//1.创建ServerSocket对象,表明自己的端口
ss = new ServerSocket(8888);
//2.调用accept(),表示接收来自于客户端的socket
accept = ss.accept();
//3.获取输入流,以便控制台打印
is = accept.getInputStream();
fos = new FileOutputStream("101_download.jpg");
byte[] buffer1 = new byte[5];
int len1;
while((len1=is.read(buffer1))!=-1){ //read() 阻塞式的,需手动关闭
fos.write(buffer1,0,len1);
}
//发送提示给客户端
os = accept.getOutputStream();
os.write("服务端已接收图片".getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源
if (os!=null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept!=null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss!=null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例三增强版:
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPTest3_2 {
// 客户端
@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
String filePath = "e:\\100.jpg";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File(filePath)));
// bytes 是filePath对应的字节数组
byte[] bytes = StreamUtils.streamToByteArray(bis);
// 通过socket 获取输出流,将bytes数据传到服务端
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
bos.write(bytes);
socket.shutdownOutput(); // 结束标记
//接收服务端消息
InputStream inputStream = socket.getInputStream();
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);
inputStream.close();
bos.close();
bis.close();
socket.close();
}
// 服务端
@Test
public void serve() throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] bytes = StreamUtils.streamToByteArray(bis); // 得到一个字解数组
String destFilePath = "src\\100.jpg";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);
bos.close();
//向客户端发送 收到
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("成功接收图片");
bw.flush(); // 把内容刷新到数据通道
socket.shutdownOutput(); // 结束标志
bw.close();
bis.close();
socket.close();
serverSocket.close();
}
}
案例四、客户端,服务端通信(字符流实现)
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPTest4 {
// 客户端
@Test
public void client(){
Socket socket = null;
OutputStream os = null;
InputStream is = null;
BufferedWriter bw = null;
BufferedReader br = null;
try {
//1.创建Socket对象,指明服务端的ip和端口号
socket = new Socket(InetAddress.getLocalHost(), 8888);
//2.获取一个输出流,用于输出数据
os = socket.getOutputStream();
//3.使用字符流的方式
bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("你好,服务器");
bw.newLine(); //插入一个换行符,表示结束标志,读取时要用readLine()
bw.flush(); //如果使用字符流,需要手动刷新
//4.接收来自服务端的数据,并显示到控制台
is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.关闭资源 (外层流)
if (br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 服务端
@Test
public void serve(){
ServerSocket ss = null;
Socket accept = null;
InputStream is = null;
OutputStream os = null;
BufferedReader br = null;
BufferedWriter bw = null;
try {
//1.创建ServerSocket对象,表明自己的端口
ss = new ServerSocket(8888);
//2.调用accept(),表示接收来自于客户端的socket
accept = ss.accept();
//3.获取输入流,以便控制台打印,以字符流的方式读取
is = accept.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
String line = br.readLine();
System.out.println(line);
//发送提示给客户端
os = accept.getOutputStream();
// 使用字符输出流的方式,进行回复
bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("你好,客户端!");
bw.newLine(); // 结束标志
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//4. 关闭资源
if (bw!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (accept!=null){
try {
accept.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ss!=null){
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
netstat 指令
netstat -an
可以查看当前主机网络情况,包括端口监听情况和网络连接情况netstat -an | more
可以分页显示
UDP实现
UDP说明:
- 没有明确的服务端和客户端,演变为数据的发送端和接收端
- 接收数据和发送数据是通过 DatagramSocket 对象完成的
- 将数据封装到DatagramPacket 对象 ,装包发送
- 当接收到DatagramPacket 对象,需要进行拆包,取出数据
- DatagramSocket 可以指定在哪个端口接收数据
import org.junit.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* @Author: mei_ming
* @DateTime: 2022/10/1 13:05
* @Description: UDP 网络通信
*
* 类 DatagramSocket 和 DatagramPacket 实现了基于UDP协议网络程序
* UDP数据通过数据报套接字 DatagramSocket 发送和接收,系统不保证
* UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达
*
* DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和
* 端口号以及接收端的IP地址和端口号
*
* 发送端 接收端
*/
public class UDPTest {
//发送端
@Test
public void sender(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
String str = "我是UDP方式发送的导弹";
byte[] bytes = str.getBytes();
InetAddress localHost = InetAddress.getLocalHost();
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, localHost,8888);
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket!=null){
socket.close();
}
}
}
//接收端
@Test
public void receiver(){
DatagramSocket socket = null;
try {
socket = new DatagramSocket(8888);
byte[] bytes = new byte[100];
DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length);
socket.receive(packet);
System.out.println(packet.getData()); //[B@32a1bec0
System.out.println(new String(packet.getData(),0,packet.getLength())); //我是UDP方式发送的导弹
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket!=null){
socket.close();
}
}
}
}
import org.junit.Test;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* @Author: mei_ming
* @DateTime: 2022/10/1 21:56
* @Description: UDP 例子2
*/
public class UDPTest2 {
//发送端
@Test
public void udpsenderB() throws IOException {
// 1. 创建 DatagramSocket 对象,准备在9998端口接收数据
DatagramSocket socket = new DatagramSocket(9998);
// 2. 将需要装包的数据 封装到 DatagramPacket 对象
byte[] data = "hello 明天吃火锅!".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName("192.168.43.96"), 9999);
socket.send(packet);
//-----接送来自接收端的回复
byte[] buf = new byte[1024];
DatagramPacket packet1 = new DatagramPacket(buf, buf.length);
socket.receive(packet1);
byte[] data1 = packet1.getData();
int length = packet1.getLength();
String s = new String(data1, 0, length);
System.out.println(s);
socket.close();
System.out.println("B端退出");
}
//接收端
@Test
public void udpreceiverA() throws IOException {
//1. 创建一个 DatagramSocket 对象,准备在9999接收数据
DatagramSocket socket = new DatagramSocket(9999);
//2. 构建一个 DatagramPacket 对象,准备接收数据
// udp最大数据报 为 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 调用 接收方法,将通过网络传输的 DatagramPacket 对象
// 填充到 packet 对象
// 当有数据报发送到 本机的9999端口时,就会接收数据,
// 否则 就会阻塞等待
System.out.println("接收端A 登海接收数据...");
socket.receive(packet);
//4. 可以把packet 进行拆包,取出数据,并显示
int length = packet.getLength();
byte[] data = packet.getData();
String s = new String(data, 0, length);
System.out.println(s);
//-------发送回复信息给发送端
byte[] data2 = "好的,明天见!".getBytes();
DatagramPacket packet1 = new DatagramPacket(data2, data2.length, InetAddress.getByName("192.168.43.96"), 9998);
socket.send(packet1);
socket.close();
System.out.println("A端退出");
}
}
作业1.
import org.junit.Test;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* @Author: mei_ming
* @DateTime: 2022/10/1 22:46
* @Description: 按照输入内容,下载资源,若服务端没有想要的,则返回默认文件
*/
public class TCPTest5 {
//客户端
@Test
public void client() throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(),8888);
OutputStream os = socket.getOutputStream();
//Scanner 要写在main中
// Scanner scanner = new Scanner(System.in);
// System.out.println("请输入名字:");
// String next = scanner.next();
// String next = "100"; // 正常下载,100.jpg
String next = "111"; // 下载 CCUT.jpg
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write(next);
bw.flush();
socket.shutdownOutput();
//--- 接收数据
InputStream is = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] bytes = StreamUtils.streamToByteArray(bis); // 得到一个字解数组
String destFilePath = "src\\"+next+".jpg";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);
bos.close();
bis.close();
bw.close();
socket.close();
}
//服务端
@Test
public void serve() throws IOException {
ServerSocket ss = new ServerSocket(8888);
System.out.println("服务端在8888端口监听 等待连接");
Socket socket = ss.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String s = br.readLine();
System.out.println("客户端希望下载的文件名="+s);
String destFile = "";
if("100".equals(s)){
//发送100.jpg
destFile="100";
}else{
//发送默认 CCUT.jpg
destFile="CCUT";
}
String destFilePath = "e:\\"+destFile+".jpg";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(destFilePath));
byte[] bytes = StreamUtils.streamToByteArray(bis); // 得到一个字解数组
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes);
socket.shutdownOutput(); // 结束标志
bos.close();
bis.close();
br.close();
socket.close();
ss.close();
}
}