• PLAY2.6-SCALA(九) WebSockets


    WebSockets是一种支持全双工通信的套接字。现代的html5通过js api使得浏览器天生支持webSocket。但是Websockets在移动端以及服务器之间的通信也非常有用,在这些情况下可以复用一个已经存在的TCP连接。

    1.处理WebSockets

    一般Play通过action来处理http请求,但是WebSockets是完全不同的,没法使用action来处理。

    Play处理WebSockets的机制是建立在Akka Streams之上的。一个WebSockets被抽象为Flow,接收的信息被添加到flow中,flow产生的信息将发送到客户端中。

    注意在概念上,flow可以被视为一个接收某些信息,对信息进行一些处理再将信息传输出去的实体,没有理由为什么必须如此,flow的输入和输出可能是完全断开的. Akka stream为了这个目的提供了一个构造器,Flow.fromSinkAndSource。并且在处理WebSockets时,输入与输出往往是不连接的。

    Play在WebSocket中提供了一些工厂方法用来构建WebSockets。

    2.使用Akka Streams和actors处理websockets

    我们可以使用Play的工具ActorFlow来将一个ActorRef转换为一个flow,它接收一个函数,该函数将ActorRef转化为发送消息给一个akka.actor.Props 对象(来描述actor),当Play接收到websocket的连接时创建的对象

    import play.api.mvc._
    import play.api.libs.streams.ActorFlow
    import javax.inject.Inject
    import akka.actor.ActorSystem
    import akka.stream.Materializer
    
    class Application @Inject()(cc:ControllerComponents) (implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
    
      def socket = WebSocket.accept[String, String] { request =>
        ActorFlow.actorRef { out =>
          MyWebSocketActor.props(out)
        }
      }
    }
    

    注意 ActorFlow.actorRef(...)可以被任何akka streams Flow[In, Out, _]取代,但是actors是最直接的方式

    import akka.actor._
    
    object MyWebSocketActor {
      def props(out: ActorRef) = Props(new MyWebSocketActor(out))
    }
    
    class MyWebSocketActor(out: ActorRef) extends Actor {
      def receive = {
        case msg: String =>
          out ! ("I received your message: " + msg)
      }
    }
    

    所有从客户端接收到的消息都会被发送到actor中,任何由Play提供的消息都会被发送到客户端中

    3.WebSocket的关闭

    当WebSocket关闭时,Play会自动的停止actor。这意味着你可以实现actor的postStop方法来处理这一情况。

    override def postStop() = {
      someResource.close()
    }
    

    当处理WebSocket的actor终止时,Play将自动关闭WebSocket。所以,为了关闭WebSocket,发送一个PoisonPill给你自己的actor

    import akka.actor.PoisonPill
    
    self ! PoisonPill

    4.拒绝一个WebSocket

    有些情况可能需要判断是否接受一个WebSocket请求,这种情况下可以用acceptOrResult方法

     import play.api.mvc._
      import play.api.libs.streams.ActorFlow
      import javax.inject.Inject
      import akka.actor.ActorSystem
      import akka.stream.Materializer
    
      class Application @Inject() (cc:ControllerComponents)(implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
    
        def socket = WebSocket.acceptOrResult[String, String] { request =>
          Future.successful(request.session.get("user") match {
            case None => Left(Forbidden)
            case Some(_) => Right(ActorFlow.actorRef { out =>
              MyWebSocketActor.props(out)
            })
          })
        }
      }
    }
    

    5.处理不同类型的信息

    import play.api.libs.json._
    import play.api.mvc._
    import play.api.libs.streams.ActorFlow
    import javax.inject.Inject
    import akka.actor.ActorSystem
    import akka.stream.Materializer
    
    class Application @Inject()(cc:ControllerComponents)
                               (implicit system: ActorSystem, mat: Materializer)
      extends AbstractController(cc) {
    
      def socket = WebSocket.accept[JsValue, JsValue] { request =>
        ActorFlow.actorRef { out =>
          MyWebSocketActor.props(out)
        }
      }
    }
    

    假设我们想要接收JSON消息,并且我们想要将传入的消息解析为InEvent并将传出的消息格式化为OutEvent我们想要做的第一件事是为out InEventOutEventtype 创建JSON格式

    import play.api.libs.json._
    
    implicit val inEventFormat = Json.format[InEvent]
    implicit val outEventFormat = Json.format[OutEvent]

       //现在我们可以为这些类型创建一个WebSocket  MessageFlowTransformer

    import play.api.mvc.WebSocket.MessageFlowTransformer
    implicit val messageFlowTransformer = MessageFlowTransformer.jsonMessageFlowTransformer[InEvent, OutEvent]

       //最后,我们可以在我们的WebSocket中使用它们:

    import play.api.mvc._
    import play.api.libs.streams.ActorFlow
    import javax.inject.Inject
    import akka.actor.ActorSystem
    import akka.stream.Materializer
    class Application @Inject()(cc:ControllerComponents)(implicit system: ActorSystem, mat: Materializer)extends AbstractController(cc) {
      def socket = WebSocket.accept[InEvent, OutEvent] { request =>
        ActorFlow.actorRef { out =>
          MyWebSocketActor.props(out)
        }
      }
    }

    6.直接使用Akka Sreams处理WebSockets

    import play.api.mvc._
    import akka.stream.scaladsl._
    
    def socket = WebSocket.accept[String, String] { request =>
    
      // Log events to the console
      val in = Sink.foreach[String](println)
    
      // Send a single 'Hello!' message and then leave the socket open
      val out = Source.single("Hello!").concat(Source.maybe)
    
      Flow.fromSinkAndSource(in, out)
    }

    一个WebSocket可以获取请求头部信息,这允许你读取标准的头部及session信息。但是无法获取请求体及响应信息。

    这个例子中我们创建了一个sink将所有的信息打印到控制台中。为了发送信息,创建了一个source发送一个hello。我们也需要连接一个什么都不做的source,否则单个source会关闭flow,进而关闭链接。

    可以在 https://www.websocket.org/echo.html上测试WebSocket,值需要将地址设为ws://localhost:9000

    下面的例子会忽略所有的输入数据,在发送一个hello后关闭连接

    import play.api.mvc._
    import akka.stream.scaladsl._
    
    def socket = WebSocket.accept[String, String] { request =>
    
      // Just ignore the input
      val in = Sink.ignore
    
      // Send a single 'Hello!' message and close
      val out = Source.single("Hello!")
    
      Flow.fromSinkAndSource(in, out)
    }
    

    将输入打印成标准输出,然后使用一个mapped flow返回给客户端

    import play.api.mvc._
    import akka.stream.scaladsl._
    
    def socket =  WebSocket.accept[String, String] { request =>
    
      // log the message to stdout and send response back to client
      Flow[String].map { msg =>
        println(msg)
        "I received your message: " + msg
      }
    } 

    7.配置帧长度

    可以通过配置play.server.websocket.frame.maxLength或在启动时添加参数-Dwebsocket.frame.maxLength来配置帧的最大长度

    sbt -Dwebsocket.frame.maxLength=64k run
  • 相关阅读:
    基础字段及选项2(11)
    模型层及ORM介绍(9)
    Luogu [P3367] 模板 并查集
    Luogu [P1958] 上学路线_NOI导刊2009普及(6)
    Luogu [P3951] 小凯的疑惑
    Luogu [P2708] 硬币翻转
    Luogu [P1334] 瑞瑞的木板(手写堆)
    一步步学习如何建立自己的个性博客~~
    Android初学者—listView用法
    SQLite命令—对表插入和修改等操作
  • 原文地址:https://www.cnblogs.com/feiyumo/p/9146508.html
Copyright © 2020-2023  润新知