当我几乎完成本文时,我注意到Play 2.0也支持websockets。 我真的很喜欢在Play和Scala中进行开发,因此作为一个实验,我将后端部分从Jetty / Java / JavaCV堆栈重写为Play2.0 / Scala / JavaCV堆栈。 如果您想自己做,请确保从此处开始使用前端代码。 由于前端代码除了websocket正在侦听的位置以外没有其他更改。
设置Play 2.0环境
我不会过多地谈论如何启动Play 2.0 / Scala项目。 如果您需要更多信息,可以在我的其他一些帖子中找到详细信息。 我们要做的是设置JavaCV的依赖关系,以便可以在Play 2中使用它们。我已将它们手动添加到本地ivy存储库中,以便可以像其他任何依赖关系一样从sbt配置中引用它们。 对于我的示例,我为JavaCV库创建了以下目录布局:
./play-2.0-RC2/repository/cache/javacv
./play-2.0-RC2/repository/cache/javacv/javacpp
./play-2.0-RC2/repository/cache/javacv/javacpp/ivy-2.3.1.xml
./play-2.0-RC2/repository/cache/javacv/javacpp/ivydata-2.3.1.properties
./play-2.0-RC2/repository/cache/javacv/javacv
./play-2.0-RC2/repository/cache/javacv/javacv/ivy-2.3.1.xml
./play-2.0-RC2/repository/cache/javacv/javacv/ivydata-2.3.1.properties
./play-2.0-RC2/repository/cache/javacv/javacv-macosx-x86_64
./play-2.0-RC2/repository/cache/javacv/javacv-macosx-x86_64/ivy-2.3.1.xml
./play-2.0-RC2/repository/cache/javacv/javacv-macosx-x86_64/ivydata-2.3.1.properties
./play-2.0-RC2/repository/local/javacv
./play-2.0-RC2/repository/local/javacv/javacpp
./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1
./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/ivys
./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/ivys/ivy.xml
./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/jars
./play-2.0-RC2/repository/local/javacv/javacpp/2.3.1/jars/javacpp.jar
./play-2.0-RC2/repository/local/javacv/javacv
./play-2.0-RC2/repository/local/javacv/javacv/2.3.1
./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/ivys
./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/ivys/ivy.xml
./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/jars
./play-2.0-RC2/repository/local/javacv/javacv/2.3.1/jars/javacv.jar
./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64
./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1
./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/ivys
./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/ivys/ivy.xml
./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/jars
./play-2.0-RC2/repository/local/javacv/javacv-macosx-x86_64/2.3.1/jars/javacv-macosx-x86_64.jar
从清单中可以看到,我只是将三个javacv提供的jar添加到了本地存储库中。 我还添加了一个最小的ivy.xml,以便可以在ivy和sbt中使用它们。 这个最小的ivy.xml看起来像这样:
<?xml version="1.0" encoding="UTF-8"?>
<ivy-module version="2.0" xmlns:m="http://ant.apache.org/ivy/maven">
<info
organisation="javacv"
module="javacv-macosx-x86_64"
revision="2.3.1"
status="release">
</info>
<publications>
<artifact type="jar"/>
</publications>
</ivy-module>
将这些文件添加到我的存储库后,我可以在Build.scala文件中设置Play 2.0项目的依赖项。
import sbt._
import Keys._
import PlayProject._
object ApplicationBuild extends Build {
val appName = "PlayWebsocketJavaCV"
val appVersion = "1.0-SNAPSHOT"
val appDependencies = Seq(
"javacv" % "javacv" % "2.3.1",
"javacv" % "javacpp" % "2.3.1",
"javacv" % "javacv-macosx-x86_64" % "2.3.1"
)
val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
// Add your own project settings here
)
}
现在运行“ play update”和“ play eclipsify”以更新依赖关系和Eclipse配置(如果使用的是Eclipse)。
在Play中配置WebSocket
在Play 2.0中使用websocket非常简单。 您需要做的第一件事是将URL添加到conf目录中的路由配置中。
GET /wsrequest controllers.Application.wsrequest
并且,当然,您需要实施此路线指向的操作:
/**
* Simple websocket listener configured in play 2. This uses a synchronous model, where
* the same channel is used to send the response. For this usecase this is useful, if
* we want async processing we could have used Akka actors together with play 2.0 async
* support.
*/
def wsrequest = WebSocket.using[Array[Byte]] { request =>
// Create the outbound value that is called for each
val out = Enumerator.imperative[Array[Byte]]();
val in = Iteratee.foreach[Array[Byte]](content => {
out.push(FaceDetect.detect(content));
})
// tie the in and out values to each other
(in, out)
}
在此代码中,我们配置一个输入通道(输入)和一个输出通道(输出),并将它们连接到插座。 每当HTML5客户端通过websocket发送请求时,都会调用“ in”方法,而当我们想向客户端发送内容时,可以使用“ out”通道。 需要将“ in”频道定义为Iteratee(更多信息,请参见这些 Play文档)。 它的作用是,对于收到的每个输入消息,我们都运行specifici方法。 在这种情况下,我们运行FaceDetect.detect操作(稍后将对此进行详细介绍),并且使用“ out”通道将该操作的结果推回客户端。 此“输出”通道本身被定义为枚举器(请参见这些播放文档)。 如果我们希望将此枚举数附加到侦听器,则可以附加不同的侦听器,但是在这种情况下,我们对消息不执行任何操作,只需将其传递给客户端即可。
从Scala使用JavaCV
最后一步是FaceDetect.detect函数的代码。 Java版本(请参阅前面提到的文章 )很容易转换为scala版本。
package javacv
import com.googlecode.javacv.cpp.opencv_core._
import com.googlecode.javacv.cpp.opencv_imgproc._
import com.googlecode.javacv.cpp.opencv_highgui._
import com.googlecode.javacv.cpp.opencv_objdetect._
import com.googlecode.javacpp.BytePointer
import java.nio.ByteBuffer
import javax.imageio.ImageIO
import java.io.ByteArrayOutputStream
import scala.tools.nsc.io.VirtualFile
object FaceDetect {
var CASCADE_FILE =PATH_TO_CASCADE_FILE;
var minsize = 20;
var group = 0;
var scale = 1.1;
def detect(imageData:Array[Byte]) : Array[Byte] = {
// we need to wrap the input array, since BytePointer doesn't accept
// a bytearray as input. It accepts a byte varargs, but Array[Byte]
// doesn't convert automatically
var wrappedData = ByteBuffer.wrap(imageData);
var originalImage = cvDecodeImage(cvMat(1, imageData.length,CV_8UC1, new BytePointer(wrappedData)));
// convert to grayscale for easy detection
var grayImage = IplImage.create(originalImage.width(), originalImage.height(), IPL_DEPTH_8U, 1);
cvCvtColor(originalImage, grayImage, CV_BGR2GRAY);
// storage is needed to store information during detection
var storage = CvMemStorage.create();
// load and run the cascade
var cascade = new CvHaarClassifierCascade(cvLoad(CASCADE_FILE));
var faces = cvHaarDetectObjects(grayImage, cascade, storage, scale, group, minsize);
// draw a rectangle for the detected faces
for (i <- 0 until faces.total) {
var r = new CvRect(cvGetSeqElem(faces, i));
cvRectangle(originalImage, cvPoint(r.x, r.y),
cvPoint(r.x + r.width(), r.y + r.height),
CvScalar.YELLOW, 1, CV_AA, 0);
}
// convert to bytearray and return
var bout = new ByteArrayOutputStream();
var imgb = originalImage.getBufferedImage();
ImageIO.write(imgb, "png", bout);
bout.toByteArray()
}
}
我遇到的唯一问题是BytePointer构造函数。 签名之一接受字节类型的变量。 在Java中,这允许我只为该构造函数提供一个byte [],而在Scala中,这是行不通的。 幸运的是,也可以使用ByteBuffer创建BytePointer。 对于其余部分,这是Java到Scala的一对一转换。
运行代码
仅此而已。 默认情况下,play在端口9000上侦听,在基于Jetty的示例中,我们使服务器在9999上运行并侦听根上下文。 要使用我们基于Play2 / Scala的服务器,我们只需要将浏览器指向正确的websocket服务器url。
ws = new WebSocket("ws://127.0.0.1:9000/wsrequest");
现在,当我们运行它时,我们将Play 2用作服务器,并使用scal运行JavaCV代码。 更重要的是,它仍然有效:
参考:来自Smart Java博客的JCG合作伙伴 Jos Dirksen的带有Play 2.0和Scala的二进制websocket(以及Java CV / OpenCV) 。
翻译自: https://www.javacodegeeks.com/2012/05/binary-websockets-with-play-20-and.html