一篇文章了解Socket通信原理

Socket

本篇尝试尽可能的介绍清除Socket

什么是Socket

Socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,
它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,
一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。

简单的说Socket就是计算机之间使用一种约定俗成的方式进行数据沟通的接口。

Socket在哪里使用

我们简单的知道了Socket的作用,现在有一个问题,什么时候我们会使用它。解决这个疑问我们就需要简单了解下
TCP/IP。

TCP/IP

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

在这里Socket是应用层与TCP/IP协议族通信的中间软件的抽象层。提供了接口服务。TCP/IP协议族中Socket所在的位置

应用层-用户的应用
Socket抽象
TCP
UDP
IP协议-网络层
链路层

Socket的使用流程

整个Socket的使用分为两部分
服务端:

  1. 服务端初始化Socket
  2. 服务端绑定端口,对端口进行监听
  3. 使用acceot进行阻塞,等待连接
  4. 接收客户端连接
  5. 接收客户端消息
  6. 返回回应数据
  7. 关闭连接

客户端

  1. 初始化Socket
  2. 连接服务器IP、端口
  3. 连接成功则和服务端建立连接
  4. 客户端发送请求到服务端
  5. 接收服务端返回的数据
  6. 关闭连接
TCP客户端
客户端socket
TCP服务端
服务端socket
创建连接
请求服务端数据
获得服务端信息
客户端关闭连接
接收客户端数据
绑定端口
等待客户连接
返回服务端数据
服务端关闭连接

网络通讯

网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。
这样我们使用“IP + 协议 + 端口”就可以在网络中标识唯一的进程了。然后我们使用socket接口实现网络进程之间的通信

socket起源于Unix,Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。

Socket能实现什么

socket 的典型应用就是 Web 服务器和浏览器:

  • 浏览器获取用户输入的 URL,向服务器发起请求,
  • 服务器分析接收到的 URL,将对应的网页内容返回给浏览器,
  • 浏览器再经过解析和渲染,就将文字、图片、视频等元素呈现给用户。

JAVA中Socket如何使用

Socket的简单使用

创建服务端

代码


    public static void main(String[] args) throws IOException {
        baseSocketServer();
    }
    
    
    /**
     * 基础是Socket案例
     */
    public static void baseSocketServer() {
        int port = 8000;
        try (ServerSocket socket = new ServerSocket(port);
             // server将一直等待连接的到来
             Socket accept = socket.accept();
             // 获取Socket的输入流,用来获得客户端的数据
             InputStream inputStream = accept.getInputStream()) {
            // 解析收到的消息
            String message = getMessage(inputStream);
            System.out.println("get message from client: " + message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 对输入流进行解析获得返回的内容
     * @param inputStream
     * @return
     * @throws IOException
     */
    private static String getMessage(InputStream inputStream) throws IOException {
        // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
        byte[] bytes = new byte[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        while ((len = inputStream.read(bytes)) != -1) {
            //注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
            sb.append(new String(bytes, 0, len,"UTF-8"));
        }
        return sb.toString();
    }
    

服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务

端口的绑定(此处源码解读)

服务器绑定端口的时候其实绑定了本地的IP和端口

    public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
        setImpl();
        if (port < 0 || port > 0xFFFF)
            throw new IllegalArgumentException(
                       "Port value out of range: " + port);
        if (backlog < 1)
          backlog = 50;
        try {
            bind(new InetSocketAddress(bindAddr, port), backlog);
        } catch(SecurityException e) {
            close();
            throw e;
        } catch(IOException e) {
            close();
            throw e;
        }
    }

在使用bind方法的时候获取InetSocketAddressHolder地址使用了默认本地IP

    public InetSocketAddress(InetAddress addr, int port) {
        holder = new InetSocketAddressHolder(
                        null,
                        addr == null ? InetAddress.anyLocalAddress() : addr,
                        checkPort(port));
    }


创建客户端


    public static void main(String[] args) throws Exception {
        baseSocketClient();
    }

    /**
     * 基础的Socket客户端
     * @throws IOException
     */
    public static void baseSocketClient() throws IOException {
        // 监听地址和端口
        SocketAddress address = new InetSocketAddress("127.0.0.1", 8000);
        try (Socket socket = new Socket()) {
            socket.connect(address);
            
            try (// 获取输入输出流,读写数据(与服务端数据读写操作相同)
                 OutputStream out = socket.getOutputStream();
                 // 获取输入流
                 InputStream in = socket.getInputStream()){
                
                // 写数据
                out.write("hello, server".getBytes());
                // 关闭此处输出流
                socket.shutdownOutput();

                // 解析服务器返回的内容
                String message = getMessage(in);
                // 获得返回的内容
                System.out.println("Server return:" + message); 
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 对输入流进行解析获得返回的内容
     * @param inputStream
     * @return
     * @throws IOException
     */
    private static String getMessage(InputStream inputStream) throws IOException {
        // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
        byte[] bytes = new byte[1024];
        int len;
        StringBuilder sb = new StringBuilder();
        while ((len = inputStream.read(bytes)) != -1) {
            //注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
            sb.append(new String(bytes, 0, len,"UTF-8"));
        }
        return sb.toString();
    }

这样我们先启动服务端内容,然后启动客户端就可以实现一次数据通讯。

socket连接建立

三次握手(three times handshake;three-way handshake)所谓的“三次握手”即对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。

三次握手分别是:

  1. 客户端发送syn包到服务器,进入SYN_SENT状态,等待服务端返回
  2. 服务端收到syn包,确认客户的SYN,同时自己发送一个SYN,即SYN+ACK,此时服务器进入SYN_RECV
  3. 客户端收到服务端的SYN+ACK,向服务器发送确认包ACK此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态
客户端 服务端 SYN J 客户端发送请求后状 态为:SYN-SE NT-等待服务器确 SYN K,ACK j+1 服务端发送请求状态 为:SYN-REC EIVED-等待确认 ACK K+1 客户端发送请求后状 态为:ESTABL ISHED-完成连接 服务端接收请求状态 为:ESTABLI SHED-完成连接 客户端 服务端

A[客户端-CLOSED]–>B
B[客户端-SYN-SENT-等待服务器确认]–>C
C[客户端-ESTABLISHED-完成连接]

E[服务端-CLOSED]–>F
F[服务端-LISTEN]–>G
G[服务端-SYN-RECEIVED-等待确认]–>H
B–>|SYN=1 SEQ=X ACK=0|F

H[服务端-ESTABLISHED]
G–>|SYN=1 SEQ=Y ACK=X+1|C
C–>|SEQ=X+1 ACK=Y+1|H

socket连接的关闭

四次挥手,别名连接终止协议。其性质为终止协议

连接的关闭需要四次挥手,操作流程分别是:

  1. 客户端发送一个FIN,主动关闭服务器数据传输。
  2. 服务器端收到FIN,返回一个ACK,确认序号为收到的序号加1。
  3. 服务器关闭客户端的连接,发送一个FIN给客户端。
  4. 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。

TCP 用三步来建立一个链接,而终止连接时需要四步。原因在于被动关闭链接一方需要关闭处理时间,因此 ACK 和 FIN 不能同时发给主动关闭一方。

客户端 服务端 FIN M 发送报文后状态为: FIN_WAIT_ 1-发送了FIN报 文但是还没收到回复 ACK M+1 服务端发送报文状 态为:CLOSE _WAIT-等待关闭 发送报文后状态为 :FIN_WAI T_2-发送了F IN报文收到回复 FIN N 服务端发送报文状态 为:LAST_AC K最后等待ACK报 ACK N 客户端发送报文后状态为:TIME_WAIT- 收到了对方的FIN报文,并发送出了ACK报文 ,就等2MSL后即可回到CLOSED可用状态 服务端接收报文 状态为:CLO SED-初始状态 客户端 服务端

连接的状态

状态描述
CLOSED初始状态
LISTEN服务端处于监听状态,可以接受连接
SYN_RCVD握手状态:接受了SYN报文,等待客户端ACK报文
SYN_SENT握手状态:客户端执行连接操作,发送SYN报文,等待服务端消息
ESTABLISHED连接已经
FIN_WAIT_1客户端发送了FIN报文,等待服务端回复
FIN_WAIT_2客户端发送了FIN报文,服务端已经回复
TIME_WAIT收到了对方的FIN报文,并发送出了ACK报文,等2MSL后返回CLOSED可用状态
CLOSE_WAIT收到对方的FIN报文,此次返回ACK报文给对方,然后等待数据传输完毕后进行关闭之间的状态
LAST_ACK发送确认关闭的FIN报文,等待对方返回ACK
CLOSING如果双方尝试同时close一个Socket,同时发送FIN报文,会出现此状态
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值