【Java】网络编程
文章目录
网络编程是什么?
计算机与计算机之间的程序进行数据传输
一、网络编程三要素
-
IP:
计算机之间互相通信,作为每台计算机的指定标识。设备的标识 -
端口:
应用程序之间的通信,为每个应用程序的指定标识。应用程序的标识 -
协议 :
计算机之间通信时需要遵守的规则,对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
比如UDP/TCP/HTTP/HTTPS 协议
二、IP
计算机设备在网络中的地址,唯一的标识。
IP地址分为两大类:
-
IPv4
给每个连接在网络上的主机分配一个32bit地址。
按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。 -
IPv6
采用128位地址长度,每16个字节一组,分成8组十六进制数
解决了网络地址资源数量不够的问题
DOS常用命令:
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
特殊IP地址:
- 127.0.0.1:是回送地址,永远代表本机地址,一般用来测试使用
三、端口号
设备上应用程序的唯一标识。
两个字节表示的整数,它的取值范围是0~65535。
其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
一个端口号一个应用程序
四、协议
网络通信协议
UDP协议
- 用户数据报协议(User Datagram Protocol)
面向无连接
通信协议。(也就是不会确定是否连接成功,直接甩数据过去)- 速度快,有大小限制一次最多发送64k,数据不安全,易丢失数据。
TCP协议
- 传输控制协议 (Transmission Control Protocol)
面向连接
的通信协议。(传送数据之间,会先判断是否连接成功)- 速度慢,没有大小限制,数据安全。
五、InetAddress 工具类
此类表示互联网协议 (IP) 地址。 内部方法可以获取本机IP地址与设备名称等
六、UDP协议
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象
DatagramSocket
类作为基于UDP协议的Socket
DatagramSocket API构造方法:
没有指定端口号,会随机指定一个空闲的端口。
DatagramPacket
:此类表示数据报包。
数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
1. 发送数据
//测试类
DatagramSocket ds = new DatagramSocket();//创建发送数据的端口 建立连接
//数据 地址 端口号
String str = "你好!";
byte[] bytes = str.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10086;
//传递
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//发送
ds.send(dp);
ds.close();
2. 接收数据
DatagramSocket ds = new DatagramSocket(10086);//接收数据的端口
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
InetAddress address = dp.getAddress();
int port = dp.getPort();
int len = dp.getLength();
System.out.println("数据为 " + new String(data, 0, len));
System.out.println("从这个地址 " + address + " 这个端口 " + port + "发送的数据");
ds.close();
先运行接收端(开启receive
接收等待), 然后再运行发送端!
发送端口是随机指定空闲的端口 ,我们 只需要确定 它发送给了10086 这个端口(并且我们从10086这个端口接收数据)即可。
使用UDP建立简单聊天室
需求:
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
//接收端
DatagramSocket ds = new DatagramSocket(15050);
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
while (true) {
ds.receive(dp);
byte[] data = dp.getData();
int len = dp.getLength();
int port = dp.getPort();
InetAddress address = dp.getAddress(); // 地址包括(主机名+IP)
String name = address.getHostName(); // 主机名
String ip = address.getHostAddress(); //IP
System.out.println("数据: " + new String(data, 0, len));
System.out.println("这个名字 "+name+" IP "+ip+" 从这个地址 " + address + " 来自这个端口 " + port + " 发送的数据");
}
//ds.close();
// 发送端
DatagramSocket ds = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("输入:");
String line = sc.nextLine();
if("886".equals(line)){
break;
}
byte[] bytes = line.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 15050;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
}
ds.close();
开启运行多个实例的模式,打开run多个发送端,就变成聊天室了
UDP的三种通信方式
-
单播
以上的代码 单个IP 就是 单播 -
组播
也就是用同一IP的多个接收端,一个发送端发送数据
-
广播
一个主机对整个局域网上所有主机上的数据通信
七、TCP协议
可靠的网络协议,在通信的两端建立Socket,在发送数据之前,会先确定连接成功。
1. 发送数据
客户端:
/*
* 创建socket对象
* 在创建对象的同时会连接服务器,如果连接不上,报错
* */
Socket socket = new Socket("127.0.0.1", 15050);
//从连接通道获取输出流
OutputStream oss = socket.getOutputStream();
//写数据
oss.write("你好!套接字Socket!hello!123".getBytes());
oss.close();
socket.close();
2. 接收数据
服务端:
ServerSocket ss = new ServerSocket(15050);
//监听客户端连接
Socket socket = ss.accept();
//从连接通道获取数据 输入流
//InputStream iss = socket.getInputStream();//字节流 输入中文会乱码 要转换为 字符流
//InputStreamReader isr = new InputStreamReader(iss);//使用转换流 将字节转换为字符流
//BufferedReader br = new BufferedReader(isr);//缓冲流 提高效率
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//以上代码直接嵌套
int b;
while ((b = br.read()) != -1) {
System.out.print((char) b);
}
socket.close();
ss.close();
三次握手,四次挥手
握手 确定关系
挥手 即分手 say goodbye
综合练习
接收到数据 并 返回一个反馈
socket.shutdownOutput();
关闭输出流,结束的标志
ServerSocket ss = new ServerSocket(15050);
Socket socket = ss.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int len;
// 需要结束标记 要确定已经结束传输数据
//否则while后面的代码不会执行 ,一直在循环中
while ((len = br.read()) != -1) {
System.out.print((char) len);
}
OutputStream os = socket.getOutputStream();
os.write("已收到!".getBytes());
socket.close();
ss.close();
客户端发送数据,并且接收返回的数据
Socket socket = new Socket("127.0.0.1", 15050);
OutputStream os = socket.getOutputStream();
os.write("你好啊".getBytes());
socket.shutdownOutput();//关闭输出流,防止服务端一致卡在读取数据的while循环
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
int len;
while ((len = br.read())!=-1){
System.out.print((char) len);
}
socket.close();
上传文件
将文件上传到客户端,提取里面的数据传送给服务端,服务端生成文件输出
客户端:
/**
* @Description: 文件上传客户端 传送给服务端
*/
public class FileUpdateTCP {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10000);
//上传文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("A-SocketNet\\123.jpg"));
//打开输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//文件读取 与 传输
byte[] bytes = new byte[1024*1024];
int len;
while ((len = bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
//结束标识,文件已经传送完毕
socket.shutdownOutput();
//接收服务器给予的反馈 打印
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = br.readLine();
System.out.println(s);
socket.close();
}
}
服务端:
/**
* @Description: 接收客户端文件 下载 给予反馈
*/
public class FileDownloadTCP {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket socket = ss.accept();
//打开输入流 接受客户端传送文件
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
//创建文件下载位置
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A-SocketNet\\server\\a.jpg"));
//文件的接收 与 下载
byte[] bytes = new byte[1024*1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 接收到数据后 发送 反馈给客户端
BufferedWriter bosToServer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bosToServer.write("服务端反馈:已收到,上传成功!");
bosToServer.newLine();
bosToServer.flush();
socket.close();
ss.close();
}
}
文件重名 UUID
针对上一题,下载的文件名字 重名 会导致覆盖文件。
使用 uuid
生成唯一的随机字符串。(表示通用唯一标识符 (UUID) 的类)
只要修改服务端的代码即可:
//使用UUID随机字符串生成文件名字
String str = UUID.randomUUID().toString().replace("-", "");
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A-SocketNet\\server\\"+str+".jpg"));
上传文件(多线程版)
服务器不关闭,持续接收文件
单线程:一个用户文件没有上传完,下一个用户不能上传 需要等上一个用户结束。
多线程:用户之间互不打扰,一个用户在上传,另外一个也能上传。
思路:使用循环 + 多线程
服务端代码修改:
public class FileDownloadTCP {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
while (true){
Socket socket = ss.accept();
new Thread(new MyRunnable(socket)).start();
}
//ss.close();
}
}
多线程版:
public class MyRunnable implements Runnable{
Socket socket;
//使用构造接收socket
public MyRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
synchronized (MyRunnable.class){
try {
//使用UUID随机字符串生成文件名字
String str = UUID.randomUUID().toString().replace("-", "");
//打开输入流 接受客户端传送文件
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
//创建文件下载位置
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("A-SocketNet\\server\\"+str+".jpg"));
//文件的接收 与 下载
byte[] bytes = new byte[1024*1024];
int len;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
// 接收到数据后 发送 反馈给客户端
BufferedWriter bosToServer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bosToServer.write("服务端反馈:已收到,上传成功!");
bosToServer.newLine();
bosToServer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}finally {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
上传文件(线程池版)
服务端添加线程池代码:
public class FileDownloadTCP {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
//创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // 核心线程数量
16,//线程池总大小
60,//空闲时间
TimeUnit.SECONDS,//空闲时间单位
new ArrayBlockingQueue<>(2),//队列
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.AbortPolicy()//阻塞队列
);
while (true){
Socket socket = ss.accept();
//new Thread(new MyRunnable(socket)).start();
pool.submit(new MyRunnable(socket));//开启线程池
}
//ss.close();
}
}
总结
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!