Socket 网络编程

Socket 网络编程

网络基础

网络通信

两台设备之间通过网络实现数据传输
网络通信是指通过网络将数据从一台设备传输到另一台设备
java 在java.net 包下提供了一系列的类或接口,来完成网络编程

网络

image.png

IP

image.png
image.png

IPV6 使用128位表示:16个字节,十六进制表示

IP地址分类

image.png

域名

为了方便记忆,将ip地址映射成域名,解决记ip的困难
比如我们经常访问百度,使用的域名 www.baidu.com 相比 ip 180.101.50.188 方便好记
image.png

端口号

用来标识计算机上运行的网络程序,网络服务都会监听一个端口,我们通过ip + 端口来访问程序。
端口范围 0~65535 ,其中 0~1024 已经被占用,比如常见的 ssh:22 ftp:21 smtp:25 http: 80

常见的网络程序端口
tomcat: 8080
mysql: 3306
oracle: 1521
sqlserver: 1433

网络协议

协议是数据的组织形式。通过协议将数据准确无误的发送给接收方,协议是共同遵守的规范
image.png

OSI模型
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
TCP/IP 模型
物理层+数据链路层、网络层(IP)、传输层 (TCP)、应用层

image.png

TCP 和 UDP

TCP协议:传输控制协议

  1. 面向连接的: 使用TCP协议前,须先建立连接
  2. 可靠的:三次握手建立连接,可靠的传输
  3. 数据量大:连接中可进行 ,大数据量的传输
  4. 效率低:传送结束后需释放已建立的连接 (四次挥手)

UDP:用户数据报协议

  1. 无连接:将数据,源、目的封装成数据包,不需要建立连接
  2. 少量数据:每个数据包的大小限制64kB
  3. 不可靠:因为是无连接的,所以不可靠
  4. 速度快:发送完数据不需要释放资源(不是面向连接的),速度快

InetAddress

InetAddress 类是 Java 中用于表示 IP 地址的类,它提供了一种在网络编程中获取和操作 IP 地址信息的方式。InetAddress 类的主要作用是用于域名解析和 IP 地址相关的操作。

以下是 InetAddress 类的一些常用方法和作用:

  1. getByName(String host) 这是 InetAddress 类的静态方法之一,用于通过主机名获取 InetAddress 对象。主机名可以是域名或 IP 地址。
InetAddress address = InetAddress.getByName("www.baidu.com");
  1. getLocalHost() 也是 InetAddress 类的静态方法,用于获取本地主机的 InetAddress 对象。
InetAddress localHost = InetAddress.getLocalHost();
  1. getHostAddress() 该方法返回 IP 地址的字符串表示形式。
String ipAddress = address.getHostAddress();
  1. getHostName() 该方法返回主机的规范化名称。如果无法解析主机名,则返回 IP 地址的字符串形式。
String hostName = address.getHostName();
  1. isReachable(int timeout) 该方法用于检查主机是否可达。它发送一个 ICMP 报文(ping)来测试主机的可达性,并在指定的超时时间内等待响应。如果主机可达,返回 true;否则返回 false
boolean isReachable = address.isReachable(3000); // 3秒超时
  1. getAllByName(String host) 该方法返回与指定主机名关联的所有 IP 地址。通常,一个主机名可能对应多个 IP 地址。
InetAddress[] addresses = InetAddress.getAllByName("www.example.com");
  1. toString() 返回 InetAddress 对象的字符串表示形式,通常是 IP 地址的字符串。
String addressStr = address.toString();
  1. equals(Object obj)** 和 hashCode():*用于比较两个 InetAddress 对象是否相等以及获取哈希码。

InetAddress 类主要用于网络编程中,例如在网络通信、域名解析、服务发现等方面。通过它,你可以获取主机的 IP 地址、判断主机的可达性、进行网络连接等操作。

Socket

socket 是一种应用于网络编程的接口,它将负责的底层通信网络封装使开发者更容易的编写网络应用,完成在不同计算机上的应用程序可以通过网络进行数据交换和通信

以下是关于Socket的一些重要概念和解释:

  1. 套接字(Socket): Socket是一种用于网络通信的编程接口,它位于应用层和传输层之间,允许应用程序通过网络发送和接收数据。Socket提供了一组API,用于建立、连接、发送和接收数据。
  2. 网络套接字(Socket): 网络套接字是通过IP地址和端口号来唯一标识的,它用于在网络上定位和识别特定的应用程序或服务。一个套接字通常由两个端点组成,一个是服务器端套接字,另一个是客户端套接字。
  3. 服务器套接字(Server Socket): 服务器套接字是用于等待客户端连接请求的套接字。一旦客户端请求连接,服务器套接字会接受连接并创建一个新的套接字,用于与客户端进行通信。
  4. 客户端套接字(Client Socket): 客户端套接字是用于与服务器建立连接并进行通信的套接字。客户端套接字通过连接到服务器套接字来建立通信通道。
  5. 协议: Socket通常使用特定的网络协议来进行通信,例如TCP(传输控制协议)或UDP(用户数据报协议)。这些协议定义了数据在网络上的传输方式和规则。
  6. 阻塞和非阻塞: Socket可以以阻塞或非阻塞方式运行。在阻塞模式下,Socket调用会一直等待,直到操作完成。在非阻塞模式下,Socket调用会立即返回,不管操作是否完成,允许程序继续执行其他任务。
  7. 通信模型: Socket可以用于实现不同的通信模型,包括客户端-服务器模型和对等通信模型。在客户端-服务器模型中,一个服务器等待客户端连接并提供服务。在对等通信模型中,两个计算机之间可以直接通信,不需要中央服务器。

netstat 指令的使用

  • 使用 neatest -an | 查看当前主机网络情况,包括端口监听情况和网络连接情况
  • netstat -an | more 可以分页显示

image.png
说明:
LISTEN 表示端口在监听 (windosw 上是 LISTENING)
如果有一个外部程序(客户端)连接到该端口,Foreign Address 就会显示一条连接信息
ctrl + c 退出

客户端也是有端口的

客户端连接到服务端后,其实客户端也是有端口的,这个端口是TCP/IP 随机分配的
可以在客户端上传一个大文件,来查看随机分配给客户端的端口

基于TCP的网络编程

案例1 client -> server 简单通信

需求:
使用字节流的方式,客户端向 本地端口9527 的服务服务端发送 hello server , 服务端监听 端口9527 接收客户端信息之后结束通信

代码实现:
SocketServer01.java

/**
 * @Description: 服务端
 * @Author : wy
 * @Date : Created 2023/9/10 5:25 PM
 */
public class SocketServer01 {
    public static void main(String[] args) throws IOException {

        //1. 创建serverSocket 对象,监听 9527 端口
        // 注意: 如果监听的端口被占用了,服务端将会启动失败
        ServerSocket serverSocket = new ServerSocket(9527);

        System.out.println("服务端已启动,监听 9527 端口");
        //2. 获取socket 对象,如果未收到客户端的消息,将会阻塞程序,接收到后继续执行下面逻辑
        Socket socket = serverSocket.accept();

        //3. 通过socket 获取该连接的 输入流对象
        InputStream inputStream = socket.getInputStream();

        byte[] buf = new byte[1024];
        int  readLen = 0;

        //4. 读取数据,输出到控制台
        while ( (readLen = inputStream.read(buf)) != -1){
            //根据读到的实际长度输出
            System.out.println(new String(buf,0,readLen));
        }

        //5. 释放资源
        inputStream.close();
        socket.close();
        serverSocket.close();

        System.out.println("服务端已关闭...");

    }


}

SocketClient01.java

/**
 * @Description: 客户端
 * @Author : wy
 * @Date : Created 2023/9/10 5:39 PM
 */
public class SocketClient01 {

    public static void main(String[] args) throws IOException {

        //1. 通过 本地ip + 9527 端口 建立 socket 连接
        Socket socket = new Socket(InetAddress.getLocalHost(),9527);

        //2. 建立连接后根据得倒的 socket 对象获取一个输出流对象
        OutputStream outputStream = socket.getOutputStream();

        //3. 通过输出流write ,发送数据
        outputStream.write("hello server".getBytes());

        //4. 关闭输出流、socket 释放连接资源
        outputStream.close();
        socket.close();

        System.out.println("客户端已退出...");

    }
}

案例2 client -> server -> client

需求:
在案例1的基础上,使用字节流的方式 ,客户端向 本地端口9527 的服务服务端发送 hello server , 服务端监听 端口9527 接收客户端信息之后给 发送消息的客户端返回 hello client ,客户端接受到服务端返回的消息后结束通信。

代码实现:
SocketServer02.java

/**
 * @Description: 服务端
 * @Author : wy
 * @Date : Created 2023/9/10 5:25 PM
 */
public class SocketServer02 {
    public static void main(String[] args) throws IOException {

        //1. 创建serverSocket 对象,监听 9527 端口
        // 注意: 如果监听的端口被占用了,服务端将会启动失败
        ServerSocket serverSocket = new ServerSocket(9527);

        System.out.println("服务端已启动,监听 9527 端口");
        //2. 获取socket 对象,如果未收到客户端的消息,将会阻塞程序,接收到后继续执行下面逻辑
        Socket socket = serverSocket.accept();

        //3. 通过socket 获取该连接的 输入流对象
        InputStream inputStream = socket.getInputStream();

        byte[] buf = new byte[1024];
        int  readLen = 0;

        //4. 读取数据,输出到控制台
        while ( (readLen = inputStream.read(buf)) != -1){
            //根据读到的实际长度输出
            System.out.println(new String(buf,0,readLen));
        }

        //5. 向服务端返回消息
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello client".getBytes());

        //6. 释放资源
        inputStream.close();
        socket.close();
        serverSocket.close();

        System.out.println("服务端已关闭...");

    }


}

SocketClient02.java

/**
 * @Description: 客户端
 * @Author : wy
 * @Date : Created 2023/9/10 5:39 PM
 */
public class SocketClient02 {

    public static void main(String[] args) throws IOException {

        //1. 通过 本地ip + 9527 端口 建立 socket 连接
        Socket socket = new Socket(InetAddress.getLocalHost(),9527);

        //2. 建立连接后根据得倒的 socket 对象获取一个输出流对象
        OutputStream outputStream = socket.getOutputStream();

        //3. 通过输出流write ,发送数据
        outputStream.write("hello server".getBytes());

        //4. 添加 结束标志
        socket.shutdownOutput();

        //5.接收服务端收到消息后的返回信息
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1){
            System.out.println(new String(buf,0,readLen));
        }

        //5. 关闭输出流、socket 释放连接资源
        outputStream.close();
        inputStream.close();
        socket.close();

        System.out.println("客户端已退出...");

    }
}

案例3 使用字符流的方式

在案例2的基础上改造下使用字符输入、输出流来发送接收数据
具体代码:
SocketServer03.java

    /**
     * @Description: 服务端
     * @Author : wy
     * @Date : Created 2023/9/10 5:25 PM
     */
    public class SocketServer03 {

        public static void main(String[] args) throws IOException {

            //1. 创建serverSocket 对象,监听 9527 端口
            // 注意: 如果监听的端口被占用了,服务端将会启动失败
            ServerSocket serverSocket = new ServerSocket(9527);

            System.out.println("服务端已启动,监听 9527 端口");
            //2. 获取socket 对象,如果未收到客户端的消息,将会阻塞程序,接收到后继续执行下面逻辑
            Socket socket = serverSocket.accept();

            //3. 通过socket 获取该连接的 输入流对象
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String line = null;
            if ((line = reader.readLine()) != null) {
                System.out.println(line);
            }


            //5. 向服务端返回消息
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            writer.write("hello client");
            writer.flush();
            socket.shutdownOutput();

            //6. 释放资源
            writer.close();
            reader.close();
            socket.close();
            serverSocket.close();

            System.out.println("服务端已关闭...");

        }


    }

SocketClient03.java

/**
 * @Description: 客户端
 * @Author : wy
 * @Date : Created 2023/9/10 5:39 PM
 */
public class SocketClient03 {

    public static void main(String[] args) throws IOException {

        //1. 通过 本地ip + 9527 端口 建立 socket 连接
        Socket socket = new Socket(InetAddress.getLocalHost(),9527);


        //2. 使用字符输出流的write ,发送数据
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write("hello server");
        writer.flush();
        socket.shutdownOutput();

        //4.接收服务端收到消息后的返回信息
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = null;
        while ((line = reader.readLine()) != null){
            System.out.println(line);
        }


        //5. 关闭输出流、socket 释放连接资源
        writer.close();
        reader.close();
        socket.close();
        System.out.println("客户端已退出...");

    }
}

注意⚠️: 使用字符输出流,时要切记调用 fulsh( ) 将数据刷新,并设置结束标志。这样接收方才能正确判断数据发送结束,不在接收数据。不然服务端和客户端会阻塞。

案例4 网络上传文件

需求:

  • 编写一个服务端,和客户端
  • 服务端监听 9527 端口
  • 客户端连接服务端,从磁盘获取一张图片发送给服务端
  • 服务端接收到 客户端发送的图片后,保存到 src 下,发送 “收到图片” 再退出
  • 客户端接收到服务端发送的 “收到图片” 再退出

工具类:
StreamUtil.java
streamToByteArray(InputStream is) 将输入流转换为 byte[], 既可以把文件内容读入到 byte[]
streamToString(InputStream is) 将输入InputStream 中的数据 转换成 String

/**
 * @Description: 字节流转换工具类
 * @Author : wy
 * @Date : Created 2023/9/11 4:43 PM
 */
public class StreamUtil {

    /**
     * 将输入流转换为 byte[], 既可以把文件内容读入到 byte[]
     * @param is 字节输入流
     * @return byte[]
     * @throws IOException
     */
    public static byte[] streamToByteArray(InputStream is) throws IOException {
        //创建输出流对象
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        
        //字节数组
        byte[] b = new byte[1024];
        int len;
        while ((len = is.read(b)) != -1) {
            //循环读取,把读取的数据写到 输出流中
            bos.write(b,0,len);
        }
        //将输出流转为 字节数组
        byte[] array = bos.toByteArray();
        bos.close();
        return array;
    }

    /**
     *  InputStream 转换成 String
     * @param is 字节输入流
     * @return
     * @throws IOException
     */
    public static String streamToString(InputStream is) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder stringBuilder = new StringBuilder();
        String line;

        while((line = reader.readLine()) != null) {
            stringBuilder.append(line).append("\r\n");
        }

        return stringBuilder.toString();
    }

}

代码实现:

FileUploadService.java

/**
 * @Description: 文件上传服务端
 * @Author : wy
 * @Date : Created 2023/9/11 6:53 PM
 */
public class FileUploadService {

    public static void main(String[] args) throws IOException {
        //监听本地 9527 端口
        ServerSocket serverSocket = new ServerSocket(9527);

        System.out.println("服务端在 9527 端口启动");
        Socket socket = serverSocket.accept();
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());

        byte[] bytes = StreamUtil.streamToByteArray(bis);

        //将到得的字节数组写入到 执行文件
        String destPath = "src/motorcycle2.jpg";
        BufferedOutputStream bos = new BufferedOutputStream(Files.newOutputStream(Paths.get(destPath)));
        bos.write(bytes);
        bos.close();

        //向 客户端回复
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write("收到图片");
        writer.flush();
        socket.shutdownOutput();


        //关闭其他资源
        writer.close();
        bis.close();
        socket.close();
        serverSocket.close();

        System.out.println("服务器结束...");
    }
}

FileUploadClient.java

/**
 * @Description: 文件上传客户端
 * @Author : wy
 * @Date : Created 2023/9/11 6:53 PM
 */
public class FileUploadClient {

    public static void main(String[] args) throws IOException {

        //创建连接
        Socket socket = new Socket(InetAddress.getLocalHost(),9527);

        //从本地磁盘获取上传文案
        String filePath = "/Users/yang/Desktop/imgs/motorcycle.jpg";
        BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(Paths.get(filePath)));

        //使用 工具类 将字节流转为 byte[]
        byte[] bytes = StreamUtil.streamToByteArray(bis);


        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        //将文件内容写入 输出流中
        bos.write(bytes);
        bis.close();
        //设置结束标志
        socket.shutdownOutput();

        //获取 服务端 收到图片后的返回信息
        String res = StreamUtil.streamToString(socket.getInputStream());
        System.out.println(res);

        //关闭资源
        bos.close();
        socket.close();

        System.out.println("客户端结束...");

    }

}

案例5 网络下载文件

需求:
客户端向服务端发送要下载的文件名,服务端接收到后根据文件名返回给客户端文件内容,如果根据文件名没有找到返回一个默认的文件

代码实现:
FileDownloadServer.java

/**
 * @Description: 文件下载服务端
 * @Author : wy
 * @Date : Created 2023/9/14 7:10 PM
 */
public class FileDownloadServer {

    public static void main(String[] args) throws IOException {

        //监听 9527 端口
        ServerSocket serverSocket = new ServerSocket(9527);
        System.out.println("服务端启动,监听 9527 端口,等待客户端连接...");

        //创建 socket 等待客户端连接
        Socket socket = serverSocket.accept();
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());

        int len = 0;
        byte[] buf = new byte[1024];

        StringBuilder downloadFileName = new StringBuilder();
        while ((len =  bis.read(buf)) != -1) {
            downloadFileName.append(new String(buf, 0, len));
        }
        System.out.println("收到来自客户端的下载请求,下载 " + downloadFileName);
        String filePath = "";
        if ("motorcycle2".contentEquals(downloadFileName)){
            filePath = "src/motorcycle2.jpg";
        }else {
            filePath = "src/dog.jpeg";
        }

        //读取文件,得到字节数组
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
        byte[] bytes = StreamUtil.streamToByteArray(bufferedInputStream);

        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(bytes);
        socket.shutdownOutput();
        outputStream.close();

        //关闭流
        bufferedInputStream.close();
        bis.close();
        socket.close();
        serverSocket.close();
        System.out.println("服务端关闭");
    }
}

FileDownloadClient.java

/**
 * @Description: 文件下载客户端
 * @Author : wy
 * @Date : Created 2023/9/14 7:09 PM
 */
public class FileDownloadClient {

    public static void main(String[] args) throws IOException {
        


        //连接服务端
        Socket socket = new Socket(InetAddress.getLocalHost(),9527);
        System.out.println("客户端已启动...");

        //读取用户输入的文件名
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入下载文件名:");
        String fileName = scanner.next();

        //获取输出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(fileName.getBytes());
        //设置输入结束标志
        socket.shutdownOutput();

        //获取输入流,得倒文件数据的字节数组
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] bytes = StreamUtil.streamToByteArray(bis);

        //将文件保存到指定路径
        String destPath = "/Users/yang/" + fileName + ".jpg";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destPath));
        bos.write(bytes);

        //关闭资源
        bos.close();
        bis.close();
        outputStream.close();
        socket.close();
        System.out.println("文件下载完成...");

    }
}

基于UDP的网络编程

通过 udp 方式实现 socket 网络编程

基本介绍

  • DatagramSocket 和 DatagramPacket 类实现了基于UDP协议的网络编程
  • UDP 数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP 数据报一定能够安全的送到目的地,也不确定什么时候可以抵达
  • DatagramPacket 对象封装了UDP 数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
  • UDP协议中每个数据报都给出了完整的地址信息,所以无须建立发送方和接收放的连接

UDP编程的基本流程

  • 核心的两个类 DatagramSocket 和 DatagramPacket
  • 建立发送端、接收端
  • 建立数据包
  • 调用DatagramSocket 的发送、接收方法
  • 关闭 DatagramSocket

image.png

image.png

简单案例

需求:
接收方 A ,收到发送 B 发来的消息后 给 B 回复消息后退出,B 接收到 A 的回复消息后也退出
代码实现:
UDPReceiverA.java

/**
 * @Description: udp接收端
 * @Author : wy
 * @Date : Created 2023/9/12 11:38 PM
 */
public class UDPReceiverA {

    public static void main(String[] args) throws IOException {
        //创建一个 DatagramSocket 准备在 9527 端口接收数据
        DatagramSocket socket = new DatagramSocket(9527);

        //构建一个DatagramPacket 来接收数据
        byte[] buf = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);

        // 调用接收方法将数据 填充到 packet
        // 当有数据发送到 监听端口 9527 上时就会接收数据,如果没有则阻塞等待
        System.out.println("准备接收发送方的消息 ..");
        socket.receive(datagramPacket);

        //把packet 拆包,取出数据
        int length = datagramPacket.getLength(); //接收到的数据长度
        byte[] data = datagramPacket.getData();  //接收到的数据
        String msg = new String(data,0,length);
        System.out.println(msg);

        //回复信息给 B 端
        byte[] sendMsg = "月亮不睡我不睡,我是秃头小宝贝~".getBytes();
        DatagramPacket sendPacket = new DatagramPacket(sendMsg,sendMsg.length, InetAddress.getByName("127.0.0.1"),9998);
        socket.send(sendPacket);
        socket.close();
        System.out.println("A 端退出");
    }
}

UDPSenderB.java

public class UDPSenderB {

    public static void main(String[] args) throws IOException {
        // 创建 DatagramSocket 对象,准备在 9998 端口接收数据
        DatagramSocket socket = new DatagramSocket(9998);

        byte[] sendMsg = "晚上早点睡!".getBytes();
        //packet 封装消息,具体给出消息 、消息长度、接收端的 ip 、接收端的 port
        DatagramPacket packet = new DatagramPacket(sendMsg,sendMsg.length, InetAddress.getByName("127.0.0.1"),9527);
        //发送消息
        socket.send(packet);

        //接收 消息
        byte[] buf = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(buf,buf.length);
        socket.receive(receivePacket);
        //从receivePacket中取出消息
        int len = receivePacket.getLength();
        byte[] res = receivePacket.getData();
        String receiveMsg = new String(res,0,len);
        System.out.println(receiveMsg);
        socket.close();
        System.out.println("B 端退出");

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值