从理论+实战的角度分析Http,Socket 与 WebSocket 三者的联系

Http

Http (超文本传输协议)是应用层协议,是一个简单的请求-响应协议,基于 TCP 进行连接,它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应头以 ASCII 形式给出;而消息内容则具有一个类似 MIME的格式。Http就是基于这样的模型进行请求响应的。

Http 只能由客户端发起消息,由服务端接收,服务端无法主动向客户端发送消息。Http连接是短连接,即客户端向服务端发送一次请求,服务端响应后连接就会断掉。

Tcp:传输层协议

Ip:网络层协议

Socket

概念

Socket(套接字)是对网络中不同主机的应用进程之间进行双向通信的端点的抽象,一个套接字就是网络上进程通信的一端,Socket由 IP+Port 组合而成,提供了应用层进程利用网络协议交换数据的机制。

套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议进行交互的接口。

Socket 是对 TCP/IP 协议的封装,Socket本身不是协议,而是一个调用接口(API),通过 Socket ,我们才能使用 TCP/IP 协议,所以说是Socket是应用程序与网路协议进行交互的接口。

Socket连接与 Http不同,是长连接,理论上客户端与服务端一旦建立连接,则不会主动断开,但是由于各种环境因素可能会断开连接,比如,服务端/客户端 宕机,网络故障,或者二者之间长时间没有数据的传输,那么为了维持连续的连接需要发送心跳消息,具体心跳消息格式是开发者自己定义的。

由于 Socket 是对TCP/IP的调用API,所以搞定 Socket必须要搞定 TCP

客户端与服务端进行连接时要经过TCP三次握手,三次握手成功后才开始传输数据,理想状态下,TCP连接一旦建立,在通讯双方中的任何一方主动断开连接之前TCP连接都会一直保持下去。当然,除了 TCP,Socket 也可以使用 UDP 协议来传输数据。

实例

Socket基于TCP
// 服务端
public class BIOServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
        InputStream in = null;
        OutputStream out = null;
        try {
            serverSocket = new ServerSocket(8000);
            System.out.println("服务器启动成功,监听端口为8000,等待客户端连接......");
            while (true) {
                // 阻塞
                socket = serverSocket.accept();
                System.out.println("客户端连接成功,客户信息为:" + socket.getRemoteSocketAddress());
                in = socket.getInputStream();
                byte[] bytes = new byte[1024];
                int len = 0;
                while ((len = in.read(bytes)) > 0) {
                    System.out.println(new String(bytes, 0, len));
                }
                out = socket.getOutputStream();
                out.write("hello!".getBytes());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 客户端
public class BIOClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8000);
        OutputStream outputStream = socket.getOutputStream();
        System.out.println("tcp连接成功 \n 请输入:");
        while (true) {
            byte[] bytes = new Scanner(System.in).nextLine().getBytes();
            outputStream.write(bytes);
            System.out.println("tcp协议的socket发送成功");
            // 刷新缓冲区
            outputStream.flush();
        }
    }
}

服务端先初始化Socket,包括绑定监听端口,进行监听,调用accept()阻塞,等待客户端连接。在此时如果有客户端初始化了一个Socket,连接服务端Socket成功后,这时客户端与服务端的连接就建立了。客户端发送数据请求,服务端接收并处理请求,服务端把响应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

Socket基于UDP
// 服务端
public class Server {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(8888);
        byte[] bytes = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length);
        System.out.println("等待UDP协议传输数据");
        datagramSocket.receive(datagramPacket);
        System.out.println("接收消息:" + new String(bytes, 0, datagramPacket.getLength()));
        datagramSocket.close();
        System.out.println("UDP协议socket接收成功");
    }
}

// 客户端
public class Client {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(2468);
        byte[] bytes = "UDP协议的Socket请求!".getBytes();
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
        DatagramPacket datagramPacket = new DatagramPacket(bytes, bytes.length,inetSocketAddress);
        datagramSocket.send(datagramPacket);
        System.out.println("UDP协议的Socket发送成功");
        datagramSocket.close();
    }
}

UDP协议是用户数据报协议,也用于网络数据传输,虽然UDP协议是一种不太可靠的协议,但有时在需要较快的接收数据并且可以容忍错误的情况下,UDP就会有更大的优势。客户端只管发送,至于服务端能不能接收到不负责。

Socket通信的常用类

类名基于用途
SocketTCP同时工作与客户端与服务端,所有方法通用,此类有三个作用:校验包信息、发起连接、操作数据流数据
ServerSocketTCP表示为服务端,主要作用是绑定并监听服务端端口,为每个建立连接的客户端“映射”一个Socket对象,具体数据操作都是这个socket对象完成的,ServerSocket只关注如何和客户端建立连接
DatagramSocketUDP用于表示发送和接收数据报包的套接字
DatagramPacketUDP用于表示数据报包,数据报包用来实现无连接包投递服务
InetAddressIP+Port代表互联网协议地址,提供了两个静态方法来获取实例
InetSocketAddressIP+Port在Socket通信时靠 InetSocketAddress来实现IP+Port的创建,不依赖任何协议

WebSocket

WebSocket 是一种在单个 TCP连接上进行全双工通信的协议,WebSocket使得客户端与服务端的数据交换变得简单,允许服务端主动向客户端通信。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

概念

很多网站为了实现推送技术,都是客户端采用轮询机制访问服务端,即每隔一定时间由客户端主动发起请求,服务端再返回最新的数据。这种传统模式有很大弊端,客户端需要不断的向服务端发送请求,然而 Http 请求可能包含较长的头部,其中真正有效的数据可能只是其中很少一部分,显然这样会浪费很多带宽资源。

WebSocket使用了自定义的协议,未加密的连接不再是 http://,而是 ws://;加密的连接不再是 https://,而是wss://

使用自定义协议而非 Http协议的好处是,能在客户端和服务端之间发送非常少量的数据,而不必担心 Http那样的请求头开销,由于传输的数据包很小,因此 WebSocket 协议非常适合移动应用。

WebSocket的优势包括:

  • 较少的控制开销
  • 更强的实时性
  • 保存连接状态
  • 更好的二进制支持
  • 可以支持扩展
  • 更好的压缩效果

实例

本实例采用 Vue+SpringBoot 实现前后端通信。

客户端
export default {
  data() {
    return {
      websocket: null,
      data: {
        code: 0,
        item: "传输的数据"
      }
    };
  },
  methods: {
    onConfirm() {
      //需要传输的数据
      this.websocketsend(JSON.stringify(this.data));
    },
    initWebSocket() {
      if (typeof WebSocket === "undefined") {
        console.log("浏览器不支持websocket");
      } else {
        // 初始化weosocket
        this.websocket = new WebSocket(
          "ws://localhost:8089/websocket/feiyangyang"
        );
        this.websocket.onmessage = this.websocketonmessage;
        this.websocket.onerror = this.websocketonerror;
        this.websocket.onopen = this.websocketonopen;
        this.websocket.onclose = this.websocketclose;
      }
    },
    websocketonopen() {
      // 连接建立之后执行send方法发送数据
      this.websocketsend(JSON.stringify(this.data));
    },
    websocketonerror() {
      console.log("WebSocket连接失败");
    },
    websocketonmessage(e) {
      // 数据接收
      console.log("数据接收" + e.data);
    },
    websocketsend(Data) {
      // 数据发送
      this.websocket.send(Data);
    },
    websocketclose(e) {
      // 关闭
      console.log("已关闭连接", e);
    }
  },
  created() {
    console.log("created");
    this.initWebSocket();
  },

  destroyed() {
    this.websocket.close(); // 离开路由之后断开websocket连接
  }
};
服务端
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat7-websocket</artifactId>
    <version>7.0.109</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
// 注入 ServerEndpointExporter bean
@Component
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

@ServerEndpoint("/websocket/{username}")
@Service
public class MyWebSocket {
    private static int onlineCount = 0;
    private static final Map<String, MyWebSocket> clients = new ConcurrentHashMap<>();
    private Session session;
    private String username;

    @OnOpen
    public void onOpen(@PathParam("username") String username, Session session) throws IOException {
        this.username = username;
        this.session = session;

        addOnlineCount();
        clients.put(username, this);
        System.out.println("已连接" + username);
    }

    @OnClose
    public void onClose() throws IOException {
        clients.remove(username);
        subOnlineCount();
    }

    @OnMessage
    public void onMessage(String message) throws IOException {
        System.out.println("message:" + message);
        // 发送数据给服务端
        sendMessageAll(JSON.toJSONString(message));
    }

    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    public void sendMessageTo(String message, String to) throws IOException {
        for (MyWebSocket item : clients.values()) {
            if (item.username.equals(to)) {
                item.session.getAsyncRemote().sendText(message);
            }
        }
    }

    public void sendMessageAll(String message) throws IOException {
        for (MyWebSocket item : clients.values()) {
            item.session.getAsyncRemote().sendText(message);
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        MyWebSocket.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        MyWebSocket.onlineCount--;
    }

    public static synchronized Map<String, MyWebSocket> getClients() {
        return clients;
    }
}

服务端打印数据

已连接:feiyangyang
message:{"code":0,"item":"传输的数据"}

总结

TCP/IP协议栈主要分为四层,如下图所示。

[图片来自网络]

Http是短连接,即客户端向服务端发送一次请求,服务端响应后连接就会断开等待下次连接。

适用场景:

  • 公司OA服务
  • 互联网访问
  • 电商
  • 办公网站等

Socket是所谓的长连接,理论上客户端与服务端建立连接后将不会主动断掉。

适用场景:

  • 网络游戏
  • 银行持续交互
  • 直播
  • 在线视频等

WebSocket 是 html5 规范中的一部分,借鉴了 Socket的思想,为 web 应用程序客户端和服务端之间提供了一种全双工通信机制。

适用场景:

  • 社交聊天
  • 弹幕
  • 协同编辑文档
  • 股票基金实时报价
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值