一、网路编程介绍
指在网络通信协议下,不同计算机上运行的程序,进行数据传输
二、软件架构分类
1)CS架构(Client Server 客户端 服务端)
在用户本地有一个客户端程序,在远程有一个服务端程序(如微信)
优点:画面精美,功能丰富用户体验度更高
缺点:需要开发客户端,也要开发服务端,用户需要下载和更新的时候比较麻烦
场景:适合定制专业化的办公类软件如:IDEA、网游
2)BS架构(Browser Server 浏览器 服务器端)
只需要一个浏览器,用户通过不同的网址,客户访问不同的服务器端程序
优点:不需要开发客户端,只需要开发服务端,用户不需要下载,打开浏览器就能使用
缺点:如果应用过大,用户体验受到影响
场景:适合移动互联网应用,可以在任何地方随时访问的系统
注:微信小程序类是BS架构,因为没有安装独立的客户端
三、网络编程三要素
1.IP:设备在网络中的唯一标识
IPV4:
互联网通信协议第四版,采用32位地址长度(2^32个IP地址),分成四组(点分十进制表示法:192.168.1.88),2019年11月月26日全部分配完毕,主流使用
1.公共地址(万维网使用)和私有地址(局域网使用)
2.192.168.开头的就是私有地址,范围在192.168.0.0~192.168.255.255,专为组织机构内部使用,以此节省IP,由交换机分配
IPV6:互联网通信协议第六版,由于互联网的蓬勃发展,IP地址的需求量越来越大,而IPV4的模式下IP的总数有限的采用128位地址长度(2^128个IP地址),分为8组(冒分16进制表示法:2001:DB8:0:23:8:800:200C:147A,如果计算出来的16进制表示形式中间有多个连续的0会省略,变成::,这种称为0位压缩表示法)
特殊IP地址:
127.0.0.1,也可以是我们常说的localhost:是回送地址也称本地回环地址,也称本机IP,永远只会寻找当前所在本机(常用于本地开发)
常用的CMD命令:
ipconfig:查看本机IP地址
ping:检查网络是否联通
InetAddress 对象:
为了方便我们对IP地址的获取和操作,Java提供 了-一个类InetAddress供我们使
InetAddress:此类表示Internet协议(IP) 地址。
方法名 | 说明 |
static InetAddress getByName(String host) | 确定主机名称的IP地址,主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
public static void main(String[] args) throws UnknownHostException {
InetAddress address = InetAddress.getByName("LAPTOP-HPUBROHL");
System.out.println(address); //用设备名称打印出来的就是LAPTOP-HPUBROHL/172.20.10.8,用IP打印出来的就是/172.20.10.8
String hostName = address.getHostName();
System.out.println(hostName);
String hostAddress = address.getHostAddress();
System.out.println(hostAddress);
}
2.端口:程序在设备中的唯一标识
由两个字节表示的整数,取值范围: 0~65535
其中0~ 1023之间的端口号用于一些知名的网络服务或者应用。
我们自己使用1024以上的端口号就可以了。.
注意:一个端口号只能被-一个应用程序使用。
3.协议:数据在网络中传输的规则,常见的协议有UDP、TCP、http、https、ftp
1) UDP:用户数据协议(User Datagram protocol)
UDP是面向无连接通信协议:速度快,有大小限制一次最多64K,数据不安全,易丢失数据
不管是否已经连接成功,都能发送
2)TCP: 传输控制协议TCP(transmission Control Protocol)
TCP协议是面向连接的通信协议:速度慢,没大小限制,数据安全
需要确保连接成功才能发送数据
四、UDP 协议应用
UDP通信——发送数据步骤:
1.创建发送端的DatagramSocket对象
2.数据打包(DatagramPacket)
3.发送数据
4.释放资源
UDP通信——接收数据步骤:
1.创建接收端的DatagramSocket对象
2.接收打包好的数据
3.解析数据包
4.释放资源
Send.java
public class Send {
public static void main(String[] args) throws Exception {
//1.DatagramSocket创建对象,如果不传参,创建的是随机绑定的
DatagramSocket socket = new DatagramSocket(8888);
/*
2.DatagramPacket创建包,打包数据
第一个参数是字节数组
第二个参数是字节数据长度
第三个参数是InetAddress
第四个是接收端的端口号
*/
String msg = "你好,蓝天白云";
byte[] bytes = msg.getBytes();
//3.打包
DatagramPacket msgPacket = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("127.0.0.1"),9999);
//4.发送
socket.send(msgPacket);
socket.close();
}
}
Resolve.java
public class Resolve {
public static void main(String[] args) throws Exception {
//1.DatagramSocket创建对象,注意端口号不要与发送端相同,端口号唯一
DatagramSocket socket = new DatagramSocket(9999);
//2.创建DatagramPacket对象,传一个字符数组,一个长度
DatagramPacket datapcket = new DatagramPacket(new byte[1024],1024);
//3.接收数据
socket.receive(datapcket);
//4.拆解
byte[] data = datapcket.getData();
String msg = new String(data,0,datapcket.getLength());
System.out.println(msg);
socket.close();
}
}
注意:启动是县启动接收,在启动发送
案例:
按照下面的要求实现程序
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
public class Senders {
public static void main(String[] args) throws Exception {
//1.创建DatagramSocket对象,这里命名时为了多次启动多个报错
DatagramSocket datagramSocket = new DatagramSocket();
//2.输入发送的数据
Scanner scanner = new Scanner(System.in);
while (true){
System.out.println("请输入:");
final String s = scanner.nextLine();
byte[] st = s.getBytes();
DatagramPacket dPacket = new DatagramPacket(
st,s.length(), InetAddress.getByName("127.0.0.1"),3333);
datagramSocket.send(dPacket);
if("886".equals(s)){
break;
}
}
datagramSocket.close();
}
}
public class ChatRoom {
public static void main(String[] args) throws Exception {
//1.创建DatagramSocket对象
DatagramSocket datagramSocket = new DatagramSocket(3333);
while (true){
//2.创建包裹
DatagramPacket CPacket = new DatagramPacket(new byte[1024], 1024);
//3.接收数据
datagramSocket.receive(CPacket);
byte[] data = CPacket.getData();
String cs = new String(data, 0, CPacket.getLength());
System.out.println(cs);
}
}
}
五、TCP协议应用
客户端:
1.创建Socket对象指定ip 和端口
Socket(String host, int port)
2.通过socket对象获取传输数据的流对象
将客户端的流写入到服务端: OutputStream getoutputStream( )
读取服务端书写过来的数据:InputStream getInputStream()
3.通过流对象收发数据
4.释放资源
服务端:
1.创建ServerSocket对象指定端口
ServerSocket(int port )
2.响应客户端发送的请求
Socket accept( )
3.通过socket 对象获取传输数据的流对象
向客户端输出:OutputStream getoutputStream()
读客户端输出流:InputStream getInputStream()
4.通过流对象收发数据
5.释放资源
public class Server {
public static void main(String[] args) throws Exception {
/*
1.创建ServerSocket对象指定端口
ServerSocket(int port )
2.响应客户端发送的请求
Socket accept( )
3.通过socket 对象获取传输数据的流对象
向客户端输出:OutputStream getoutputStream()
读客户端输出流:InputStream getInputStream()
4.通过流对象收发数据
5.释放资源
* */
//1.创建ServerSocket对象指定端口,ServerSocket(int port )
ServerSocket serverSocket = new ServerSocket(8899);
//2.响应客户端发送的请求
Socket accept = serverSocket.accept();
//3.通过socket 对象获取传输数据的流对象
InputStream inputStream = accept.getInputStream();
OutputStream outStream = accept.getOutputStream();
//4.通过流对象收发数据
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
String msg = new String(bytes, 0, len);
String hAddress = accept.getInetAddress().getHostAddress();
System.out.println("这是" + hAddress + "发的消息:" + msg);
outStream.write("你好".getBytes());
//5.释放资源
accept.close();
}
}
public class Client {
public static void main(String[] args) throws Exception {
/*
1.创建Socket对象指定ip 和端口
Socket(String host, int port)
2.通过socket对象获取传输数据的流对象
将客户端的流写入到服务端:OutputStream getoutputStream( )
读取服务端书写过来的数据:InputStream getInputStream()
3.通过流对象收发数据
4.释放资源
* */
//1.创建Socket对象指定ip 和端口,Socket(String host, int port)
Socket socket = new Socket("127.0.0.1",8899);
//2.通过socket对象获取传输数据的流对象
OutputStream outStream = socket.getOutputStream();//将客户端的流写入到服务端
InputStream inputStream = socket.getInputStream();//读取服务端写过来的数据
//3.通过流对象收发数据
String msg1 = "你是谁";
outStream.write(msg1.getBytes());
byte[] bytes = new byte[1024];
int len = inputStream.read(bytes);
String msg = new String(bytes, 0, len);
String hAddress = socket.getInetAddress().getHostAddress();
System.out.println("这是" + hAddress + "发的消息:" + msg);
//4.释放资源
socket.close();
}
}
注意:先启动服务端
TCP的三次握手
第一次:客户单端向服务器发出连接请求(SYN包),等待服务器确认
第二次:服务器向客户端返回一个响应(SYN包 + ACK包),告诉客户端收到请求
第三次:客户端向服务器再次发出确认信息(ACK包),连接建立
TCP的四次挥手
第一次:客户端向服务器发出取消连接请求
第二次:服务器向客户端返回一个响应,表示接收到客户端取消请求
(服务器讲最后的数据处理完毕)
第三次:服务器向客户端发出确认取消信息
第四次:客户端再次发送确认信息,连接取消
案例:模拟上传文件
客户端:将本地文件上传到服务器。接收服务器的反馈。
服务器:接收客户端上传的文件,上传完毕之后给出反馈。
要求:处理文件重名、多线程、使用线程池
Server.java
public class Server {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//1.创建Socket对象
ServerSocket server = new ServerSocket(8899);
System.out.println("服务端已启动……");
while (true){
//2.响应客户端发送的请求
Socket socket = server.accept();
//实现多线程,频繁新建和销毁线程会降低效率
//new Thread(new SubmintText(socket)).start();
//线程池维护线程,提高效率
pool.submit(new SubmintText(socket));
}
}
}
SubmintText.java
public class SubmintText implements Runnable{
private Socket socket;
public SubmintText(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//3.获取传输数据的流对象
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
BufferedReader bR = new BufferedReader(new InputStreamReader(is));
BufferedWriter bW = new BufferedWriter(new OutputStreamWriter(os));
//4.读取上传文件名
String fileName = bR.readLine();
//5.会写客户端信息(可以上传字节了)
bW.write("ok");
bW.newLine();//写换行
bW.flush();//刷数据
//注意:手动拼接服务端的存储位置
//UUID.randomUUID().toString()生成一个随机字符串用于解决文件名字重复问题
File file = new File("D:\\java\\javaStudy\\javaStudy\\img", UUID.randomUUID().toString() + fileName);
//6.读取客户端上传的文件(字节)
FileOutputStream fos = new FileOutputStream(file);
byte[] bys = new byte[1024];
int len;
while ((len = is.read(bys)) != -1){
fos.write(bys,0,len);
}
fos.close();
//7.回写给客户端消息
bW.write("上传成功");
bW.newLine();
bW.flush();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Client.java
public class Client {
public static void main(String[] args) throws Exception {
//1.创建Socket对象
Socket socket = new Socket("127.0.0.1", 8899);
//2.获取传输数据的流对象
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
BufferedReader bR = new BufferedReader(new InputStreamReader(is));
BufferedWriter bW = new BufferedWriter(new OutputStreamWriter(os));
//3.写出文件名
File file = new File("E:\\Java_study\\A.jpg");
bW.write(file.getName());
bW.newLine();
bW.flush();
//4.读取服务端回写的信息
String flag = bR.readLine();
if ("ok".equals(flag)){
//5.创建本地字节流,关联要上传的文件
FileInputStream fis = new FileInputStream(file);
//6.上传文件的字节
byte[] bys = new byte[1024];
int len;
while ((len = fis.read(bys)) != -1){
//因为这里获取到的事字节流所以用字节流写出
os.write(bys,0,len);
}
fis.close();
//重点:给服务端一个结束得标记
socket.shutdownOutput();
}
//7.读取服务端的返回消息
String result = bR.readLine();
System.out.println(result);
socket.close();
}
}