• 【Java】网络编程--Socket与TCP网络通信编程


    写在前面

     Hello,大家好,我是黄小黄。经过了上一节的学习,我们初步对网络编程相关的概念有了一定的认识,下面我们将尝试使用Java代码进行网络编程。
     本节中,你将学习到Socket是什么,以及使用TCP网络通信编程完成双向通信和上传文件的功能。上一节的内容补充请戳:从Java代码到网络编程,三次握手又该如何理解
    在这里插入图片描述

      在进行本节内容前,我们需要先了解一个Java的常用类:InetAddress
    在后面,我们经常会用到。InetAddress的常用方法示例见如下代码:

    import java.net.InetAddress;
     
    public class Main {
     
    	public static void main(String[] args) throws Exception {
     
    		InetAddress ia1 = InetAddress.getLocalHost();// 获取本机信息、IP地址
    		System.out.println(ia1);
     
    		InetAddress ia2 = InetAddress.getByName("LAPTOP-TR72VVGT");// 根据本机信息获取IP地址
    		System.out.println(ia2);
     
    		InetAddress ia3 = InetAddress.getByName("www.baidu.com");// 根据域名获取IP地址
    		System.out.println(ia3);
     
    		String str1 = ia3.getHostAddress();// 已知对应信息,获取IP地址
    		System.out.println(str1);
     
    		String str2 = ia3.getHostName();// 已知对应信息,获取域名
    		System.out.println(str2);
     
    	}
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    正文开始!!!

    💁 个人主页:黄小黄的博客主页
    ❤️ 支持我:👍 点赞 🌷 收藏 🤘关注
    🎏 格言:All miracles start from sometime somewhere, make it right now.
    本文来自专栏:JavaSE从入门到精通
    在这里插入图片描述



    1 Socket

    🐰 基本介绍:

    • 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准;
    • 通信的两端都要有Socket,是两台机器间通信的端点;
    • 网络通信其实就是Socket间的通信;
    • Socket允许程序把网络连接当作一个流,数据可以在两个Socket间通过IO传输;
    • 一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端

    Socket该如何理解?
    在这里插入图片描述


    2 TCP网络通信编程

    2.1 TCP字节流编程

    因资源有限,笔者操作均在同一台计算机完成

    题目摘自:韩顺平Java_TCP字节流编程1、2

    2.1.1 案例:客户端发送数据,服务端接收并显示

    编写一个服务器端,和一个客户端。服务端在 9999端口监听,客户端连接到服务端,并发送"hello,server",然后退出。服务端接收到客户端发送的信息后,输出该信息,并退出。

    服务端代码:

    import java.io.IOException;
    import java.io.InputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 服务端
     */
    public class SocketTCPServer1 {
        public static void main(String[] args) throws IOException {
            //1.在本机的9999端口监听,等待连接,要求本机没有其他服务在监听9999
            ServerSocket serverSocket = new ServerSocket(9999);
            System.out.println("服务端,在9999端口监听,等待连接..");
            //2.当没有客户端连接9999端口时,程序会阻塞,等待连接
            //如果有客户端连接,则会返回Socket对象,程序继续
            Socket socket = serverSocket.accept();
            System.out.println("服务端 socket=" + socket.getClass());
            //3.通过socket.getInputStream() 读取客户端写入数据通道的数据,并显示
            InputStream inputStream = socket.getInputStream();
            //4.IO读取
            byte[] buf = new byte[1024];
            int readLen = 0;
            while ((readLen = inputStream.read(buf)) != -1){
                System.out.println(new String(buf, 0, readLen));
            }
            //5.关闭资源
            inputStream.close();
            socket.close();
            serverSocket.close();
            System.out.println("服务端退出......");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    客户端代码:

    import java.io.IOException;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 客户端,发送"hello,server"给服务端
     */
    public class SockerTCPClient1 {
        public static void main(String[] args) throws IOException {
            //1.连接服务器(ip 端口)
            //连接本机的9999端口,如果连接成功,返回Socket对象
            Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
            System.out.println("客户端 socket返回=" + socket.getClass());
            //2.连接上后,生成Socket,通过输出流,写入数据到 数据通道
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("hello,server".getBytes());
            //3.关闭流对象
            outputStream.close();
            socket.close();
            System.out.println("客户端退出......");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    结果如下:
    在这里插入图片描述
    在这里插入图片描述

    2.1.2 案例进阶:双向通信

    1.编写一个服务端,和一个客户端;
    2.服务端在9999端口监听;
    3.客户端连接到服务端,发送"hello,server",并接收服务端回发的"hello,cilent",再退出;
    4.服务器端接收到客户端发送的信息,输出,并发送"hello, cilent",再退出

    该案例为案例1的进阶,可以直接在上述代码中进行修改。

    服务端代码:

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 服务端
     */
    public class SocketTCPServer1 {
        public static void main(String[] args) throws IOException {
            //1.在本机的9999端口监听,等待连接,要求本机没有其他服务在监听9999
            ServerSocket serverSocket = new ServerSocket(9999);
            System.out.println("服务端,在9999端口监听,等待连接..");
            //2.当没有客户端连接9999端口时,程序会阻塞,等待连接
            //如果有客户端连接,则会返回Socket对象,程序继续
            Socket socket = serverSocket.accept();
            System.out.println("服务端 socket=" + socket.getClass());
            //3.通过socket.getInputStream() 读取客户端写入数据通道的数据,并显示
            InputStream inputStream = socket.getInputStream();
            //4.IO读取
            byte[] buf = new byte[1024];
            int readLen = 0;
            while ((readLen = inputStream.read(buf)) != -1){
                System.out.println(new String(buf, 0, readLen));
            }
            socket.shutdownInput();
            //5.写入数据到数据通道
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("hello, client".getBytes());
            socket.shutdownOutput();
            //6.关闭资源
            inputStream.close();
            outputStream.close();
            socket.close();
            serverSocket.close();
            System.out.println("服务端退出......");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    客户端代码:

    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 客户端,发送"hello,server"给服务端
     */
    public class SockerTCPClient1 {
        public static void main(String[] args) throws IOException {
            //1.连接服务器(ip 端口)
            //连接本机的9999端口,如果连接成功,返回Socket对象
            Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
            System.out.println("客户端 socket返回=" + socket.getClass());
            //2.连接上后,生成Socket,通过输出流,写入数据到 数据通道
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("hello,server".getBytes());
            socket.shutdownOutput();
            //3.读取服务端数据
            InputStream inputStream = socket.getInputStream();
            byte[] buf = new byte[1024];
            int readLine = 0;
            while ((readLine = inputStream.read(buf)) != -1){
                System.out.println(new String(buf, 0, readLine));
            }
            //4.关闭流对象
            inputStream.close();
            outputStream.close();
            socket.close();
            System.out.println("客户端退出......");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    注意使用 socket.shutdownOutput() 设置结束标记
    类比一下生活中两个人聊天,对方如果不停止讲话,或者告诉你说完了,你始终无法确定对方是否已经说完了话,因此,你会一直处于听的状态(如果礼貌的前提下!)
    网络通信也一样,负责监听的服务端,并不知道客户端是否已经说完了话,因此,客户端在发送消息完毕时,应该给服务端发送一个结束标记!只有这样,服务端才知道该何时停止读取(Input)状态,进行下一步操作。

    结果:
    在这里插入图片描述
    在这里插入图片描述

    2.2 TCP字符流编程

    需求同案例进阶:双向通信,不同点是,这次我们尝试使用字符流来实现!

    服务端代码:

    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 服务端
     */
    public class SocketTCPServer1 {
        public static void main(String[] args) throws IOException {
            //1.在本机的9999端口监听,等待连接,要求本机没有其他服务在监听9999
            ServerSocket serverSocket = new ServerSocket(9999);
            System.out.println("服务端,在9999端口监听,等待连接..");
            //2.当没有客户端连接9999端口时,程序会阻塞,等待连接
            //如果有客户端连接,则会返回Socket对象,程序继续
            Socket socket = serverSocket.accept();
            System.out.println("服务端 socket=" + socket.getClass());
            //3.通过socket.getInputStream() 读取客户端写入数据通道的数据,并显示
            InputStream inputStream = socket.getInputStream();
            //4.IO读取,使用字符流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s = bufferedReader.readLine();
            System.out.println(s);
            //5.写入数据到数据通道
            OutputStream outputStream = socket.getOutputStream();
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("hello,client");
            bufferedWriter.newLine();
            bufferedWriter.flush();
            //6.关闭资源
            bufferedReader.close();
            bufferedWriter.close();
            socket.close();
            serverSocket.close();
            System.out.println("服务端退出......");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    客户端代码:

    import java.io.*;
    import java.net.InetAddress;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 客户端,发送"hello,server"给服务端
     */
    public class SockerTCPClient1 {
        public static void main(String[] args) throws IOException {
            //1.连接服务器(ip 端口)
            //连接本机的9999端口,如果连接成功,返回Socket对象
            Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
            System.out.println("客户端 socket返回=" + socket.getClass());
            //2.连接上后,生成Socket,通过输出流,写入数据到 数据通道
            OutputStream outputStream = socket.getOutputStream();
            //字符流需要包装和转化
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
            bufferedWriter.write("hello,server");
            //插入一个换行符表示写入内容结束
            bufferedWriter.newLine();
            //需要刷新
            bufferedWriter.flush();
            //3.读取服务端数据
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s = bufferedReader.readLine();
            System.out.println(s);
            //4.关闭流对象
            bufferedReader.close();
            bufferedWriter.close();
            socket.close();
            System.out.println("客户端退出......");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.3 网络上传文件

    需求如下:将客户端的图片,通过网络上传到服务器,服务器回复消息

    1.编写一个服务端,一个客户端;
    2.服务端在8888端口监听;
    3.客户端连接到服务端,发送一张图片;
    4.服务端接收到图片后将客户端发送的图片保存到src目录,并发送“收到图片”后退出;
    5.客户端在接收到服务端“收到图片”后退出;
    6.要求使用StreamUtils,java(自己封装的)

    🐱 思路分析:

    1. 先将磁盘上的文件读入客户端,存储在字节数组中(图片是二进制);
    2. 将字节数组(文件数据)通过Socket获取的输出流输出到数据通道上;
    3. 在服务端,需要通过Socket获取输入流存储到字节数组中;
    4. 然后,在通过输出流将字节数组的数据保存到磁盘上;
    5. 通过Socket获得输出流,将“收到图片”打入数据通道,结束;
    6. 客户端通过输入流,读取数据,接收到“收到图片”提示,显示信息,结束。

    StreamUtils.java

    import java.io.ByteArrayOutputStream;
    import java.io.InputStream;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     */
    public class StreamUtils {
        /**
         * 将输入流转化成 byte[],即可以将文件读入到字节数组
         * @param is
         * @return
         * @throws Exception
         */
        public static byte[] streamToByteArray(InputStream is) throws Exception{
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len=is.read(bytes))!=-1){
                bos.write(bytes, 0, len);
            }
            byte[] array = bos.toByteArray();
            bos.close();
            return array;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    服务端:

    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 服务端
     */
    public class TCPFileUploadServer {
        public static void main(String[] args) throws Exception {
            //1.服务端在本机8888端口监听
            ServerSocket serverSocket = new ServerSocket(8888);
            System.out.println("服务端在8888端口监听");
            //2.等待连接
            Socket socket = serverSocket.accept();
            //3.读取客户端的数据
            BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
            byte[] bytes = StreamUtils.streamToByteArray(bis);
            //4.将数组写入到指定路径
            String filePath = "D:\\Ideaproject2021\\JavaSE\\src\\upload\\zzf2.jpg";
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
            bos.write(bytes);
            bos.close();
            //5.向客户端发送,收到图片
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            writer.write("收到图片");
            writer.newLine();
            writer.flush();
            socket.shutdownOutput();
            //关闭其他资源
            writer.close();
            bis.close();
            socket.close();
            serverSocket.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    客户端:

    import java.io.*;
    import java.net.InetAddress;
    import java.net.Socket;
    
    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 客户端
     */
    public class TCPFileUploadClient {
        public static void main(String[] args) throws Exception {
            //1.客户端连接服务端,得到socket对象
            Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
            //2.创建读取磁盘文件的输入流
            String filePath = "C:\\Users\\26510\\Desktop\\csdn\\zzf.jpg";
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
            //3.把文件写入到字节数组中
            byte[] bytes = StreamUtils.streamToByteArray(bis);
            //4.将字节数组的数据发送到服务端
            BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
            bos.write(bytes);
            bis.close();
            socket.shutdownOutput();
            //5.获取信息
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String s = reader.readLine();
            System.out.println(s);
            //关闭流
            reader.close();
            bos.close();
            socket.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行后,图片成功上传到服务端:
    在这里插入图片描述


    写在最后

    🌟以上便是本文的全部内容啦,后续内容将会持续免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
    如果有问题,欢迎私信或者评论区!
    在这里插入图片描述

    共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”
    在这里插入图片描述

  • 相关阅读:
    leetcode 387. First Unique Character in a String(字符串中第一个独一无二的字母)
    Spring Boot 日志的使用
    Java项目中jar war pom包的区别
    C++ 实现基于时序公平的读写锁
    聚观早报 | 抖音上线 Mac 客户端;理想 ONE 将不会再生产
    AMD Ryzen 5 7600X 6-core/12-thread Raphael CPU【搬运外媒VedioCardz报道(手工翻译)】
    AI助力校园安全:EasyCVR视频智能技术在校园欺凌中的应用
    拓展培训开场白集锦
    爱普生机器人修改IP
    Python + Django4 搭建个人博客(七): Admin后台管理系统
  • 原文地址:https://blog.csdn.net/m0_60353039/article/details/126870131