(二十一)套接字

套接字(Socket)

1、基本概念

套接字基于网络进行数据传输的API。实际上就是基于网络的流。
套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

网络模型

通信协议划分为七层:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。

想记住这七层协议就记住这七个字就行:会表应传网数物。

网络层

通过IP地址标记网络设备。

IP协议

IPv4用四组数来标记一个IP地址,每组数的取值范围是0-255(232),将近能标记43亿个地址。
127.0.0.1 指向本机。
255.255.255.255 广播地址。
IPv6用了六组十六进制数来表示一个IP地址。

端口

端口是计算机与外部来交换信息的媒介。
端口号:范围0-65535。0-1024被计算机占用。

域名

二级域名:baidu.com

DNS解析服务器

将域名解析为对应的IP地址。
localhost 指向本机。

传输层

传输层协议:UDP/TCP

表示层

表示层协议HTTP、POP3、FTP、

2、SocketAddress abstract class(套接字类)

此类表示不带任何协议附件的 Socket Address。作为一个抽象类,应通过特定的、协议相关的实现为其创建子类。 它提供不可变对象,供套接字用于绑定、连接或用作返回值。

InetSocketAddress class

此类继承了SocketAddress class。
此类实现 IP 套接字地址(IP 地址 + 端口号)。它还可以是一个对(主机名 + 端口号),在此情况下,将尝试解析主机名。如果解析失败,则该地址将被视为未解析 地址,但是其在某些情形下仍然可以使用,比如通过代理连接。
它提供不可变对象,供套接字用于绑定、连接或用作返回值。
通配符 是一个特殊的本地 IP 地址。它通常表示“任何”,只能用于 bind 操作。

构造函数

InetSocketAddress(InetAddress addr,int port);
根据 IP 地址和端口号创建套接字地址。
InetSocketAddress(int port);
创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。
InetSocketAddress(String hostname,int Port);
根据主机名和端口号创建套接字地址。

重要方法

getAddress();
获取IP地址。
getHostName();
获取主机名或者域名。
getPort();
获取端口号。
toString();

3、UDP

基于流的。不建立连接,不可靠。数据传输的速度比较高,会对数据进行封包操作,每个包不超过64k。适用于相对速度要求比较高而对安全性要求比较低的场景,比如视频聊天。
先启动接收端,后启动发送端。

1.实现类

DatagramSocket

此类表示用来发送和接收数据报包的套接字。
在DatagramSocket上总是启用UDP广播发送。为了接受广播包,应该将DatagramSocket绑定到通配符地址。在某些实现中,将DatagramSocket绑定到一个更加具体的地址时广播包也可以被接收。

构造函数

DatagramSocket();
构造数据报套接字并将其绑定到本地主机上任何可用的端口。
DatagramSocket(int Port);
创建数据报套接字并将其绑定到本地主机上的指定端口。

重要方法

receive();
接收数据。
send();
发送数据。

DatagramPacket

此类表示数据包。

构造函数

DatagramPacket(byte[] buf,int length,InetSocketAddress itsa);
第一个参数表示实际数据,第二个参数表示数据的大小,第三个参数表示接收端的地址。
DatagramPacket(byte[] buf,int length);
第一个参数是一个字节数组,用于存放数据;第二个参数表示所能存储的最大长度。

重要方法

getData();
获取字节数组。
getLength();
获取数据的实际大小。
getAddress();
获取发送端地址。
getPort();
获取发送端的端口号。

2.发送端:

1.创建套接字对象。
2.准备数据包:实际数据,指定数据的实际大小,指定接收端地址。
3.发送数据包。
4.关流。

public class UDPSenderDemo {
    public static void main(String[] args) throws Exception {
        // 创建套接字对象
        DatagramSocket ds = new DatagramSocket();
        // 准备数据包
        // 第一个参数表示的是实际数据--- 你好~~~
        // 第二个参数表示的是数据的大小
        // 第三个参数表示的是要发往的地址
        // 127.0.0.1---指向本机
        // DNS服务器会自动的将localhost解析为127.0.0.1
        DatagramPacket dp = new DatagramPacket("你好~~~".getBytes(), "你好~~~".getBytes().length,
                new InetSocketAddress("localhost", 9999));
        // 发送数据
        ds.send(dp);
        // 关流
        ds.close();
    }
}

3.接收端:

1.创建套接字对象,绑定端口号:需要和发送的时候指定的端口号一致。
2.准备数据包:指定一个字节数组作为实际存储数据的容器,指定这个容器所能使用的大小。
3.接收数据包。
4.关流。
5.解析数据:获取盛放数据的数组以及数据的实际大小。

public class UDPREceiverDemo {
    public static void main(String[] args) throws Exception {
        // 创建套接字对象,绑定端口号
        DatagramSocket ds = new DatagramSocket(9999);
        // 准备一个数据包
        // 第一个参数是一个字节数组,用于存放数据
        // 第二个参数表示所能存储的最大长度
        DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
        // 接收数据
        // 阻塞
        ds.receive(dp);
        // 关流
        ds.close();
        // 解析数据
        // 获取字节数组
        byte[] data = dp.getData();
        // 获取数据的实际大小
        int len = dp.getLength();
        System.out.println(new String(data, 0, len));
        // 获取发送端的地址
        System.out.println(dp.getAddress());
        // 获取发送用的端口号
        System.out.println(dp.getPort());
    }
}

注意
1.如果是写在两个Java文件中,要先启动接收端,后启动发送端。
2.在接收端报错有可能是发送端出问题。
3.接收端receive();方法会产生阻塞。
4.发送端send();方法会产生阻塞。

练习

单人聊天
用一个线程作为发送端,一个线程作为接收端。

public class UDPChatDemo {
    public static void main(String[] args) {
        new Thread(new Sender()).start();
        new Thread(new Receiver()).start();
    }
}
class Sender implements Runnable {
    @SuppressWarnings("resource")
    @Override
    public void run() {
        try {
            // 创建套接字对象
            DatagramSocket ds = new DatagramSocket();
            Scanner s = new Scanner(System.in);
            while (true) {
                // 从控制台获取数据
                String str = s.nextLine();
                // 将数据封包
                DatagramPacket dp = new DatagramPacket(str.getBytes(), str.getBytes().length,
                        new InetSocketAddress("255.255.255.255", 9999));
                // 发送数据包
                ds.send(dp);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
class Receiver implements Runnable {
    @SuppressWarnings("resource")
    public void run() {
        try {
            // 创建套接字对象,绑定端口号
            DatagramSocket ds = new DatagramSocket(9999);
            // 准备数据包
            DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
            while (true) {
                // 接收数据
                ds.receive(dp);
                System.out.println(dp.getAddress() + ":");
                // 解析数据
                System.out.println(new String(dp.getData(), 0, dp.getLength()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4、TCP

基于流的。建立连接,经过三次握手,可靠。传输速度相对较低,理论上不对数据的大小进行限制。分为客户端和服务器端。

1.客户端(Client)

1.创建客户端套接字(Socket)对象。
2.向服务器端发起连接,绑定连接地址。这一步产生阻塞。
3.获取输出流,然后利用输出流来写出数据。
4.通知服务器端数据已经写出完毕。
5.关流

public class TCPClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建客户端的套接字对象
        Socket s = new Socket();
        // 发起连接
        // 阻塞
        s.connect(new InetSocketAddress("localhost", 8090));
        // 获取输出流
        OutputStream out = s.getOutputStream();
        // 写出数据
        out.write("abc".getBytes());
        // 通知服务器端数据已经写出完毕
        s.shutdownOutput();
        // 关流
        s.close();
    }
}
Socket class

实现客户端的套接字。

构造方法

Socket();

重要方法

connect();
发起连接。
getOutputStream();
获取输出流。
shutdownOutput();
通知服务器数据传输完毕。
shutdownInput();
通知客户端数据接受完毕。
getInputStream();
获取输入流。

2.服务器端(Server)

1.创建服务器端套接字(ServerSocket)对象,绑定端口。
2.接收连接,获取一个Socket对象。这一步会产生阻塞。
3.获取输入流,然后利用输入流来读取数据。
4.通知客户端数据已经读取完毕。
5.关流。

public class TCPServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建服务器端的套接字对象,绑定端口
        ServerSocket ss = new ServerSocket(8090);
        // 接收连接
        // 阻塞
        Socket s = ss.accept();
        System.out.println("连接成功~~~");
        // 获取输入流
        InputStream in = s.getInputStream();
        // 读取数据
        byte[] bs = new byte[1024];
        int len = -1;
        while ((len = in.read(bs)) != -1) {
            System.out.println(new String(bs, 0, len));
        }
        // 通知客户端数据已经读取完毕
        s.shutdownInput();
        // 关流
        ss.close();
    }
}
ServerSocket class

实现服务器端的套接字。

构造方法

ServerSocket();

重要方法

accept();
接收连接。

注意
receive/connect/accept/read/write这些方法会产生阻塞。

扩展

BIO

BlockingIO同步式阻塞式。

NIO

NewIO同步式非阻塞式IO,JDK1.4出现的。别名:NonBlockingIO。

AIO

AsynchronousIO异步式非阻塞式IO,JDK1.8出现的。

练习

传输文件。
将D:\\a目录作为服务器端存储目录。

public class FileClientDemo {
    public static void main(String[] args) throws IOException {
        // 创建Socket对象
        Socket s = new Socket();
        // 发起连接
        s.connect(new InetSocketAddress("localhost", 8090));
        // 获取输出流
        OutputStream out = s.getOutputStream();
        // 创建File对象代表要传输的文件
        File file = new File("F:\\java基础增强.txt");
        // 记录文件名
        byte[] name = file.getName().getBytes();        
        // 写出文件名的大小
        out.write(name.length);
        // 将文件名写出
        out.write(name);
        // 创建输入流读取文件
        FileInputStream in = new FileInputStream(file);
        // 读取数据
        byte[] data = new byte[1024];
        int len = -1;
        while ((len = in.read(data)) != -1) {
            // 将读取的内容写出
            out.write(data, 0, len);
        }
        // 通知服务器端数据已经全部写出
        s.shutdownOutput();
        // 关流
        s.close();
        in.close();
    }
}

public class FileServerDemo {
    public static void main(String[] args) throws IOException {
        // 创建ServerSocket对象
        ServerSocket ss = new ServerSocket(8090);
        // 接收连接
        Socket s = ss.accept();
        // 获取输入流
        InputStream in = s.getInputStream();        
        // 获取的是文件名的字节的个数
        int i = in.read();      
        // 读取文件名
        byte[] name = new byte[i];
        in.read(name);
        // 创建输出流
        FileOutputStream out = new FileOutputStream("E:\\" + new String(name));
        // 读取数据
        byte[] data = new byte[1024];
        int len = -1;
        while ((len = in.read(data)) != -1) {
            // 将读取的数据写出
            out.write(data, 0, len);
        }
        // 通知客户端数据已经读取完毕
        s.shutdownInput();

        // 关流
        ss.close();
        out.close();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值