09130915(051053天 网络编程)
每日一狗(田园犬西瓜瓜)
网络编程
文章目录
课前准备
tcp/ip分层协议栈(七层架构):
tcp的三握四挥:
三次握手
- 客户端:我要和你创建连接了,你准备一下吧
- 服务器:我准备好了,你链接吧
- 客户端:好的,连接创建确定
四次挥手
- 客户端:我要断开连接了,你准备一下
- 服务器:别忙我这还有活没玩,等会
- 服务器:活干完了,断开连接吧
- 客户端:好的,断开连接确定
背:.tcp断开链接为什么时四挥,能不能三挥?
因为其中还有一段时间需要将工作干完了才能断开
HTTP1.0和1.1的区别:
tcp和udp的区别:
TCP面向连接的协议 | UDP 用户数据协报议 | |
---|---|---|
是否需要建立连接 | 是,三握四挥 | 传输数据之前源端和终端不建立连接 |
是否安全 | 是 | 否 |
有没有重传机制 | 为保证安全需要重传和对传输数据进行正确性验证 | 否 |
思想 | 就是安全 | 就是快 |
应用场景 | 像是直播一样对数据的时效性要求很高的数据 |
1. 啥也不是
URL
URL对象代表统一资源定位器,是指向互联网资源的指针
http协议的标准端口为80 https=http+SSL 加密数据传输的http,标准端口为443
- URL统一资源指针,可以指定一个具体的资源,例如一个html网页
- URI统一资源标识符,可以认为为了方便记忆,给URL起的别名
// 创建一个URL对象,用于指代网络中的一个资源,如果网址不合法,则抛出MalformedURLException
URL url = new URL("url");
// 重要方法
url.openConnection():URLConnection// 可以获取输入、输出流
url.openStream():InputStream// 直接获取输入流
// 不重要的方法:通过URL对象的一些方法可以访问该URL对应的资源:
String getFile()://获取该URL的资源名
String getHost()://获取主机名
String getPath()://获取路径部分
int getPort()://获取端口号
URL和URLConnection比较
URL:统一资源指针
URLConnection:一种连接
URL url = new URL("https://zhuanlan.zhihu.com/p/24860273");
InputStream is = url.openStream();
URLConnection conn = url.openConnection();
InputStream is1 = conn.getInputStream();
OutputStream os1 = conn.getOutputStream();
URL只能获取输入流,但是URLConnection可以获取输入输出流
TCP编程
TCP
- 提供可靠的数据通讯
- 面向连接的端对端的保证可靠传输的协议
- 可以得到一个顺序的无差错的数据流
- 建立连接后可以互相通信
UDP
- 不保证数据的可靠性
- 协议简单、传输速度块(如音视频传输,这些不需要很高的可靠性,允许偶尔丢包)
Socket
Socket套接字,就是两台主机之间逻辑连接的端点。
TPC协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。Socket本质上就是一组接口,是对TCP/IP协议的封装和应用(程序员层面上)
端口号的范围是0到65536,但是0到1024是为特权服务保留的端口号,我们可以选择任意一个当前没有被其他进程使用的端口。
客户端请求与服务器进行连接的时候,根据服务器的域名或者IP地址,加上端口号,打开一个套接字。当服务器接受连接后,服务器和客户端之间的通信就像输入输出流一样进行操作。
开放端口包括三种:
- 0.0.0.0:端口号
- 127.0.0.1:端口号(只供本机访问的端口)
- 主机ip:端口号
cmd命令查看端口使用情况
netstat -an
- LISTENING是指开放着的,等待连接的
- ESTABLISHED是正在连接
- CLOSE_WAIT、TIME_WAIT、SYN_SENT是三次握手四次挥手过程中的某些状态
ServerSocket类
Java.net包中的ServerSocket类用于表示服务器套接字,其主要功能是监听客户端的请求,然后将客户端的请求连接存入队列中,默认请求队列大小是50。
端口扫描
//利用的是ServerSocket在创建时,如果端口已经被占用,则报异常 for(int i=0;i<=65535;i++) { //端口号0表示使用自由端口,实际上是不能建立连接的 try { ServerSocket ss=new ServerSocket(i); ss.close(); //finally } catch (Exception e) { System.out.println("端口"+i+"已经被占用"); } }
客户端Socket构造器
Socket( //
String host,
int port,
InetAddress localAddr,
int localPort
);
Socket( // InetAddress对象中可以封装一个IP地址和一个主机名
InetAddress address,
int port,
InetAddress localAddr,
int localPort
);
Socket练习题
**1、**客户端与服务器端相互通讯
// 客户端发送:
来个套餐
// 服务器回复:
欢迎光临
这个套餐没了,明天吧
// 服务器端
// 构建服务器对象
ServerSocket ss = new ServerSocket(9009);
// 请求阻塞
Socket socket = ss.accept();
// 获取连接的输入输出流
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
// 使用输出流构建ps方便输出
PrintStream ps = new PrintStream(os);
// 发送信息
ps.println("欢迎光临");
// 使用输入流构建bf方便读取
BufferedReader bf = new BufferedReader(new InputStreamReader(is));
// 接受并输出信息
String str = bf.readLine();
System.out.println(str);
// 发送信息
ps.println("这个套餐没了,明天吧");
// 关闭资源
bf.close();
ps.close();
socket.close();
ss.close();
// 客户端
// 与指定服务器的指定端口建立连接
Socket socket = new Socket("127.0.0.1", 9009);
// 获取连接的输入输出流
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
// 读取并显示
BufferedReader bf = new BufferedReader(new InputStreamReader(is));
String str = bf.readLine();
System.out.println(str);
// 发送信息
PrintStream ps = new PrintStream(os);
ps.println("来个套餐");
// 获取返回信息并显示
str = bf.readLine();
System.out.println(str);
// 关闭资源
bf.close();
ps.close();
socket.close();
2、
3、
简单的Client/Setver程序
需求:客户端传送字符串给服务区端
**服务器端:**一般会常开一个端口来为客户端提供服务
- 创建ServerSocket对象,绑定监听端口
- 通过accept方法监听客户端请求
- 连接建立后,通过输入流读取客户端发送的请求信息
- 通过输出流向客户端发送相应信息
- 关闭相应的资源对象
ServerSocket server = new ServerSocket(9999);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("hello");
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String ss = br.readLine();
System.out.println("Client:" + ss);
// 传输数据为指定数据时返还当前时间数据
if ("hello".equals(ss)) {
Date now = new Date();
DateFormat df = new SimpleDateFormat("yyyy-MM-DD-E hh:mm:ss");
ss = df.format(now);
ps.println(ss);
}
// 关闭资源
br.close();
ps.close();
socket.close();
server.close();
客户端:
- 创建Socket对象,指明需求连接的服务器地址和端口号
- 连接建立后,通过输出流向服务器发送请求信息
- 通过输入流获取服务器响应的信息
- 关闭相应资源对象
Socket socket = new Socket("127.0.0.1", 9999);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
// 传输数据
PrintStream ps = new PrintStream(os);
ps.println("hello");
// 使用网络传输的输入流创建缓冲流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String ss = br.readLine(); // 阻塞程序
System.out.println("Server:" + ss);
System.out.println("Setver:" + br.readLine());
// 关闭资源
br.close();
ps.close();
socket.close();
服务器端的编程思想
主线程一直处于阻塞等待状态,如果一旦链接建立成功则启动一个新线程对外提供服务,而主线程继续等待新的链接请求。
public class MyServer {
public static void main(String[] args) throws Exception {
ServerSocket server = new ServerSocket(9999);
while (true) {
Socket socket = server.accept();
new MyWorker(socket).start();
}
}
}
class MyWorker extends Thread {
private Socket socket;
public MyWorker(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
BufferedReader br = null;
PrintStream ps = null;
try {
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
br = new BufferedReader(new InputStreamReader(is));
ps = new PrintStream(os);
String ss = br.readLine();
System.out.println("Client:" + ss);
if ("hello".equals(ss)) {
Date now = new Date();
DateFormat df = new SimpleDateFormat("yyyy-MM-ddE hh:mm:ss");
ss = df.format(now);
ps.println(ss);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (br != null)
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
if (ps != null)
ps.close();
if (socket != null)
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
c/s和b/s区别
- C/S (Client/Server,客户机/服务器)结构,胖客户端应用,是软件系统体系结构的一种。
- C/S模式简单地讲就是基于企业内部网络的应用系统,大部分的应用逻辑都集中在客户端中,而一般服务器端只提供数据的存储。
- 不依赖企业外网环境,即无论企业是否能够上网,都不影响应用。
- B/S结构(Browser/Server结构)结构即浏览器和服务器结构,瘦客户端应用,主要逻辑集中在服务器端,客户端一般只包含的简单的显示逻辑。
- 它是随着Internet技术的兴起,对C/S结构的一种变化或者改进的结构。
- 主要事务逻辑在服务器端Server实现,形成所谓三层3-tier结构。
C/S聊天室的实现
服务器端应用包含多个线程,每个Socket对应一个线程,该线程负责读取Socket对应输入流的数据,并将读到的数据向每个Socket发送一次
每个客户端应该包含两个线程,一个负责读取用户键盘输入并将用户输入数据写入Socket对应的流中,一个负责读取Socket对应输入流的数据并打印显示
3. 粘包
数据包界限不清,先发送一个int值标识接下来的数据的长度,然后你读的时候就读这些,然后去读取下一个
粘包问题主要是由于数据包界限不清,最好的解决办法就是在发送数据包前事先发送一个int型数据,该数据代表将要发送的数据包的大小,这样接收端可以每次触发OP_READ的时候先接受一个int大小的数据段到缓存池中,然后,紧接着读出后续完整的大小的包,这样就会处理掉粘包问题。因channel.read()方法不能给读取数据的大小的参数,所以无手动指定读取数据段的大小。但每次调用channel.read()返回的是实际读取的大小。
怎么处理
粘的时候搞一个分隔符、固定消息长度、TLV格式消息
分隔符
固定消息长度
4. UDP网络程序
1、使用DatagramSocket()创建一个数据包套接字。
2、使用DatagramPacket(byte[]buf, int offset, int length, InetAddress address, int port)创建要发送的数据包。
3、使用DatagramSocket类的send()方法数据包
UDP网络通信的收包过程
1、使用DatagramSocket(int)创建一个数据包套接字,绑定到指定的端口。
2、使用DatagramPacket(byte[]buf,int length)创建字节数组来接收数据包.
3、使用DatagramSocket类的receive()方法接收UDP
获取的数据实际上就存储在创建空包的数组种,转换显示时建议设置长度
System.out.println(dp.getLength()); //数据的具体长度
String str=new String(buffer,0,dp.getLength());
一般来说UDP协议的最大数据包的长度64k
编码实现
发送方
package com.test;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Send {
public static void main(String[] args) throws Exception {
DatagramSocket ds = new DatagramSocket();
String msg = "now?";
byte[] buffer = msg.getBytes();
DatagramPacket dp = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("localhost"), 8888);
ds.send(dp); // 发送信息
buffer = new byte[8192];
dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp); // 接受信息
msg = new String(dp.getData(), 0, dp.getLength());
System.out.println("Send:" + msg);
ds.close();
}
}
接收方
package com.test;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Rec {
public static void main(String[] args) throws Exception {
DatagramSocket ds = new DatagramSocket(8888);
byte[] buffer = new byte[8192];
DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
ds.receive(dp); // 接收信息
String ss = new String(buffer, 0, dp.getLength());
if ("now?".equals(ss)) {
Date now = new Date();
DateFormat df = new SimpleDateFormat("yyyy-MM-ddE hh:mm:ss");
ss = df.format(now);
InetAddress ia = dp.getAddress();
int port = dp.getPort();
buffer = ss.getBytes();
dp = new DatagramPacket(buffer, buffer.length, ia, port);
ds.send(dp); // 发送信息
}
ds.close();
}
}
多播或者组播的实现
真正聊天室的实现原理
MulticastSocket
MulticastSocket可以将数据报以广播的方式发送给加入指定组的所有客户端
组播是指把信息同时传递给一组目的地址。它使用的策略是最高效的,因为消息在每条网络链路上只需传递一次,且只有在链路分叉的时候,消息才会被复制。与多播相比,常规的点到单点传递被称作单播。当以单播的形式把消息传递给多个接收方时,必须向每个接收者都发送一份数据副本。由此产生的多余副本将导致发送方效率低下,且缺乏可扩展性。不过,许多流行的协议——例如XMPP,用限制接收者数量的方法弥补了这一不足
5. 代理服务器
JDK1.5提供了Proxy和ProxySelector类来实现代理访问。
Proxy代表一个代理服务器,可以在打开URLConnection连接时指定代理,也可以在创建Socket连接时指定代理。ProxySelector是一个代理选择器,提供了对代理服务器更加灵活的控制,可以对http\https\ftp\socket等进行分别设置,还可以设置不需要通过代理服务器的主机和地址
代理服务器的功能
- 突破自身IP限制
- 提高访问速度
大作业 网盘
要求:实现一个网盘管理功能
上传、下载、删除、列表显示所有目录和文件