JAVA-套接字
网络基本概念
七层模型:物理层 数据链路层 网络层 传输层 - UDP/TCP 会话层 应用层 表示层 — HTTP、FTP、POP3、SMTP …
IP地址:在网络中标记主机。
IPv4 — 四组数表示一个IP地址,每一组数的取值范围是0-255
IPv6 — 六组数表示一个IP地址
端口:计算机与外界交互的媒介 — 端口号 — 0~65535 — 0-1024
域名:各个网站提供的便于记忆的标记
DNS解析服务器:将域名和IP地址进行对应
UDP
基于流的。不建立连接,不可靠。传输速度相对比较快的。需要对数据进行封包,每个包不超过64K大小。 适用于对速度依赖性比较强但是对可靠性依赖性比较低的场景 — 视频聊天 — DatagramSocket DatagramPacket
通信过程
发送端:
- 创建套接字对象
- 准备数据包,将数据放入数据包中,并且绑定要发往的地址
- 发送数据包
- 关流
接收端:
- 创建套接字对象,并且绑定要接收的端口号
- 准备数据包
- 接收数据
- 关流
- 解析数据
案例:使用一个线程表示发送端,一个线程表示接收端—实现单人聊天功能
public class UDPChatDemo {
public static void main(String[] args) {
new Thread(new Sender()).start();
new Thread(new Receiver()).start();
}
}
// 发送端
class Sender implements Runnable {
private DatagramSocket ds;
private DatagramPacket dp;
private Scanner s;
{
try {
// 创建套接字对象
ds = new DatagramSocket();
s = new Scanner(System.in);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
// 读取数据
byte[] bs = s.nextLine().getBytes();
// 准备数据包
dp = new DatagramPacket(bs, bs.length, new InetSocketAddress("255.255.255.255", 8090));
try {
// 发送数据
ds.send(dp);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 接收端
class Receiver implements Runnable {
private DatagramSocket ds;
private DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
{
// 创建UDP对象并且绑定端口号
try {
ds = new DatagramSocket(8090);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
try {
// 接收数据
ds.receive(dp);
// 解析数据
System.out.println(dp.getAddress() + ":");
System.out.println(new String(dp.getData(), 0, dp.getLength()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
TCP
基于流的。建立连接,经历三次握手,可靠。但是传输速率相对较慢。理论上不限制传输的数据的大小。适用于对可靠性的依赖性更高对速度依赖性较低的场景 — 文件传输
注意:receive/connect/accept/write/read都会产生阻塞。
BIO - Blocking IO - 同步式阻塞式IO
NIO - New IO - NonBlocking IO - 同步式非阻塞式IO - JDK1.4
AIO - Asynchronous IO - 异步式非阻塞式IO - JDK1.8
通信过程
客户端 - Socket
- 创建客户端的套接字对象
- 发起连接,绑定连接地址
- 获取自带的输出流,写出数据,禁用输出流
- 如果服务器端有打回的数据,则需要获取输入流读取数据,禁用输入流
- 关流
服务器端 - ServerSocket
- 创建服务器端的套接字对象,并且绑定监听的端口号
- 接受连接,获取到一个Socket对象
- 获取输入流,读取消息,禁用输入流
- 如果需要向客户端打回消息,则需要获取输出流写出数据,禁用输出流
- 关流
案例:上传文件 - 在客户端读取文件并将读取的内容传到服务器端;在服务器端接收数据然后将接收的数据在写到对应的文件上
Tips:如果文件名的字节个数超过了一个字节所能表示的范围,那么这个时候可以用2个字节传输所表示文件名的字节个数
byte n = (byte)(len / 256); // 确定文件名中有几个完整的字节
byte left = (byte)(len % 256 - 128); // 去掉完整字节之后的剩余的可以传输的字节个数
读取的时候通过int len = n * 256 + left + 128; 从而表示完整的全部文件名占用的字节个数
public class FileClient {
public static void main(String[] args) throws Exception {
// 创建一个客户端的套接字对象并且发起连接
Socket s = new Socket();
s.connect(new InetSocketAddress("localhost", 8090));
// 获取输出流
OutputStream out = s.getOutputStream();
// 创建File对象指向要写出文件
File file = new File("E:\\JAVASE.xmind");
// 获取文件的名字及类型
byte[] name = file.getName().getBytes();
// 写出文件名的长度
byte n=(byte)(name.length/256);
byte left=(byte)(name.length%256-128);
out.write(n*256+left+128);
// 写出文件名
out.write(name);
// 读取文件内容并将内容写出
FileInputStream fin = new FileInputStream(file);
byte[] bs = new byte[1024];
int len = -1;
while ((len = fin.read(bs)) != -1)
out.write(bs, 0, len);
// 关流
s.shutdownOutput();
fin.close();
s.close();
}
}
public class FileServer {
public static void main(String[] args) throws Exception {
// 创建服务器端的套接字对象并接受连接
ServerSocket ss = new ServerSocket(8090);
Socket s = ss.accept();
// 获取输入流
InputStream in = s.getInputStream();
// 读取文件名的长度
int len = in.read();
// 读取文件名
byte[] name = new byte[len];
in.read(name);
// 读取文件内容,并将读取的数据写出
FileOutputStream fout = new FileOutputStream("D:\\" + new String(name));
byte[] bs = new byte[1024];
int i = -1;
while ((i = in.read(bs)) != -1)
fout.write(bs, 0, i);
// 关流
s.shutdownInput();
fout.close();
ss.close();
}
}