【JavaEE】Socket简单体验, TCP和UDP

作者: 吾非言 | 来源:发表于2017-08-29 18:15 被阅读228次

    作者:邹峰立,微博:zrunker,邮箱:zrunker@yahoo.com,微信公众号:书客创作,个人平台:www.ibooker.cc

    本文选自书客创作平台第30篇文章。阅读原文

    书客创作

    对于程序员来说,在实际生产过程中,使用最多的莫过于Http和Socket通信两中。Socket相对于Http来说速度快,效率高,所以广受青睐。

    说到Socket很让人联想到"套接字"、"长连接"、"TCP/UDP"、"通信"。没错,Socket是套接字的意思,Socket能够实现长连接,所以常常人们所使用的一些即时通信的软件大多数是通过Socket来实现。TCP和UDP是两种形式,TCP是面向连接的,较安全。UDP是无连接的,速度较快。

    基本概念理解

    1、InetAddress用于标识网络上硬件资源。

    // 获取本机的InetAddress实例
    InetAddress inetAddress = InetAddress.getLocalHost();
    System.out.println("计算机名:" + inetAddress.getHostName());
    System.out.println("IP地址:" + inetAddress.getHostAddress());
    // 获取字节数组形式的IP地址
    byte[] bytes = inetAddress.getAddress();
    System.out.println("字节数组形式的IP地址:" + Arrays.toString(bytes));
    // 直接输出InetAddress对象
    System.out.println(inetAddress);
            
    // 根据主机名获取InetAddress实例
    // InetAddress address = InetAddress.getByName("SC-201702092057");
    // 根据IP地址获取InetAddress实例
    InetAddress address = InetAddress.getByName("192.168.155.1");
    System.out.println("计算机名:" + address.getHostName());
    System.out.println("IP地址:" + address.getHostAddress());
    

    2、URL统一资源定位符,通过资源可以读取和写入网络上的数据。

    // 创建URL实例
    URL baidu = new URL("http://www.baidu.com");
    // 根据已存在URL可以创建一个新的URL
    URL url = new URL(baidu, "/index.html?username=tom#test");// ?后面表示参数,#后面表示锚点
    System.out.println("协议:" + url.getProtocol());
    System.out.println("主机:" + url.getHost());
    // 如果未指定端口号,则使用默认端口号,此时url.getPort()获取的返回值为-1
    System.out.println("端口:" + url.getPort());
    System.out.println("文件路径:" + url.getPath());
    System.out.println("文件名:" + url.getFile());
    System.out.println("相对路径:" + url.getRef());
    System.out.println("查询字符串:" + url.getQuery());
            
    /**
     * 使用URL获取网络内容
     */
    // 通过URL的openStream方法获取URL对象所表示的资源的字节输入流
    InputStream is = baidu.openStream();
    // 将字节输入流转换成字符输入流(字节流与字符流之间的桥梁)
    InputStreamReader isr = new InputStreamReader(is, "utf8");
    // 为字符输入流添加缓冲(从字符输入流中读取文本)
    BufferedReader br = new BufferedReader(isr);
    String data = null;
    while ((data = br.readLine()) != null) {
        System.out.println("当前行数据:" + data);
    }
    

    Socket实现基于TCP通信

    Socket通信分为服务端和客户端,在Java语言中,要实现基于TCP的通信需要通过ServerSocket(服务端)和Socket(客户端)。因为TCP在通信过程中采用字节流传输,所以数据的取出和传递都要通过字节流来实现。

    1、服务端搭建

    服务端的搭建基本上可以分为5步。

    (1)、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口。

    ServerSocket serverSocket = new ServerSocket(2500);
    

    对于端口号,系统默认取值范围是065535,一般认为01023为系统端口号,所以这里采用>1023的端口号。

    (2)、用accept()方法开始监听,等待客户端的连接。

    Socket socket = serverSocket.accept();
    

    注意accept方法会使程序处于阻塞状态,所以一般是放在子线程中完成。而接受到的socket就是用于服务端和客户端进行通信的socket。

    (3)、获取输入流,并读取客户端信息

    InputStream is = socket.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);// 转换成字符输入流
    BufferedReader br = new BufferedReader(isr);// 字符输入流缓冲
    String info=null;
    while((info=br.readLine())!=null){// 循环读取客户端的信息
        System.out.println("*******客户端信息:" + info);
    }
    socket.shutdownInput();// 关闭输入流
    

    获取客户端数据需要通过获取socket的输入流,这里使用到了字节流->字符流的过程,最后打印出字符。

    (4)、获取输出流,响应客户端

    OutputStream os = socket.getOutputStream();
    PrintWriter pw = new PrintWriter(os);// 将输出流包装为打印流
    pw.write("我是服务端,你好");
    pw.flush();// 刷新缓存,将缓存输出
    socket.shutdownOutput();// 关闭输出流
    

    响应客户端需要通过获取socket输出流,这里是字节流->打印流。当然也可以直接进行输出。

    (5)、关闭资源,最后将以上资源全部关闭即可。

    pw.close();
    os.close();
    br.close();
    isr.close();
    is.close();
    socket.close();
    serverSocket.close();
    
    

    到这里一个简单的服务端就构建完成了,上面的过程仅仅是用来说来,使用Socket如何构建一个服务端,实际上ServerSocket在接收链接的过程需要在子线程中完成。

    优化代码:

    // 1、创建一个服务器端Socket,即ServerSocket,指定绑定的端口,并监听此端口
    // (0~65535,使用1023之后的端口,0~1023一般为系统默认端口)
    ServerSocket serverSocket = new ServerSocket(2500);
    Socket socket = null;
    // 记录客户端的数量
    int count = 0;
    System.out.println("*****服务端已开启******");
    // 循环监听等待客户端的连接
    while (true) {
        // 调用accept()方法开始监听,等待客户端的连接
        socket = serverSocket.accept();
        // 创建一个新的线程
        TcpServerThread tcpServerThread = new TcpServerThread(socket);
        tcpServerThread.setPriority(4);// 设置线程优先级,[0,10],默认5
        // 启动线程
        tcpServerThread.start();
        count++;// 统计客户端的数量
        System.out.println("客户端的数量:" + count);
        InetAddress address = socket.getInetAddress();
        System.out.println("当前客户端的IP:" + address.getHostAddress());
    }
    

    而对于socket的处理,也要放在一个子线程中完成。

    public class TcpServerThread extends Thread {
        // 和本线程相关的socket
        private Socket socket = null;
    
        public TcpServerThread(Socket socket) {
            this.socket = socket;
        }
    
        // 线程执行的操作,响应客户端的请求
        @Override
        public void run() {
            super.run();
            if (socket != null) {
                InputStream is = null;
                InputStreamReader isr = null;
                BufferedReader br = null;
                OutputStream os = null;
                PrintWriter pw = null;
                try {
                    // 获取输入流,并读取客户端信息
                    is = socket.getInputStream();
                    isr = new InputStreamReader(is);// 转换成字符输入流
                    br = new BufferedReader(isr);// 字符输入流缓冲
                    String info = null;
                    while ((info = br.readLine()) != null) {// 循环读取客户端的信息
                        System.out.println("*******客户端信息:" + info);
                    }
                    socket.shutdownInput();// 关闭输入流
    
                    // 获取输出流,相应客户端
                    os = socket.getOutputStream();
                    pw = new PrintWriter(os);// 将输出流包装为打印流
                    pw.write("我是服务端,你好");
                    pw.flush();// 刷新缓存,将缓存输出
                    socket.shutdownOutput();// 关闭输出流
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    // 关闭资源
                    try {
                        if (pw != null)
                            pw.close();
                        if (os != null)
                            os.close();
                        if (br != null)
                            br.close();
                        if (isr != null)
                            isr.close();
                        if (is != null)
                            is.close();
                        if (socket != null)
                            socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    2、客户端搭建

    客户端的搭建只需要4步

    (1)、创建客户端Socket,指定服务端地址和端口

    Socket socket = new Socket("localhost", 2500);
    

    这里的服务端地址指的是服务端ip地址,端口是服务端设置的端口号。

    (2)、创建输出流,向服务端发送消息

    OutputStream os = socket.getOutputStream();
    PrintWriter pw = new PrintWriter(os);// 将输出流包装为打印流
    pw.write("我是客户端,你好啊");
    pw.flush();// 刷新缓存
    //pw.close();// 不能关闭输出流,会导致socket也关闭
    socket.shutdownOutput();// 关闭输出流
    

    注意一点,输出数据之后不要将流关闭,否则会导致socket也关闭。

    (3)、获取输入流,并读取服务端信息

    InputStream is = socket.getInputStream();
    InputStreamReader isr = new InputStreamReader(is);// 转换成字符输入流
    BufferedReader br = new BufferedReader(isr);// 字符输入流缓冲
    String info=null;
    while((info=br.readLine()) != null){// 循环读取客户端的信息
        System.out.println("*******服务端信息:" + info);
    }
    socket.shutdownInput();// 关闭输入流
    

    获取服务端数据需要通过获取socket的输入流,这里使用到了字节流->字符流的过程,最后打印出字符。

    (4)、关闭资源,最后将以上资源全部关闭即可。

    br.close();
    isr.close();
    is.close();
    pw.close();
    os.close();
    socket.close();
    

    Socket实现基于UDP通信

    UDP通信时无连接的,传输过程中是通过数据包来完成,所以要想实现UDP通信,需要使用到Java中的DatagramSocket和DatagramPacket。DatagramSocket是用来建立连接,而DatagramPacket是用来传递数据。

    1、服务端搭建

    服务端搭建分为接收和响应客户端两个过程。

    首先实现接受客户端发送的数据。

    (1)、创建服务端DatagramSocket,指定端口

    DatagramSocket datagramSocket = new DatagramSocket(2501);
    

    同样要设置端口号,端口号取>1023并且<65535的整数。

    (2)、创建数据报用来接收客户端传递过来的数据

    byte[] data = new byte[1024];// 创建字节数组,指定接受数据包大小
    DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
    

    这里接受数据为数据报,所以要采用DatagramPacket类来实现数据的接收。

    (3)、接受客户端发送的数据,receive方法用于接收链接,最后的方式也是放于子线程完成这一过程

    datagramSocket.receive(datagramPacket);// 此方法在接收到数据报之前会处于阻塞
    

    (4)、读取数据

    String info = new String(data, 0, datagramPacket.getLength());
    System.out.println("****客户端数据*****" + info);
    

    其次是响应客户端

    (1)、定义客户端地址,端口号,数据

    InetAddress inetAddress = datagramPacket.getAddress();
    int port = datagramPacket.getPort();
    byte[] data2 = "我是服务端,你好".getBytes();
    

    这里就采用了InetAddress来获取客户端的相关信息,例如客户端ip和相关端口号。

    (2)、创建数据报,包含发送信息

    DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length, inetAddress, port);
    

    此处的DatagramPacket构造方式与上面的构造方法不同,此处还要知道客户端的ip才能准确的发送数据。

    (3)、向客户端发送数据报,通过send方法发送数据。

    datagramSocket.send(datagramPacket2);
    

    最后关闭资源

    datagramSocket.close();
    

    2、客户端搭建

    其实客户端的构建与服务端的构建是一个相似的过程,一个逆向的过程。

    向服务端发送数据。

    // 1、定义服务端地址,端口号,数据
    InetAddress inetAddress = InetAddress.getByName("localhost");
    int port = 2501;
    byte[] data = "我是客户端,你好".getBytes();
    // 2、创建数据报,包含发送信息
    DatagramPacket datagramPacket = new DatagramPacket(data, data.length, inetAddress, port);
    // 3、创建DataGramSocket对象
    DatagramSocket datagramSocket = new DatagramSocket();
    // 4、向服务端发送数据报
    datagramSocket.send(datagramPacket);
    

    接受服务端响应的数据

    // 1、创建数据报用来接收服务端传递过来的数据
    byte[] data2 = new byte[1024];// 创建字节数组,指定接受数据包大小
    DatagramPacket datagramPacket2 = new DatagramPacket(data2, data2.length);
    // 2、接受服务端发送的数据
    datagramSocket.receive(datagramPacket2);// 此方法在接收到数据报之前会处于阻塞
    // 2、读取数据
    String info = new String(data2, 0, datagramPacket2.getLength());
    System.out.println("****服务端数据*****" + info);
    

    关闭资源

    datagramSocket.close();
    

    Github地址
    阅读原文


    微信公众号:书客创作

    相关文章

      网友评论

        本文标题:【JavaEE】Socket简单体验, TCP和UDP

        本文链接:https://www.haomeiwen.com/subject/lecadxtx.html