• 这可能是最容易入门的socket教程了


    前言:

      如今,网络编程已然成为了一个后端开发工程师需要具备的核心技能之一。因此,该博客力求提供最简单、通俗的描述方式,来描绘网络编程中常见的知识点,同时附带代码示例,后期会加上具体的抓包分析,实际项目、框架案例,希望可以和大家共同探索网络世界。

    什么是socket?

      在计算机通讯领域,socket被翻译为“套接字”
      但是这种翻译方法其实是不优雅的,因为从字面意思来看,我们并不能很直白的理解socket的具体用途。如果可以的话,我更希望它被翻译成“通讯插头”
      我们可以这样想象一下,现在有一根“网线”,一头连接着客户端,一头连接着服务器,客户端和服务器的数据通过这跟“网线”来进行传输,而socket的作用,其实就相当于连接网线的插头。在通讯之前,客户端和服务器都要建立一个socket。

    socket在网络编程中的位置

    socket是基于TCP、UDP协议的,如果你熟悉TCP/IP协议。你就会知道,TCP、UDP属于传输层。由于我们主要是学习Socket,所以这里只是简单介绍一下这两种协议:
      1. TCP是面向连接的、UDP是面向无连接的,所谓面向连接,指的就是通讯双方会维护一组状态,保证连接的可靠性。
      2. TCP的数据包结构相对复杂,而UDP则相对简单。无论是TCP还是UDP,他们的包头上都应该有端口号和目标端口号,TCP的包头上有序号(顺序),确认序号(不丢包)、窗口大小(流量控制 & 拥塞控制)、状态码(FIN ACK SYN)等。TCP和UDP的包头如下图。
      3. TCP数据传输方式是基于数据流的,而UDP则是基于数据报。

    udp包头

     tcp包头

    (我们将在其他博文中对TCP和UDP进行具体讲解)
     一个包在网络中传输,一定不会只有上层没下层。TCP、UDP处于传输层。TCP/IP协议从上到下分别为:应用层、传输层、网络层、链路层、物理层。所以传输层的数据包想要在网络中传输,除了端口号,我们还需要网络层中的ip地址。socket的创建需要提供的信息就是 ip+端口。

    如何使用socket:

    TCP是基于数据流的,所以如果是基于TCP的Socket,我们就需要建立连接,然后获取数据流,再进行通讯。
    每对连接需要建立一组socket,具体步骤如下:

    代码(TCP):

    服务器:

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * 服务器.
     *
     * @author jialin.li
     * @date 2019-12-10 19:45
     */
    public class TcpServer {
        public static void main(String[] args) throws IOException {
            int port = 8099;
            ServerSocket connectionSocket = new ServerSocket(port);
            // 监听端口,accept为阻塞方法
            System.out.println("Get socket successfully, wait for request...");
            Socket communicationSocket = connectionSocket.accept();
            // 获取输入流,读取数据
            InputStream inputStream = communicationSocket.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String message;
            while ((message = bufferedReader.readLine()) != null) {
                System.out.printf("get message from client : %s",message);
            }
            communicationSocket.shutdownInput();
            // 获取输出流,返回结果
            OutputStream outputStream = communicationSocket.getOutputStream();
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
            BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
            bufferedWriter.write("I got your message and the communication is over.");
            bufferedWriter.flush();
            communicationSocket.shutdownOutput();
            // 关闭资源
            bufferedWriter.close();
            bufferedReader.close();
        }
    }

    客户端:

    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    
    /**
     * 客户端.
     *
     * @author jialin.li
     * @date 2019-12-10 22:30
     */
    public class TcpClient {
        public static void main(String[] args) throws IOException {
            // 指定ip 端口,创建socket
            String host = "127.0.0.1";
            int port = 8099;
            Socket communicationSocket = new Socket(host, port);
            // 获取输出流,写入数据
            OutputStream outputStream = communicationSocket.getOutputStream();
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
            BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
            bufferedWriter.write("hello world");
            bufferedWriter.flush();
            communicationSocket.shutdownOutput();
            // 获取输入流,读取服务器返回信息
            InputStream inputStream = communicationSocket.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String message;
            while ((message = bufferedReader.readLine()) != null) {
                System.out.printf("get message from server : %s", message);
            }
            communicationSocket.shutdownInput();
            // 关闭资源
            bufferedWriter.close();
            bufferedReader.close();
        }
    }

    执行结果:

    server:

    Get socket successfully, wait for request...
    get message from client : hello world

    client:

    get message from server : I got your message and the communication is over.

    outputStream.close与shutdownOutput的区别:

      这里只对outputStream进行分析,inputStream与之相同。

      可以看出,上述代码,我们在执行完输入/输出动作之后,会调用一个shutdownInput/shutdownOutput方法,这个方法有什么作用呢?可不可以用close方法替代?

      首先,我们阅读jdk中关于该方法的doc注释:

    Disables the output stream for this socket.
    For a TCP socket, any previously written data will be sent
    followed by TCP's normal connection termination sequence.

    If you write to a socket output stream after invoking
    shutdownOutput() on the socket, the stream will throw
    an IOException.

      大体意思是说,该方法会禁用socket的输出流,对于基于TCP协议的Socket,任何在方法执行之前发送数据,都可以被正常发送,如果在这个方法执行后发送数据,就会抛出一个IO异常。通过该方法关闭流,Socket连接不会收到影响,但是如果我们直接使用close方法关闭流,那么Socket连接也会随之关闭。接下来我们来测试一下,在Server中用inputStream.close来代替socket.shutdownInput方法。

    结果是由于inputStream.close提前关闭了socket,导致服务器在输出数据的时,socket.getOutputStream方法抛出异常:java.net.SocketException: Socket is closed 

    为什么我们每次调用BufferedWrite的write方法,都要调用flush方法:

      这个问题不属于网络编程的范畴,但却是我们在写socket程序时,很容易犯的一个错误。bufferedWrite是字符缓存流,它的原理其实很简单,在内存中设置一个缓存区,将原本逐个发送的字符缓存起来,批量发送(有很多框架也采用了这种思想,比如kafka的批量发送),flush方法是手动的将我们缓存区中的数据刷出。缓存区中的数据,将会在流关闭之前,进行flush。但是由于我们在发送数据之后,调用了shutdownOutput方法,导致最后close的时候,没办法将缓存区中的数据flush,因此会抛出一个写入失败的异常:java.net.SocketException: Broken pipe (Write failed)。

    UDP是基于数据报的,因此不需要每对连接都建立一组socket,而是只要有一个socket,就能与多个客户端通讯,所以只需要通过创建数据报,然后通过socket发送即可,具体步骤如下:

    代码(UDP):

    服务器:

    import java.net.DatagramPacket;
    import java.io.IOException;
    import java.net.DatagramSocket;
    
    /**
     * 服务器.
     *
     * @author jialin.li
     * @date 2019-12-10 19:45
     */
    public class UdpServer {
        public static void main(String[] args) throws IOException {
            // 监听端口,阻塞方法
            int port = 8099;
            DatagramSocket socket = new DatagramSocket(port);
            // 创建数据报,用于接收客户端发送的数据
            byte[] data = new byte[1024];
            DatagramPacket packet = new DatagramPacket(data, data.length);
            // 接收客户端发送的数据
            System.out.println("Get socket successfully, wait for request...");
            socket.receive(packet);
            String message = new String(data, 0, packet.getLength());
            System.out.printf("get message from client : %s", message);
    
            // 向客户端发送数据
            byte[] data2 = "I got your message and the communication is over.".getBytes();
            DatagramPacket packet2 = new DatagramPacket(data2, data2.length, packet.getAddress(), packet.getPort());
            socket.send(packet2);
            socket.close();
        }
    }

    客户端:

    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    
    /**
     * 客户端.
     *
     * @author jialin.li
     * @date 2019-12-11 11:32
     */
    public class UdpClient {
        public static void main(String[] args) throws IOException {
            // 封装数据报:ip、端口、数据
            InetAddress ip = InetAddress.getByName("127.0.0.1");
            int port = 8099;
            byte[] data = "hello world".getBytes();
            DatagramPacket packet = new DatagramPacket(data, data.length, ip, port);
            // 创建socket,发送数据报
            DatagramSocket socket = new DatagramSocket();
            socket.send(packet);
    
            // 读取服务器返回信息
            byte[] data2 = new byte[1024];
            DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
            socket.receive(packet2);
            // 读取数据
            String message = new String(data2, 0, packet2.getLength());
            System.out.printf("get message from server : %s", message);
            //关闭资源
            socket.close();
        }
    }

    执行结果:

    server:

    Get socket successfully, wait for request...
    get message from client : hello world

    client:

    get message from server : I got your message and the communication is over.

  • 相关阅读:
    bzoj3576 [Hnoi2014]江南乐
    codeforces 438D The Child and Sequence
    bzoj1926 [Sdoi2010]粟粟的书架
    bzoj1185 [HNOI2007]最小矩形覆盖
    bzoj1069 [SCOI2007]最大土地面积
    bzoj4569 [Scoi2016]萌萌哒
    西西爆难积分
    概率问题-距离的期望
    各类平均与数列极限
    三角恒等式
  • 原文地址:https://www.cnblogs.com/nedulee/p/12026342.html
Copyright © 2020-2023  润新知