网络编程
简介
-
概述:
- 网络编程也叫Socket编程,也叫套接字编程,就是用来实现 网络互联的 不同的计算机上 运行的程序间 可以进行数据交互
-
原理:
- 通信两端都独有自己的Socket对象, 数据在两个Socket之间通过IO流 或者 **数据报包(DatagramPacket)**的方式进行传输
- 例如: 你给你女朋友打电话, 看起来好像是你们两个人在沟通, 其实是: 两个手机在沟通.
软件结构:
- C/s结构:
- 概述:指的就是 Client(客户端) / **Server(服务器端)**结构,即:需要用户安装指定的软件.
- 好处:
1.界面相对比较美观.
2.提高用户体验. 界面美观,用起来舒服.
3.降低服务器压力,因为可以把一些非重要级别的文件让用户提前安装. - 弊端:
- 当服务器端升级的时候,客户端也需要同步升级更新.
- B/s结构:
- 概述:指的是Browser(浏览器端) / **Server(服务器端)**结构,即:用户只需要安装一个浏览器即可.
- 好处:
- 提高用户体验 操作简单,只需要一个浏览器即可.
- 不受地域(设备)限制.
- 弊端:
服务器压力过大,所需的所有的资料和数据都需要从服务器下载.
三大要素
IP地址:
-
概述:指的是设备(电脑,手机,Ipad…)在网络中的唯一标识.
-
组成: //此处以IPv4举例.
- 网络(网关)号码 + 主机地址
-
分类:
- 城域网: 1 + 3
- 广域网: 2 + 2
- 局域网: 3 + 1,即:前三段属于网关号码, 最后一段是主机地址.
例如: 192.168.16.52, 192.168.16.99 …
-
两个特殊的IP:
- 127.0.0.1 本机回环(回路)地址,即:代表本机.
- 255.255.255.255 广播地址
-
两个DOS指令:
- 查看IP的: ipconfig/all
- 测试网络连接的: pig ip地址/网址 -t
InetAddress:IP地址的包装类
- 概述:它是java.net包下的,用之前,需要先导包.它表示IP地址对象形式.
成员方法:
String形式的 IP -> IP 地址对象
- public static InetAddress getByName(String ipOrHostname); 根据主机名或者字符串ip, 获取其对应的IP地址对象
IP地址对象 -> String形式的IP
- public String getHostAddress(); 根据Ip地址对象, 获取其对应的: 字符串形式的IP.
- public String getHostName(); 根据Ip地址对象, 获取其对应的: 字符串形式的主机名.
案例:InetAddress简介
package com.ithiema.api.demo;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Demo02 {
public static void main(String[] args) throws UnknownHostException {
String ip = "192.168.0.105";
String host = "YULIANG";
InetAddress inet1 = InetAddress.getByName(ip);
InetAddress inet2 = InetAddress.getByName(host);
//String类型的IP -> InetAddress IP地址对象.
System.out.println(inet1);
System.out.println(inet2);
//InetAddress IP地址对象 -> String类型的IP
System.out.println(inet1.getHostName());
System.out.println(inet1.getHostAddress());
System.out.println(inet2.getHostName());
System.out.println(inet2.getHostAddress());
System.out.println("---------------------");
}
}
端口号:
-
概述:指的是程序在设备中的唯一标识.
-
范围:065535**,其中**01234已经被系统占用了,或者作保留端口,你在自定义端口的时候,尽量不要用这个范围.
-
和端口号相关的几个DOS命令:
- netstat -ano 查看本机所有的端口号
- netstat -ano /finder 3306 查看指定的端口号被哪个pid程序占用了
- taskkill /f/pid 4636 根据pid值,强制关闭某个进程.
协议:
- 概述:设备之间进行数据传输时所要遵循的规则,范例,就叫:协议.
- 分类:UDP,TCP。
UDP:
//类似于: QQ群聊.
- 面向无连接.
- 采用数据报包的形式发送数据, 每个包的大小不能超过64KB.
- 不安全(不可靠)协议.
- 效率相对较高.
- 不区分客户端和服务器端, 叫: 发送端和接收端.
涉及到的API:
- DatagramSocket类中的方法:
- 构造方法:
- public DatagramSocket();
- public DatagramSocket(int port); 指定端口号
- 成员方法:
- public void send(DatagramPacket dp); 发送数据报包
- public void receive(DatagramPacket dp); 接收数据, 并封装到数据报包中.
- public void close(); 关闭Socket对象.
- 构造方法:
- DatagramPacket类中的方法:
- 构造方法:
- public DatagramPacket(byte[] bys, int length, InetAddress inet, int port);
- 创建数据包,发送长度为len的数据包到指定主机的指定端口(要发送的数据,字节数组长度,接收端的IP对象,端口号)
- public DatagramPacket(byte[] bys, int length);
- public DatagramPacket(byte[] bys, int length, InetAddress inet, int port);
- 成员方法:
- public byte[] getData(); 从数据报包中获取数据.
- public int getLength(); 返回从数据报包中获取到的具体有效字节数.
- 构造方法:
案例:演示UDP协议发送数据,接收端 收到数据,并打印
发送端
package com.ithiema.api.demo;
//发送端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Demo03 {
public static void main(String[] args) throws Exception {
//1.创建发送端的Socket对象
DatagramSocket senderSocket = new DatagramSocket();
//2.将要发送的数据转成对应的字节数组形式
byte[] bys = "你好,UDP,我来了!".getBytes();
//3.创建数据报包,关联:要发送的数据,字节数组长度,接收端的IP对象,端口号
DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("192.168.0.104"), 10010);
//4.发送数据报包
senderSocket.send(dp);
//5.关闭发送端的Socket对象
senderSocket.close();
}
}
接收端
package com.ithiema.api.demo;
//接收端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Demo04 {
public static void main(String[] args) throws Exception {
//1.创建接收端的Socket对象,指定:自身IP对象,端口号
DatagramSocket receiveSocket = new DatagramSocket(10086);
//2.创建字节数组,用来存储指定的自身数据的
byte[] bys = new byte[1024];
//3.创建数据报包,关联:字节数组对象,字节数组的长度
DatagramPacket dp = new DatagramPacket(bys, bys.length);
//4.接收数据报包
receiveSocket.receive(dp);
//5.从数据报包中解析收到的数据,并打印
//获取字节数组
byte[] bys2 = dp.getData();
//获取实际的字节长度
int len = dp.getLength();
//封装数据
String s = new String(bys2, 0, len);
System.out.println(s);
//6.关闭接收端的Socket对象
receiveSocket.close();
}
}
TCP:
//类似于: 打电话
- 面向有连接(三次握手)
- 采用IO流的形式发送数据, 理论上数据无大小限制.
- 安全(可靠)协议.
- 效率相对较低.
- 区分客户端和服务器端.
涉及到的类
Socket类:
- 构造方法:
- public Socket(InetAddress inet, int port);
- public Socket(String id, int port); 服务器ip, 端口号.
- 成员方法:
- public InputStream getInputStream(); 获取输入流, 可以读取服务器端写过来的内容.
- public OutputStream getOutputStream(); 获取输出流, 可以往服务器端写数据.
- public void close(); 关闭socket对象.
ServerSocket类: 服务器端的Socket类
- 构造方法:
- public ServerSocket(int port); 服务器端的Socket对象, 负责监听客户端连接 和 分配资源的.
- 成员方法:
- public Socket accept(); 监听客户端连接, 如果有客户端申请建立连接, 服务器端在审核数据
演示TCP协议之客户端给服务器端发送一句话
客户端
package com.ithiema.api.demo;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Demo05 {
public static void main(String[] args) throws Exception {
//1.创建客户端的Socket对象,指定:服务器端IP,端口号
Socket socket = new Socket("192.168.0.104", 10086);
//2.通过Socket#getOutputStream()方法,获取输出流,可以往服务器端写数据
OutputStream os = socket.getOutputStream();
//把获取的可以往服务器端写数据的字节输出流 -> 转换输出流 -> 字节高效输出流
//OutputStreamWriter osw = new OutputStreamWriter(os);
//BufferedWriter bw = new BufferedWriter(osw);
//省略版
//BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//3.具体的给服务器端写数据的操作
os.write("你好呀,约吗".getBytes());
//4.获取输入流对象,可以读取服务器端写过来的回执信息
InputStream is = socket.getInputStream();
//5.具体的读取服务器端写过来的回执信息的动作,并打印
byte[] bys = new byte[1024];
int len = is.read(bys);
String s = new String(bys,0,len);
System.out.println(s);
//6.释放资源
//os.close(); //如果Socket关闭了,和他关联的对象也会被释放
socket.close();
}
}
服务器端
package com.ithiema.api.demo;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo06 {
public static void main(String[] args) throws Exception {
//1.创建服务器端的Socket对象(ServerSocket),指定端口号
ServerSocket serverScoket = new ServerSocket(10086);
//2.监听连接,当服务器端监听到有客户端申请建立连接,并在审核数据成功后,会创建一个Socket对象,与该客户端交互
Socket accept = serverScoket.accept();
//3.通过Socket#getInputStream()方法,获取输入流,可以读取客户端写过来的数据
InputStream is = accept.getInputStream();
//4.具体的读取客户端写过来的数据的操作
byte[] bys = new byte[1024];
int len = is.read(bys); //读取到的有效字节数
//5.将读取到的数据转成字符串,并打印
String s = new String(bys, 0, len);
System.out.println("服务器端接收到数据:" + s);
//6.获取输出流,可以往客户端写数据
OutputStream os = accept.getOutputStream();
//7.给客户端c吃藕,不约!".getBytes());
//8.释放资源
//is.close(); //可以不写,因为Socket关了,和它关联的对象也会被释放
accept.close();
//serverSocket.close(); //实际开发中,服务器端一般是不关闭的
}
}
补充close()和flush()方法的区别
-
close():
- 是用来关闭流对象释放资源的,关闭之前,会自动刷新一次缓冲区,关闭之后,流对象就不能继续使用了
-
flush():
- 是用来刷新缓冲区,刷新之后,流对象可以继续使用
各个流的缓冲区的大小,具体如下:
字符高效流:16KB
字节高效流:8KB
Writer:2KB
演示TCP协议之文件上传案例
客户端
package com.ithiema.api.demo;
import java.io.*;
import java.net.Socket;
public class Demo07 {
public static void main(String[] args) throws Exception {
//1.创建客户端的Socket对象,指定:服务器端IP,端口号
Socket socket = new Socket("192.168.0.104", 10086);
//2.通过Socket#getOutputStream()方法,获取输出流,可以往服务器端写数据
//OutputStream os = socket.getOutputStream();
//3.通过转换流 和 字符高效率流,将上一步获取的字节输出流封装成:字符高效输出流
//BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
//合并版
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//4.创建字符高效输入流,关联:数据源文件(具体要上传的文件)
BufferedReader br = new BufferedReader(new FileReader("D:\\快捷方式\\0\\文件\\账号记录.txt"));
//5.具体的上传文件的动作(其实就是IO流的读写代码)
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); //换行
bw.flush(); //记得刷新缓冲区
}
//6.核心步骤:给服务器一个结束的标记,意思是:我上传完毕了,你该干嘛干嘛
socket.shutdownOutput();
//7.通过Socket#getInputStream(),可以读取服务器端写过来的回执信息
//8.将上一步获取到的流对象,封装成:字符高效输入流
BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//9.具体的读取服务器端写过来的回执信息的动作,并打印
String result = br2.readLine();
System.out.println(result);
//10.释放资源
br.close();
//bw.close(); //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
socket.close();
}
}
服务器端
package com.ithiema.api.demo;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo08 {
public static void main(String[] args) throws Exception {
//1.创建服务器端的Socket对象(ServerSocket),指定端口号
ServerSocket serverSocket = new ServerSocket(10086);
//2.监听连接,当服务器端监听到有客户端申请建立连接,并在审核数据成功后,会创建一个Socket对象,与该客户端交互
Socket accept = serverSocket.accept();
//3.通过Socket#getInputStream()方法,获取输入流,可以读取客户端写过来的数据
//4.通过转换流和字符高效流将上一步获取的字节输入流封装成 字符高效流
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
//5.创建字符高效流,关联目的地文件(即:接收到的数据往哪里写)
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\IdeaProjects\\Java\\JavaSE\\data\\1.txt"));
//6.具体的IO读写动作,接收上传的数据
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//7.通过Socket#getOutputStream()获取输出流,可以给客户端写回执信息
//8.将上述的流对象封装成 字符高效输出流
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
//9.具体给客户端写回执信息的操作
bw2.write("文件上传成功!");
bw2.newLine(); //换行
bw2.flush(); //刷新缓冲区
//10.释放资源
bw.close();
//br.close(); //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
accept.close();
//serverSocket.close(); //实际开发中,服务器端一般是不关闭的
}
}
多线程版的文件上传
客户端
package com.ithiema.tcp;
import java.io.*;
import java.net.Socket;
public class ClientDemo {
public static void main(String[] args) throws Exception{
//1.创建客户端的Socket对象,指定:服务器端IP,端口号
Socket socket = new Socket("192.168.0.104", 10086);
//2.通过Socket#getOutputStream()方法,获取输出流,可以往服务器端写数据
//OutputStream os = socket.getOutputStream();
//3.通过转换流 和 字符高效率流,将上一步获取的字节输出流封装成:字符高效输出流
//BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
//合并版
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//4.创建字符高效输入流,关联:数据源文件(具体要上传的文件)
BufferedReader br = new BufferedReader(new FileReader("D:\\快捷方式\\0\\文件\\账号记录.txt"));
//5.具体的上传文件的动作(其实就是IO流的读写代码)
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); //换行
bw.flush(); //记得刷新缓冲区
}
//6.核心步骤:给服务器一个结束的标记,意思是:我上传完毕了,你该干嘛干嘛
socket.shutdownOutput();
//7.通过Socket#getInputStream(),可以读取服务器端写过来的回执信息
//8.将上一步获取到的流对象,封装成:字符高效输入流
BufferedReader br2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//9.具体的读取服务器端写过来的回执信息的动作,并打印
String result = br2.readLine();
System.out.println(result);
//10.释放资源
br.close();
//bw.close(); //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
socket.close();
}
}
服务器端
package com.ithiema.tcp;
import java.net.ServerSocket;
import java.net.Socket;
/*
//改造1. 创建一个MyRunnable类(资源类),实现Runnable接口
//改造2. 定义成员变量Socket对象,表示具体的和客户端交互的Socket对象
//改造3. 定义带参构造,接收具体的Socket对象,它负责和客户端交互
//改造4. 重写run()方法,里面记录的是:服务器端具体的接收客户端上传的文件,并给出回执的 一套完整逻辑
//改造5. 细节:注意文件重名问题,因为可能要上传多个文件,所以文件名千万不要重复,否则就是覆盖了
//改造6. 因为run()方法中有异常是不能抛,只能try的,所以我们用try来修改代码
//改造7. 扩展:加入try.catch.finally的嵌套,来释放资源。
//改造8. 把之前的ServerDemo类中,完整的一套 接收客户端上传并给出回执的代码拷贝到run()方法
//改造9. 修改ServerDemo类中的代码,把一次监听改为无限监听
//改造10. 每监听到有新的客户端申请建立连接,在审核数据合法后,就创建一个线程对象,负责和它交互
//改造11. 注意:该线程对象封装的是:MyRunnable资源类对象。
*/
public class ServerDemo {
public static void main(String[] args) throws Exception{
//1.创建服务器端的Socket对象(ServerSocket),指定端口号
ServerSocket serverSocket = new ServerSocket(10086);
//2.监听连接,当服务器端监听到有客户端申请建立连接,并在审核数据成功后,会创建一个Socket对象,与该客户端交互
//改造9. 修改ServerDemo类中的代码,把一次监听改为无限监听
while (true) {
Socket accept = serverSocket.accept();
//改造10. 每监听到有新的客户端申请建立连接,在审核数据合法后,就创建一个线程对象,负责和它交互
//格式:new Thread(Runnable接口中的子类对象).start();
//改造11. 注意:该线程对象封装的是:MyRunnable资源类对象。
//new Thread(new MyRunnable(accept)).start();
new Thread(new MyRunnable(accept)).start();
}
}
}
MyRunnable类
package com.ithiema.tcp;
import java.io.*;
import java.net.Socket;
//改造1. 创建一个MyRunnable类(资源类),实现Runnable接口
public class MyRunnable implements Runnable {
//改造2. 定义成员变量Socket对象,表示具体的和客户端交互的Socket对象
private Socket accept;
//改造3. 定义带参构造,接收具体的Socket对象,它负责和客户端交互
public MyRunnable(Socket accept) {
this.accept = accept;
}
//改造4. 重写run()方法,里面记录的是:服务器端具体的接收客户端上传的文件,并给出回执的 一套完整逻辑
@Override
public void run() {
//改造5. 细节:注意文件重名问题,因为可能要上传多个文件,所以文件名千万不要重复,否则就是覆盖了
int count = 1;
File destFile = new File("javaSE/data/copy(" + ++count + ").txt");
while (destFile.exists()) {
destFile = new File("javaSE/data/copy(" + ++count + ").txt");
}
//自定义字符输出流,关联:目的地文件
BufferedWriter bw = null;
//改造6. 因为run()方法中有异常是不能抛,只能try的,所以我们用try来修改代码
try {
//改造8. 把之前的ServerDemo类中,完整的一套 接收客户端上传并给出回执的代码拷贝到run()方法
//3.通过Socket#getInputStream()方法,获取输入流,可以读取客户端写过来的数据
//4.通过转换流和字符高效流将上一步获取的字节输入流封装成 字符高效流
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
//5.创建字符高效流,关联目的地文件(即:接收到的数据往哪里写)
bw = new BufferedWriter(new FileWriter(destFile));
//6.具体的IO读写动作,接收上传的数据
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//7.通过Socket#getOutputStream()获取输出流,可以给客户端写回执信息
//8.将上述的流对象封装成 字符高效输出流
BufferedWriter bw2 = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
//9.具体给客户端写回执信息的操作
bw2.write("文件上传成功!");
bw2.newLine(); //换行
bw2.flush(); //刷新缓冲区
} catch (IOException e) {
e.printStackTrace();
} finally {
//改造7. 扩展:加入try.catch.finally的嵌套,来释放资源。
//10.释放资源
try {
if (bw != null) {
bw.close();
bw = null; //GC会优先回收null对象
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//br.close(); //这个可以不写,因为Socket关闭了,和它关联的对象也会自动关闭
try {
if (bw != null) {
accept.close();
accept = null;
}
} catch (IOException e) {
e.printStackTrace();
}
//serverSocket.close(); //实际开发中,服务器端一般是不关闭的
}
}
}
}