计算机与计算机之间的程序进行数据传输
IP:
计算机之间互相通信,作为每台计算机的指定标识。设备的标识
端口:
应用程序之间的通信,为每个应用程序的指定标识。应用程序的标识
协议 :
计算机之间通信时需要遵守的规则,对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
比如UDP/TCP/HTTP/HTTPS 协议
计算机设备在网络中的地址,唯一的标识。
IP地址分为两大类:
IPv4
给每个连接在网络上的主机分配一个32bit地址。
按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。
IPv6
采用128位地址长度,每16个字节一组,分成8组十六进制数
解决了网络地址资源数量不够的问题
DOS常用命令:
特殊IP地址:
设备上应用程序的唯一标识。
两个字节表示的整数,它的取值范围是0~65535。
其中,0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。
如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
一个端口号一个应用程序
面向无连接
通信协议。(也就是不会确定是否连接成功,直接甩数据过去)面向连接
的通信协议。(传送数据之间,会先判断是否连接成功)此类表示互联网协议 (IP) 地址。 内部方法可以获取本机IP地址与设备名称等
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象
DatagramSocket
类作为基于UDP协议的Socket
DatagramSocket API构造方法:
没有指定端口号,会随机指定一个空闲的端口。
DatagramPacket
:此类表示数据报包。
数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
//测试类
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();
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发送数据:数据来自于键盘录入,直到输入的数据是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多个发送端,就变成聊天室了
可靠的网络协议,在通信的两端建立Socket,在发送数据之前,会先确定连接成功。
客户端:
/*
* 创建socket对象
* 在创建对象的同时会连接服务器,如果连接不上,报错
* */
Socket socket = new Socket("127.0.0.1", 15050);
//从连接通道获取输出流
OutputStream oss = socket.getOutputStream();
//写数据
oss.write("你好!套接字Socket!hello!123".getBytes());
oss.close();
socket.close();
服务端:
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();
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随机字符串生成文件名字
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();
}
}