网络编程
网络编程的要素
- IP地址 :想要让网络中的计算机能够通信,每一个计算机就要有一个标识,识别其他的计算机。IP地址就可以理解成这个标识。
- IP地址分为两大类,一种是IPV4,一种是IPV6,IPV4规则下,每个ip地址32个比特位。四个字节。ip地址通常被写成十进制的形式。 由于IP地址的资源有限,所以为了扩大地址空间,通过IPV6重新定义地址空间,采用128位地址长度,十六个字节一组,分为八组十六进制树。这样能够解决网络地址资源不够的问题。
- 端口号 : 网络通信本质是两个应用之间的通信。应用跑在端口号上,通过端口号找到相应的应用。
- 协议:连接和通信的规则,被称之为协议。
OSI七层模型
应用层:为应用程序提供服务
表示层:数据格式转化,数据加密
会话层:建立管理和维护会话
传输层:建立、管理和维护端对端的连接
网络层:IP选址以及路由选择。
数据链路层:提供介质访问和链路管理。
物理层:物理传输
TCP/IP 四层
应用层
:应用层决定了向用户提供应用服务时通信的活动。主要协议有:HTTP协议、FTP协议、SNMP(简单网络管理协议)、SMTP(简单邮件传输协议)和POP3(Post Office Protocol 3的简称,即邮局协议的第3个版)等。传输层
:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。TCP(Transmission Control Protocol)协议,即传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务。网络层
:网络层是整个TCP/IP协议的核心,支持网间互连的数据通信。它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。而IP协议是一种非常重要的协议。IP(internet protocal)又称为互联网协议。IP的责任就是把数据从源传送到目的地。它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。
物理+数据链路层
:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。
TCP
TCP进行通信的两个进程是客户端和服务端。使用TCP之前必须先建立连接。
传输之前必须要三次握手。
三次握手:
第一次握手:客户端向服务端发起连接的请求
第二次握手:服务器发送针对客户端TCP连接请求的确认
第三次握手:客户端发送,确认的 确认。
三次握手之后,客户端和服务端就可以进行数据传输了,这种传输是可靠的。TCP协议能够保证数据传输的安全。能够用于传输重要的数据。
四次挥手:发送数据结束之后:释放连接的时候需要四次挥手
第一次:客户端向服务端提出结束连接的请求,客户端处于半关闭状态,表示不在向服务器发送数据,但是还能接收数据。
第二次:服务器接收到客户端的请求后,会将最后的数据发送给客户端,告知上层应用不再接受数据。
第三次:服务端发送完数据后,发送一个释放连接的报文。客户端知道可以正式的释放连接了。
第四次:客户端接收到释放报文之后,回复一个彻底断开的报文。服务器收到会彻底释放列连接。客户端发送之后会等待2MSL,在这个时间内如果服务器没收到,就会再次发送释放连接的报文。等待时间过后如果没收到,就彻底断开连接。
UDP
UDP是一种无连接的通信协议,在进行数据传输的时候发送端和接收端不建立逻辑连接。也就是说在发送数据的时候发送方不会确认接收端是否存在,只管发。接收端接收数据的时候不需要向发送端确认。
UDP的消耗的系统资源比较小,通信效率高。一般用于视频音频等普通数据的传输。这种数据丢失一两个包问题不大。
UDP Java 编程
流程:java 提供了DatagramSocket类,作为基于udp协议的socket。DatagramPacket对象封装了UDP数据报,数据报中包含了发送端的IP地址和端口号。
发送端的基本操作流程:
创建DatagramSocket对象
创建DatagramPacket:将要发送的数据用字节数组表示,指定发送的数据长度接收方的IP地址和端口号
调用send方法
关闭datagramSocket对象
接收端的基本流程:
创建DatagramSocket对象
创建DatagramPacket:指定接收数据的字符数组,接收数据。
调用datagramSocket的receive方法,接收数据。
关闭datagramSocket对象。
演示:
// 发送方
public class Server {
public static void main(String[] args) {
try (DatagramSocket ds = new DatagramSocket();) {
Scanner scanner = new Scanner(System.in);
while (true) {
String input = scanner.nextLine();
if (input.equals("over")) {
break;
}
byte[] pack = input.getBytes(StandardCharsets.UTF_8);
DatagramPacket dp = new DatagramPacket(pack, pack.length,
InetAddress.getLocalHost(), 12345);
ds.send(dp);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* 接收端
*/
public class Client {
public static void main(String[] args) {
try (DatagramSocket ds = new DatagramSocket(12345);) {
while (true) {
byte[] buffer = new byte[1024];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp);
String receiveData = new String(dp.getData(), 0, dp.getLength());
System.out.println(receiveData);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
UDP有三种通讯方式:
- 单播:两个主机之间的端对端通信
- 组播:一组特定的主机进行通信
- 广播:一个主机对整个局域网的所有主机进行通信
组播实现:
发送端:
创建socket对象
创建数据,打包
发送数据,发送到组播的地址
释放资源
接收端
创建MulticastSocket对象
创建一个箱子,接收数据
把本机绑定到一个组播地址里面
数据接收到箱子里
解析数据
释放资源
演示:
// 发送端
public class ClinetDemo {
public static void main(String[] args) throws IOException {
// 1. 创建发送端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket();
String s = "hello 组播";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10000;
// 2. 创建数据,并把数据打包(DatagramPacket)
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
// 3. 调用DatagramSocket对象的方法发送数据(在单播中,这里是发给指定IP的电脑但是在组播当中,这里是发给组播地址)
ds.send(dp);
// 4. 释放资源
ds.close();
}
}
// 接收端
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 1. 创建接收端Socket对象(MulticastSocket)
MulticastSocket ms = new MulticastSocket(10000);
// 2. 创建一个箱子,用于接收数据
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
// 3. 把当前计算机绑定一个组播地址,表示添加到这一组中.
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
// 4. 将数据接收到箱子中
ms.receive(dp);
// 5. 解析数据包,并打印数据
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data,0,length));
// 6. 释放资源
ms.close();
}
}
TCP网络编程:
服务端:
调用serverSocket,创建一个服务器socket,绑定到指定端口上,用于监听客户端的请求。
调用accept,监听连接请求,如果客户端请求连接,则接受连接,返回通信socket对象。
调用getOutputStream,getInputStream:获取输入输出流,开始网络数据的发送和接收。
关闭资源.
- 注意事项
- accept方法是阻塞的,作用就是等待客户端连接
- 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
- 针对客户端来讲,是往外写的,所以是输出流
针对服务器来讲,是往里读的,所以是输入流 - read方法也是阻塞的
- 客户端在关流的时候,还多了一个往服务器写结束标记的动作
- 最后一步断开连接,通过四次挥手协议保证连接终止
/**
* 客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10086);
OutputStream outputStream = socket.getOutputStream();
outputStream.write("samuel".getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
socket.close();
}
}
/**
* 服务端
*/
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(10086);
Socket accept = socket.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
char[] buffer = new char[1024];
int len = 0;
while((len = br.read(buffer)) != -1) {
System.out.println(new String(buffer , 0, len));
}
socket.close();
accept.close();
br.close();
}
}
练习一:多发多收
需求:
客户端:多次发送数据
服务器:接收多次接收数据,并打印
代码示例:
public class Client {
public static void main(String[] args) throws IOException {
//客户端:多次发送数据
//服务器:接收多次接收数据,并打印
//1. 创建Socket对象并连接服务端
Socket socket = new Socket("127.0.0.1",10000);
//2.写出数据
Scanner sc = new Scanner(System.in);
OutputStream os = socket.getOutputStream();
while (true) {
System.out.println("请输入您要发送的信息");
String str = sc.nextLine();
if("886".equals(str)){
break;
}
os.write(str.getBytes());
}
//3.释放资源
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
//客户端:多次发送数据
//服务器:接收多次接收数据,并打印
//1.创建对象绑定10000端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据
InputStreamReader isr = new InputStreamReader(socket.getInputStream());
int b;
while ((b = isr.read()) != -1){
System.out.print((char)b);
}
//4.释放资源
socket.close();
ss.close();
}
}
练习二:接收并反馈
-
案例需求
客户端:发送数据,接受服务器反馈
服务器:收到消息后给出反馈
-
案例分析
- 客户端创建对象,使用输出流输出数据
- 服务端创建对象,使用输入流接受数据
- 服务端使用输出流给出反馈数据
- 客户端使用输入流接受反馈数据
-
代码实现
// 客户端 public class ClientDemo { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1",10000); OutputStream os = socket.getOutputStream(); os.write("hello".getBytes()); // os.close();如果在这里关流,会导致整个socket都无法使用 socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line; while((line = br.readLine())!=null){ System.out.println(line); } br.close(); os.close(); socket.close(); } } // 服务器 public class ServerDemo { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(10000); Socket accept = ss.accept(); InputStream is = accept.getInputStream(); int b; while((b = is.read())!=-1){ System.out.println((char) b); } System.out.println("看看我执行了吗?"); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream())); bw.write("你谁啊?"); bw.newLine(); bw.flush(); bw.close(); is.close(); accept.close(); ss.close(); } }
练习三:上传练习(TCP协议)
-
案例需求
客户端:数据来自于本地文件,接收服务器反馈
服务器:接收到的数据写入本地文件,给出反馈
-
案例分析
- 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
- 创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后。使用输出流给客户端反馈信息
- 客户端接受服务端的回馈信息
-
相关方法
方法名 说明 void shutdownInput() 将此套接字的输入流放置在“流的末尾” void shutdownOutput() 禁止用此套接字的输出流 -
代码实现
public class Client { public static void main(String[] args) throws IOException { //客户端:将本地文件上传到服务器。接收服务器的反馈。 //服务器:接收客户端上传的文件,上传完毕之后给出反馈。 //1. 创建Socket对象,并连接服务器 Socket socket = new Socket("127.0.0.1",10000); //2.读取本地文件中的数据,并写到服务器当中 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg")); BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); byte[] bytes = new byte[1024]; int len; while ((len = bis.read(bytes)) != -1){ bos.write(bytes,0,len); } //往服务器写出结束标记 socket.shutdownOutput(); //3.接收服务器的回写数据 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = br.readLine(); System.out.println(line); //4.释放资源 socket.close(); } }
public class Server { public static void main(String[] args) throws IOException { //客户端:将本地文件上传到服务器。接收服务器的反馈。 //服务器:接收客户端上传的文件,上传完毕之后给出反馈。 //1.创建对象并绑定端口 ServerSocket ss = new ServerSocket(10000); //2.等待客户端来连接 Socket socket = ss.accept(); //3.读取数据并保存到本地文件中 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\a.jpg")); int len; byte[] bytes = new byte[1024]; while ((len = bis.read(bytes)) != -1){ bos.write(bytes,0,len); } bos.close(); //4.回写数据 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); bw.write("上传成功"); bw.newLine(); bw.flush(); //5.释放资源 socket.close(); ss.close(); } }
练习四:文件名重复
```java
public class UUIDTest {
public static void main(String[] args) {
String str = UUID.randomUUID().toString().replace(“-”, “”);
System.out.println(str);//9f15b8c356c54f55bfcb0ee3023fce8a
}
}
```
public class Client {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1. 创建Socket对象,并连接服务器
Socket socket = new Socket("127.0.0.1",10000);
//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
//往服务器写出结束标记
socket.shutdownOutput();
//3.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);
//4.释放资源
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
//2.等待客户端来连接
Socket socket = ss.accept();
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
//5.释放资源
socket.close();
ss.close();
}
}
练习五:服务器改写为多线程
服务器只能处理一个客户端请求,接收完一个图片之后,服务器就关闭了。
优化方案一:
使用循环
弊端:
第一个用户正在上传数据,第二个用户就来访问了,此时第二个用户是无法成功上传的。
所以,使用多线程改进
优化方案二:
每来一个用户,就开启多线程处理
public class Client {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1. 创建Socket对象,并连接服务器
Socket socket = new Socket("127.0.0.1",10000);
//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
//往服务器写出结束标记
socket.shutdownOutput();
//3.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);
//4.释放资源
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
while (true) {
//2.等待客户端来连接
Socket socket = ss.accept();
//开启一条线程
//一个用户就对应服务端的一条线程
new Thread(new MyRunnable(socket)).start();
}
}
}
public class MyRunnable implements Runnable{
Socket socket;
public MyRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.释放资源
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
练习六:线程池改进
public class Client {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//1. 创建Socket对象,并连接服务器
Socket socket = new Socket("127.0.0.1",10000);
//2.读取本地文件中的数据,并写到服务器当中
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("mysocketnet\\clientdir\\a.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
byte[] bytes = new byte[1024];
int len;
while ((len = bis.read(bytes)) != -1){
bos.write(bytes,0,len);
}
//往服务器写出结束标记
socket.shutdownOutput();
//3.接收服务器的回写数据
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = br.readLine();
System.out.println(line);
//4.释放资源
socket.close();
}
}
public class Server {
public static void main(String[] args) throws IOException {
//客户端:将本地文件上传到服务器。接收服务器的反馈。
//服务器:接收客户端上传的文件,上传完毕之后给出反馈。
//创建线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量
16,//线程池总大小
60,//空闲时间
TimeUnit.SECONDS,//空闲时间(单位)
new ArrayBlockingQueue<>(2),//队列
Executors.defaultThreadFactory(),//线程工厂,让线程池如何创建线程对象
new ThreadPoolExecutor.AbortPolicy()//阻塞队列
);
//1.创建对象并绑定端口
ServerSocket ss = new ServerSocket(10000);
while (true) {
//2.等待客户端来连接
Socket socket = ss.accept();
//开启一条线程
//一个用户就对应服务端的一条线程
//new Thread(new MyRunnable(socket)).start();
pool.submit(new MyRunnable(socket));
}
}
}
public class MyRunnable implements Runnable{
Socket socket;
public MyRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
//3.读取数据并保存到本地文件中
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
String name = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mysocketnet\\serverdir\\" + name + ".jpg"));
int len;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
//4.回写数据
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
//5.释放资源
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}