Dart学习笔记(23):Socket套接字

发表于2018-07-04 13:31 阅读(34)

按照以往的经验,Socket可以简单的分为3类:
流套接字(SOCK_STREAM)、数据包套接字(SOCK_DGRAM)
以及原始套接字(SOCK_RAW)

本文地址:http://www.cndartlang.com/841.html

其中流套接字使用了TCP协议
因此保证了面向连接、可靠的数据传输
实现数据的无差错、无重复发送,并按顺序接收

数据包套接字使用UDP协议
它提供了一种无连接的服务
但不能保证数据传输的可靠性,且无法保证顺序

原始套接字允许对较低层次的协议进行访问
比如IP、ICMP、IGMP协议
可以用来接收TCP/IP栈不能处理的IP包
或发送自定义包头、自定义协议的IP包
网络监听技术很大程度依赖原始套接字

接着,出于安全性方面的需要
在上面3类套接字之后,安全套接字应运而生

安全套接字使用TLS和SSL协议
始终对服务器进行认证,可选择的对客户端进行认证
使得客户端和服务器之间的的通信不会被攻击者嗅探、篡改
(早些年出现过利用DNS劫持再利用SSLStrip来进行嗅探)

要通过Internet进行通信,至少需要一对套接字
一个运行在服务器、一个运行在客户端
因此套接字也可简单的分为服务器端套接字和客户端套接字

大概提了一下之后,Dart中套接字的使用又如何呢?

在Dart中套接字主要有以下类:
Socket、ServerSocket
RawSocket、RawServerSocket
RawDatagramSocket

而下面的安全套接字则是继承上述对应的套接字
并且使用TLS和SSL协议
SecureSocket、SecureServerSocket
RawSecureSocket、RawSecureServerSocket
API很简单,就不一一写示例了

使用套接字进行数据处理有两种基本模式:同步和异步
而在Dart中使用异步编程非常简单、方便!

1、流套接字

Socket采用TCP协议
并且实现了Stream数据流和IOSink缓冲池的接口
使得Socket可以同其他的Stream一起使用,示例如下

注:
ServerSocket.bind返回的是Future<ServerSocket>
因此可以用下面的方法遍历连接到自身的所有套接字
await for(Socket socket in serverSocket)

socket_server.dart

import 'dart:io';
import 'dart:convert';

main() async {
  //绑定本地localhost的4041端口
  var serverSocket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 4041);

  //遍历所有连接到服务器的套接字
  await for(var socket in serverSocket) {
    //数据流Stream操作:监听接收到的数据
    socket.transform(UTF8.decoder).listen((data) {
      print("接收到来自Client的数据:" + data);
      print("向Client发送数据:Client 你好!");
      //缓冲池IOSink操作:写入数据
  socket.add(UTF8.encode('Client 你好!'));
    });
  }
}

socket_client.dart

import 'dart:io';
import 'dart:convert';

main() async {
  //连接服务器的4041端口
  var socket = await Socket.connect(InternetAddress.LOOPBACK_IP_V4, 4041);

  print("向Server发送数据:Server 你好!");
  socket.add(UTF8.encode('Server 你好!'));
  socket.transform(UTF8.decoder).listen((data) {
    print("接收到来自Server的数据:" + data);
  });
}

运行结果:

客户端
向Server发送数据:Server 你好!
接收到来自Server的数据:Client 你好!

服务端
接收到来自Client的数据:Server 你好!
向Client发送数据:Client 你好!

2、数据包套接字

RawDatagramSocket采用UDP协议
由系统暴露原始数据的事件信号
也就是说
数据包套接字不能监听数据,而是监听事件
当事件为RawSocketEvent.READ的时候
才能通过receive函数接收数据

同时,与一般编程语言不同的是
Dart中数据包套接字必须绑定地址和接口

注:
流套接字和原始套接字可以通过remoteAddress和remotePort获取地址和端口
因此客户端不用绑定地址和端口,仅通过服务器地址和端口创建套接字

raw_datagram_socket_server.dart

import 'dart:io';
import 'dart:convert';

main() async {
  /**
   * reuseAddress参数默认为true
   * 允许多个进程同时监听、绑定同一个端口
   */
  var rawDgramSocket = RawDatagramSocket.bind(InternetAddress.LOOPBACK_IP_V4, 4041);

  rawDgramSocket.then((socket) {
    //监听套接字事件
    socket.listen((event) {
      if(event == RawSocketEvent.READ) {
        print(UTF8.decode(socket.receive().data));
        socket.send(UTF8.encode("已收到!"), InternetAddress.LOOPBACK_IP_V4, 4042);
      }
    });
  });
}

raw_datagram_socket_client.dart

import 'dart:io';
import 'dart:convert';

main() async {
  var rawDgramSocket = RawDatagramSocket.bind(InternetAddress.LOOPBACK_IP_V4, 4042);

  rawDgramSocket.then((socket) {
    socket.send(UTF8.encode("你好!"), InternetAddress.LOOPBACK_IP_V4, 4041);

    socket.listen((event) {
      if(event == RawSocketEvent.READ) {
        print(UTF8.decode(socket.receive().data));
      }
    });
  });
}

运行结果:

客户端
已收到!

服务端
你好!

3、原始套接字

前面说过,原始套接字可以对低层的数据协议进行访问
比如IP、ICMP、IGMP协议
或发送自定义包头、自定义协议的IP包

这里就不举聊天的例子了
下面的实例是获取、发送IP协议数据包

raw_server_socket.dart

import 'dart:io';
import 'dart:convert';

/**
* 获取、发送IP协议数据包
* 浏览器访问http://localhost:4043
*/
main() async {
  var rawServerSocket = await RawServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 4043);

  rawServerSocket.listen((socket) {
    socket.listen((event) {
      if(event == RawSocketEvent.READ) {
        print(UTF8.decode(socket.read()));
        socket.write(UTF8.encode("""
        <!DOCTYPE html>
        <html>
          <head>
            <meta http-equiv="content-type" content="text/html;charset=utf-8">
          </head>
          
          <body>
            <h1>RawServerSocket 服务器</h1>
          </body>
        </html>
        """));
      }
    });
  });
}

浏览器访问http://localhost:4043

运行结果:

GET / HTTP/1.1
Host: localhost:4043
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,ms;q=0.6,en;q=0.4
Cookie: fCSG_2132_ulastactivity=dd83Ralbi6qpMdA0DC%2BflA7TGD8%2BgdgFEQ33XqqYN7lWDaKLtnb4; fCSG_2132_lastcheckfeed=1%7C1444323716; fCSG_2132_nofavfid=1; fCSG_2132_smile=1D1; _ga=GA1.1.1753627046.1444410873