网络编程
概述
- 在网络通信协议下,不同计算机上运行的程序,可以进行数据传输。
三要素
IP地址
- 设备在网络中的地址,是唯一的标识
- 全称"互联网协议地址",也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6
端口
- 应用程序在设备在唯一的标识
端口号
用俩个字节表示的整数,它的取值范围是0~65535,其他0~1023之间的端口号用于一些知名的网络服务或者应用,一遍使用1024以上的端口就行。 一个端口号只能被一个应用程序使用。
协议
- 数据在网络中传输的规则,常见的协议又UDP协议和TCP协议
UDP协议
- 用户数据报协议(User Datagram Protocal)
- UDP是面向无连接通信协议,速度快,有大小限制一次最多发送64K,数据不安全,易丢失
TCP协议
- 传输控制协议(Transmission Control Protocal)
- TCP协议是面向连接的通信协议。速度慢,没有大小限制,数据安全。
InetAddress类
方法
public static InetAddress getByName(String host)
:确定主机名称的IP地址。
public String getHostName()
:获取此IP地址的主机名。
public String getHostAddress()
:返回文本显示中的IP地址字符串
public class InetAddressDemo1 {
public static void main(String[] args) throws UnknownHostException {
InetAddress address = InetAddress.getByName("LAPTOP-FHMDM194");
// 主机名
String hostName = address.getHostName();
System.out.println(hostName);
// ip
String ip = address.getHostAddress();
System.out.println(ip);
}
}
UDP通信程序
UDP的三种通信方式
单播
发送步骤
- 创建发送端的DatagramSocket对象
DatagramSocket()
构造数据报套接字并将其绑定到本地主机上的任何可用端口。
- 创建数据,并把数据打包(DatagramPacket)
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
参数介绍:
buf:要发送的数据
length:你要把数组中多少个数据发送过去
address:接收端
port:接收端的端口号
- 调用DatagramSocket对象的方法发送数据
- 释放数据
public class ClientDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
byte[] bytes = "发送的信息".getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
ds.send(dp);
ds.close();
}
}
接收步骤
- 创建接收端的DatagramSocket对象
DatagramSocket(int port)
构造数据报套接字并将其绑定到本地主机上的指定端口。
- 创建个变量,接收数据
DatagramPacket(byte[] buf, int length)
构造一个 DatagramPacket用于接收长度的数据包 length 。
- 调用DatagramSocket的方法接收数据并把数据放入箱子中
- 解析数据包
- 释放资源
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 指定一个端口号,接收发送端发送的数据
DatagramSocket ds = new DatagramSocket(10000);
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
ds.close();
}
}
注意事项
- 在运行的过程中,需要先运行接收端,然后再去运行发送端
- 接收端启动后,如果发送端没有发送数据,就会处于等待状态
- 在接收数据的时候,可以调用getLength的方法,可以获取接收数据的长度
组播
组播地址:224.0.0.0 ~ 239.255.255.255,其中224.0.0.0 ~ 224.0.0.255为预留的组播地址
发送步骤
- 创建发送端的DatagramSocket对象
DatagramSocket()
构造数据报套接字并将其绑定到本地主机上的任何可用端口。
- 创建数据,并把数据打包(DatagramPacket)
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
参数介绍:
buf:要发送的数据
length:你要把数组中多少个数据发送过去
address:接收端
port:接收端的端口号
- 调用DatagramSocket对象的方法发送数据,发送给组播地址
-
释放数据
public class ClientDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
byte[] bytes = "组播发送".getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int post = 8888;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,post);
ds.send(dp);
ds.close();
}
}
接收步骤
- 创建接收端的MulticastSocket对象
MulticastSocket(int port)
创建组播套接字并将其绑定到特定端口。
- 创建个变量,接收数据
DatagramPacket(byte[] buf, int length)
构造一个 DatagramPacket用于接收长度的数据包 length 。
- 调用DatagramSocket的方法接收数据并把数据放入箱子中
- 解析数据包
- 释放资源
public class ServerDemo {
public static void main(String[] args) throws IOException {
MulticastSocket ms = new MulticastSocket(8888);
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
// 把当前计算机绑定一个组播地址,表示添加到这一组中
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
ms.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
ms.close();
}
}
广播
广播地址:255.255.255.255
发送步骤
- 创建发送端的DatagramSocket对象
DatagramSocket()
构造数据报套接字并将其绑定到本地主机上的任何可用端口。
- 创建数据,并把数据打包(DatagramPacket)
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
参数介绍:
buf:要发送的数据
length:你要把数组中多少个数据发送过去
address:接收端
port:接收端的端口号
- 调用DatagramSocket对象的方法发送数据,发送给广播地址
-
释放数据
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 指定一个端口号,接收发送端发送的数据
DatagramSocket ds = new DatagramSocket(8888);
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
ds.close();
}
}
#### 接收步骤
1. 创建接收端的DatagramSocket对象
`DatagramSocket(int port)`构造数据报套接字并将其绑定到本地主机上的指定端口。
2. 创建个变量,接收数据
`DatagramPacket(byte[] buf, int length)`构造一个 DatagramPacket用于接收长度的数据包 length 。
3. 调用DatagramSocket的方法接收数据并把数据放入箱子中
4. 解析数据包
5. 释放资源
```Java
public class ClientDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
byte[] bytes = "广播发送".getBytes();
InetAddress address = InetAddress.getByName("255.255.255.255");
int post = 8888;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,post);
ds.send(dp);
ds.close();
}
}
TCP通信程序
TCP通信协议是一个可靠的网络协议,它在通信的俩端各简历一个Socket对象
客户端用的类为Socket
,服务端用的类为ServerSocket
发送数据步骤
- 创建客户端的Socket对象与指定服务器连接
Socket(String host, int port)
:创建流套接字并将其连接到指定主机上的指定端口号。
- 获取输出流,写数据
public OutputStream getOutputStream()
:返回此套接字的输出流。
- 释放资源
void close()
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello".getBytes());
// 释放资源
outputStream.close();
socket.close();
}
}
接收数据步骤
- 创建服务器端的Socket对象(ServerSocket)
ServerSocket(int port)
创建绑定到指定端口的服务器套接字。
- 监听客户端连接,返回一个Socket对象
Socket accept()
注:执行到这一步的时候,如果没有客户端来连接就会发送阻塞。
-
获取输入流,读数据,并把数据显示在控制台
InputStream getInputStream()
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
Socket accept = ss.accept();
InputStream inputStream = accept.getInputStream();
int b;
while ((b= inputStream.read()) != -1){
System.out.println((char)b);
}
inputStream.close();
ss.close();
}
}
#### 注意事项
1. accept方法是阻塞的,它的作用就等待客户端连接。
2. 客户端创建对象并连接服务器,此时是通过三次握手协议保证跟服务器之间的连接。
3. 对于客户端而言,是往外写的,所以是输出流,对于服务器而言,是往里读的,所以是输入流
4. read方法也是阻塞的
5. 在关闭客户端输出流的时候,会自动往服务器写一个结束标记的动作。
6. 通过四次挥手保证连接终止
#### 三次握手
1. 客户端向服务器发送链接请求(等待服务器确认)
2. 服务器向客户端返回一个响应(告诉客户端收到了请求)
3. 客户端向服务器再次发送确认信息(建立连接)
#### 四次挥手
1. 客户端向服务器发出取消连接请求
2. 服务器向客户端返回一个响应(表示收到客户端取消请求)
3. 第二次发送完得时候会去处理数据,处理完了再次向客户端发送确认取消信息
4. 客户端再次发送确认消息(连接取消)
#### TCP小练习
```Java
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello".getBytes());
socket.shutdownOutput();// 仅仅关闭输出流,并写一个结束标记,对socket没有任何影响
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 字节转字符
String line;
while ((line = br.readLine())!=null){
System.out.println(line);
}
br.close();
outputStream.close();
socket.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
Socket accept = ss.accept();
InputStream inputStream = accept.getInputStream();
int b;
while ((b= inputStream.read())!=-1){
System.out.println((char)b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream())); // 字节转字符
bw.write("你谁啊");
bw.newLine();
bw.flush();
bw.close();
inputStream.close();
accept.close();
ss.close();
}
}
文件上传
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
// 本地流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\FileTest\\1.mp4"));
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
int b;
while ((b=bis.read())!= -1){
bos.write(b); // 通过网络写到服务器
}
// 给服务器结束标记
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
bis.close();
socket.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
Socket accept = ss.accept();
// 网络中的流
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\FileTest\\copy\\1.mp4"));
int b;
while ((b = bis.read()) != -1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
accept.close();
ss.close();
bos.close();
}
}
服务端优化
- 使用死循环让服务器保持一直运行
- 使用UUID来确保文件的唯一性
- 使用多线程可以处理多个客户端请求
- 在使用线程池优化多线程的资源消耗过大的问题
UUID
一个表示不可变的通用唯一标识符(UUID)的类。 UUID表示128位值。
方法
public static UUID randomUUID()
静态工厂检索一个类型4(伪随机生成)的UUID。 UUID是使用加密强伪随机数生成器生成的。
String toString()
返回代表这个 UUID的 String对象
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,// 核心线程数量
10,// 线程池的总数量
60,// 临时线程的空闲时间
TimeUnit.SECONDS,// 临时线程的空闲时间单位
new ArrayBlockingQueue<>(5),//阻塞队列
Executors.defaultThreadFactory(),//创建线程的方式
new ThreadPoolExecutor.AbortPolicy()// 任务拒绝策略
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);
// new Thread(ts).start();
pool.submit(ts);
}
// ss.close();
}
}
public class ThreadSocket implements Runnable {
private Socket acceptSocket;
public ThreadSocket(Socket accept) {
this.acceptSocket = accept;
}
@Override
public void run() {
BufferedOutputStream bos = null;
try {
// 网络中的流
BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
bos = new BufferedOutputStream(new FileOutputStream("D:\\FileTest\\copy\\"+ UUID.randomUUID().toString().replace("-","") +".mp4"));
int b;
while ((b = bis.read()) != -1){
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (acceptSocket != null){
try {
acceptSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
// 本地流,读取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\FileTest\\1.mp4"));
OutputStream outputStream = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outputStream);
int b;
while ((b=bis.read())!= -1){
bos.write(b); // 通过网络写到服务器
}
// 给服务器结束标记
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
bis.close();
socket.close();
}
}