目录
1.UDP发送端---使用的为DatagramSocket对象
2.UDP接收端---也是创建DatagramSocket对象
十二、TCP练习题二的服务器弊端二(服务器无法同时处理多个客户端发送)
网络编程三要素:
ip:设备在网络中的地址,是唯一标识。(设备:电脑、手机等等)
端口:应用程序在设备中的唯一标识
协议:数据在网络中传输的规则,常见的协议有UDP和TCP协议
一、ip
全程“互联网协议地址”,也称ip地址。是分配给上网设备的数字标签。常见的ip分类为:ipv4和ipv6,我们想要链接哪台电脑,我们就找到该电脑ip就可以
1.ipv4
先明白换算关系 1Byte(字节) = 8bit(位) ipv4是由32bit(4Byte)组成
举例:11000000 10101000 00000001 01000010 ----根据点分十进制表示法-->192.168.1.66
点分十进制:就是把每一个字节先转换位十进制,最后每个字节中间用 . 连接
注意:ipv4逐渐满足不了世界级用户使用,因为每个字节表示范围[0--255],也就是一个字节能表示256位,那32位的ipv4最多只能表示256*256*256*256 = 42亿+。数量逐渐满足不了用户数。故此使用ipv6
2.ipv6
采用128bit位地址长度,每16个bit位也就是2个Byte字节,分为一组,总共8组。比如:
二、inteAddress类的使用
为了方便我们对ip地址的获取和操作,所提供的一个类
inteAddress:此类表示internet协议(ip)地址
常用方法及其使用:
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Demo1 {
public static void main(String[] args) throws UnknownHostException {
/*
InteAddress类的常用方法
1.static InteAddress getByName(String host);
确定主机名称ip的地址,主机名称可以是机器名称,也可以是ip地址
2.String getHostName();
获取ip地址的主机名
3.String getHostAddress();
返回文本显示的ip地址字符串
*/
InetAddress byName = InetAddress.getByName("xxx.xxx.x.xx");
String hostName = byName.getHostName();
System.out.println("主机名为:"+hostName);
String hostAddress = byName.getHostAddress();
System.out.println("ip为:"+hostAddress);
}
}
三、端口
端口是设备与外界通讯数据的出口,是应用程序在设备中的唯一标识,一个端口只能被一个应用端口绑定。
端口号:用两个字节表示的整数,取值范围[0---65535],其中0--1023的一些端口用于一些知名网络服务或者应用。我们自己写代码,只能使用1024以上的,且小于等于65535
四、协议
计算机网络中, 连接和通信的规则被称为网络通信协议,分为UDP和TCP
1.UDP协议
①用户数据报协议(User Datagram Protocol)
②UDP是面向无连接通信协议(无连接:不管发送端和接收端是否连接成功)
特点:速度快、有大小限制一次最多64k,数据不安全,容易丢失。像一般音频或者视频会使用此协议。
2.TCP协议
①传输控制协议(Transmission Control Protocol)
②TCP协议是面向连接的通信协议(面向连接:发送端和接收端必须产生连接)
特点:速度慢、没有大小限制、数据安全
五、UDP发送端/UDP接收端
1.UDP发送端---使用的为DatagramSocket对象
由于UDP采用无连接通信,所以发送端需要做的步骤有:①.找码头,②.打包,③.由码头发送包裹,④.完成发送
import java.io.IOException;
import java.net.*;
public class UdpDemo1 {
/*
UDP发送端代码实现
*/
public static void main(String[] args) throws IOException {
//1.找码头,创建UDP码头对象new DatagramSocket()
DatagramSocket ds = new DatagramSocket();
//2.打包信息,创建打包对象
//new DatagramPacket(四个参数)
//参数一:发送的内容数组
String s = "祝你新年快乐,2022年1月5日,23点24分";
byte[] bytes = s.getBytes();
//参数二:内容数组里需要发送的内容长度
//参数三:本机的ip/地址
InetAddress address = InetAddress.getByName("127.0.0.1");
//参数四:端口号
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//3.码头发送,让打包的信息dp和码头ds发生联系
ds.send(dp);
//4.完成发送,释放资源
ds.close();
}
}
2.UDP接收端---也是创建DatagramSocket对象
接收端需要进行的步骤:①找到对应码头,传入对应端口号、②拿一个新的包、③去码头接收并且将礼物放入新包中、④从新包中获得礼物、⑤接收结束
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpDemo2 {
/*
UDP接收端代码实现
*/
public static void main(String[] args) throws IOException {
//1.找码头,参数为发送端目的地的端口号
DatagramSocket ds = new DatagramSocket(10001);
//2.找个新箱子,传递两个参数
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//3.去码头接收礼物,然后将礼物放入新箱子
ds.receive(dp);//表示码头拿这个箱子去接收数据
//4.从新箱子中获得礼物
byte[] data = dp.getData();
/*
注意:此时的接收端获得的内容,会有很多null
所以我们需要优化,优化思路就是确定箱子中含有数据的长度
最后打印传入new String();参数中去
*/
int length = dp.getLength();
System.out.println(new String(data,0,length));
//5.最后完成礼物接收
ds.close();
}
}
注意:创建好发送端和接收端之后,先运行接收端!!!如果接收端一直没有接收到东西,就会一直死等(阻塞)。
六、UDP综合练习(发送端可以一直发送)
题目要求:发送端可以一直键盘录入发送,一直等到发送“886”之后,才会停止发送。而接收端可以接收到发送端的信息
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class ClientDemo {
/*
发送端
*/
public static void main(String[] args) throws IOException {
//由于发送端要用到键盘录入,所以创建Scanner对象
Scanner sc = new Scanner(System.in);
//1.找码头
DatagramSocket ds = new DatagramSocket();
//2.打包信息,最后封装到DatagramPacket对象中
while (true) {
//要打包的内容
String s = sc.nextLine();
if("886".equals(s)){
break;
}
byte[] bytes = s.getBytes();
//确认接收端的ip地址
InetAddress address = InetAddress.getByName("127.0.0.1");
//确认接收端的码头端口号
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
//3.让码头将包发送
ds.send(dp);
}
//4.发送完毕
ds.close();
}
}
---------------------------------------------------------------------------------------
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class ServerDemo {
/*
接收端
*/
public static void main(String[] args) throws IOException {
//1.找到码头,要根据传入端口号跟核实码头是否正确
DatagramSocket ds = new DatagramSocket(10000);
while (true) {
//2.创建一个新的包,并设置包传输一次的内容大小
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//3.用新包在码头上接收发过来的礼物
ds.receive(dp);
//4.将新包中的礼物进行解析
byte[] data = dp.getData();
int len = dp.getLength();
System.out.println(new String(data,0,len));
}
/*
接收完毕,离开码头,
一般我们在接收端完毕后,就会调用close(),但是由于我们要求
发送端会一直发送东西,所以我们这里不关闭
*/
}
}
注意:该发送端可以一次打开多个,接收端都可以接收的到
七、UDP三种通讯方式
我们可以根据UDP协议接收端数量的不同,去分为三种通讯形式:
①点播;②组播(只在ipv4中的概念,在ipv6中叫多播);③广播
1.点播
(上述的综合练习,就相当于点播)
2.组播
一个发送端 ——> 一组接收端
组播地址:224.0.0.0 --- 239.255.255.255,其中224.0.0.0 --- 224.0.0.255为预留的组播地址,我们不可用!
Ⅰ---发送端步骤:①创建码头对象;②创建数据,并且打包;③由码头发送到指定组播地址;④发送完成.close();
Ⅱ---接收端步骤:①创建MulticastSocket()对象;②创建箱子,接收数据;③将当前电脑添加到组中;④调用DatagramSocket中的方法receive()去接收并且存放至箱子;⑤解析箱子内容,并打印控制台;⑥接收完毕.close();
/*
发送端
*/
public static void main(String[] args) throws IOException {
//1.创建码头
DatagramSocket ds = new DatagramSocket();
//2.将发送内容并打包打包
//内容
String s = "hello,组播方式";
byte[] bytes = s.getBytes();
//组播ip地址
InetAddress byName = InetAddress.getByName("224.0.1.0");
//指定端口号
int port = 10002;
//打包
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,byName,port);
//3.发送
ds.send(dp);
//4.发送结束
ds.close();
}
---------------------------------------------------------------------------------
/*
接收端
*/
public static void main(String[] args) throws IOException {
//1.创建MulticastSocket对象,传入对应端口
MulticastSocket ms = new MulticastSocket(10002);
//2.创建个新的箱子
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//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();
}
3.广播
发送端步骤:和组播差不多,但是就是把组播ip改为广播用的ip(255.255.255.255);
接收端:这个时候接收端对象还是用DatagramSocket对象,注意端口一致就可以
八、TCP客户端/服务器(发送端/接收端)
定义:TCP通信协议是一种可靠的网络协议,它在通信的两端个建立一个Socket对象
客户端的对象为:Socket;
接收端的对象为:ServerSocket;
-----在通信之前要保证已经建立了连接,然后通过Socket产生IO流来进行通信。
/*
客户端(发送端)建立
*/
public static void main(String[] args) throws IOException {
//1.创建客户端对象,传入ip,和端口号
Socket socket = new Socket("127.0.0.1",10004);
//2.获取一个IO流开始写数据,这里获得的为一个往外写的字节流
OutputStream os = socket.getOutputStream();
//所以这里写的应该是个字节数据
os.write("TCP123".getBytes());
//3.释放资源
os.close();
socket.close();
}
------------------------------------------------------
/*
服务器(接收端)建立
*/
public static void main(String[] args) throws IOException {
//1.创建服务器对象ServerSocket,传入客户端一致的端口
ServerSocket ss = new ServerSocket(10004);
//2.等待客户端连接,如果连接成功则返回一个Socket对象,如果没有返回
//那么代码就会在这一行死等(阻塞)
Socket accept = ss.accept();
//3.获得输入流对象
InputStream is = accept.getInputStream();
/*
注意:我们的is一次只能读一个字节,
所以我们需要建立循环去读
*/
int b;
while((b = is.read()) != -1){
System.out.print((char) b);//记得转换为char
}
//4.释放资源
is.close();
ss.close();
}
注意:也是要先执行服务器!!!
总结:
九、三次握手和四次挥手
1.三次握手
2.四次挥手
为什么会有四步,是因为要多出一步去处理已经建立的数据传输通道
十、TCP通信协议练习
1.练习一:服务器端会返回一句信息(用到IO流转换)
要求:客户端发送给服务器的消息过后,服务器会返回一句信息
import java.io.*;
import java.net.Socket;
public class TcpDemo2A {
/*
练习题:客户端---"这个是客户端发送的一句话123HOHO!"
练习题要求:当客户端发送了一句话之后,服务器会返回一句话
*/
public static void main(String[] args) throws IOException {
//1.创建客户端对象
Socket socket = new Socket("127.0.0.1",10005);
//2.获得一个io流开始往出去写数据
OutputStream os = socket.getOutputStream();
os.write("这个是客户端发送的一句话123HOHO!".getBytes());
//这里也要添加个标记,输出流标记
socket.shutdownOutput();
/*
根据题干:我们需要再这里接收服务器返回的信息
由于返回的为一个字符串,我们将字节流强转至字符流
*/
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s;
while((s = reader.readLine()) != null){
System.out.print(s);
}
//3.释放资源
socket.close();
os.close();
reader.close();
}
}
-----------------------------------------------------------
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpDemo2B {
/*
练习题:服务器---"服务器已经接收到了!123"
*/
public static void main(String[] args) throws IOException {
//1.创建服务器对象
ServerSocket ss = new ServerSocket(10005);
//2.调用accept()等待客户端连接
Socket accept = ss.accept();
//3.获得一个往里读的对象并读取
//最开始我们会用下面这个方式,但是客户端发送的为字符串的时候,容易出现编解码错误
// InputStream is = accept.getInputStream();
// int b;
// while((b = is.read())!=-1){
// System.out.print((char)b);
// }
//故此我们再这里对字节输入流进行一个强转,转成字符流读取
BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
String s;
while((s = reader.readLine())!= null){
System.out.print(s);
}
//如果此处我们没有添加输入流标记,那么下面的返回语句也没机会发送给客户端(阻塞)
accept.shutdownInput();
/*
注意题干:当我们接收到客户端发送的消息,我们需要返回一个消息
所以这里我们要用到Socket类中的字节输出流去写
*/
OutputStream os = accept.getOutputStream();
os.write("服务器已经接收到了!123".getBytes());
//4.释放资源
ss.close();
accept.close();
reader.close();
os.close();
}
}
2.练习二:读取本地文件传给服务器并接收回应
import java.io.*;
import java.net.Socket;
public class TcpDemo3A {
/*
客户端
要求:把本地文件a.jpg读取给服务器
让服务器返回一个"上传成功";提示
*/
public static void main(String[] args) throws IOException {
//1.创建客户端对象
Socket socket = new Socket("127.0.0.1",10006);
//2.创建本地流,关联本地文档读取
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("inter\\TCP练习发送本地文件\\a.jpg"));
//创建网络流,去发送给服务器,os相当于一个字节流,所以我们用一个包装类,提高效率
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
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.print(line);
}
socket.close();
bis.close();
}
}
------------------------------------------------------------
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpDemo3B {
/*
服务器端
接收客户端发的文件,然后返回一句信息
*/
public static void main(String[] args) throws IOException {
//1.创建服务器端对象
ServerSocket ss = new ServerSocket(10006);
//2.等待客户端连接
Socket accept = ss.accept();
//创建两个流,一个读,一个写
//第一个流为网络中的流,读取客户端发送的信息
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//第二个流为本地的流,去写入
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("inter\\TCP练习发送本地文件\\b.jpg"));
int b;
while((b = bis.read())!=-1){
bos.write(b);
}
//给客户端返回一个接收到的信息
OutputStream os = accept.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));//加快效率
bw.write("成功");
bw.newLine();
bw.flush();
//3.释放资源,注意我们这里只用关闭本地流和服务器端就行,网络流在服务器端对象中,就不用单独关了
ss.close();
accept.close();
bos.close();
}
}
注意:我们可以对服务器进行死循环优化,让客户端发送一次之后还可以继续发送,参考本文章六条目!!!
十一、TCP练习题二的服务器弊端一(覆盖原路径)
1.UUID引入
弊端:当我们在客户端进行了一次网络传输之后,我们的服务器在新建本地文档的时候,第二次上传文件时,会原来的文件进行覆盖!
解决思路:我们需要一个随机生成且不重复的方法去生成新的文件名
所以引出了一个类:UUID
import java.util.UUID;
public class UUIDDemo {
/*
UUID的使用方法
*/
public static void main(String[] args) {
int count = 0;
while (count<3) {
UUID uuid = UUID.randomUUID();
String s = uuid.toString();
count++;
System.out.println(s);
}
}
}
生成的是随机且不重复的,而且可以用toString();输出为字符串
2.UUID解决上述问题的实现
//第二个流为本地的流,去写入
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("inter\\TCP练习发送本地文件\\"+ UUID.randomUUID().toString()+".jpg"));
就是将上述服务器端的bos中的路径名加入生成的UUID字符串
十二、TCP练习题二的服务器弊端二(服务器无法同时处理多个客户端发送)
问题描述:如果仅仅使用while(true){},只解决了服务器可以接收多个客户端的连接请求,但是没有解决同时跟多个客户端的连接请求和传输!!!!
解决思路:采用多线程,将服务器接收到客户端连接之后的操作写成一个线程类
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.UUID;
public class TcpDemo3B {
public static void main(String[] args) throws IOException {
//1.创建服务器端对象
ServerSocket ss = new ServerSocket(10006);
while (true) {
//2.等待客户端连接
Socket accept = ss.accept();
/*
思路:当服务器接收到客户端连接了之后,创建一个线程
去进行操作。
*/
ThreadSocket ts = new ThreadSocket(accept);
new Thread(ts).start();
}
//ss.close();
}
}
//创建一个服务器的线程类
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("inter\\TCP练习发送本地文件\\"
+ UUID.randomUUID().toString()+".jpg"));
int b;
while((b = bis.read())!=-1){
bos.write(b);
}
//给客户端返回一个接收到的信息
OutputStream os = acceptSocket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));//加快效率
bw.write("成功");
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bos!=null){
//对这个加try{}catch{}的时候要进行非空判断
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(acceptSocket!=null){
try {
acceptSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
十三、对十二解决方案的进一步优化(线程池)
问题描述:使用多线程确实解决了服务器同时跟多个客户端的连接和传输,但是资源消耗太大
解决思路:利用自定义线程池(JAVA多线程Ⅱ高级),我们去对服务器进行最终优化
public static void main(String[] args) throws IOException {
//1.创建服务器端对象
ServerSocket ss = new ServerSocket(10006);
//线程池优化
ThreadPoolExecutor tp = new ThreadPoolExecutor(
1,//核心线程
3,//最大线程数
60,//临时线程存活时间
TimeUnit.SECONDS,//存活时间单位
new ArrayBlockingQueue<>(5),//阻塞队列,最多允许5个线程排队
Executors.defaultThreadFactory(),//创建线程的方式
new ThreadPoolExecutor.AbortPolicy()//拒绝策略:一、默认方式(带有抛出异常的)
);
while (true) {
Socket accept = ss.accept();
ThreadSocket ts = new ThreadSocket(accept);
//用线程池优化
tp.submit(ts);
//注意:由于要随时等待客户端发送,所以这里不关闭线程池
//tp.shutdown();
}
//ss.close();
}