简体   繁体   English

Java Web套接字服务器

[英]Java web socket server

I'm trying to develop a Web socket library for Processing.org, and I want to use the Tyrus implementation. 我正在尝试为Processing.org开发Web套接字库,并且我想使用Tyrus实现。

I have managed to make the client work without any issues. 我设法使客户端正常工作。 The problems arrive around the Web socket server - it simply seems unnecessarily complex, and cumbersome to create a server (I have developed servers in both Python, and node.js previously without any real issues). 问题出现在Web套接字服务器周围-看起来不必要地复杂,并且创建服务器很麻烦(我以前在Python和node.js中都开发了服务器,以前没有任何实际问题)。

In essence I want to have a simple Web socket server class I can instantiate in Processing. 本质上,我希望有一个可以在Processing中实例化的简单Web套接字服务器类。 The way I imagine it to be is as follows (a Processing sketch): 我的想象方式如下(一个处理草图):

import websockets.*;

WSServer ws;

void setup(){
  ws= new WSServer(this,"localhost",8025); //this=PApplet (Processing specific detail)

  ws.sendMessage("A message"); //How I want to send messages to all clients
}

void draw(){
}

//Event listener for incoming messages
void webSocketServerEvent(String msg){
 println(msg); 
}

At first it is not necessary to focus on specifics like sub-protocols and the like. 首先,没有必要专注于诸如子协议之类的细节。 I simply want a Processing developer to be able to spin up a simple web socket server, stating the fully qualified URI in the constructor, the port, and define the onMessage eventlistener method. 我只是希望处理开发人员能够启动一个简单的Web套接字服务器,在构造函数,端口中声明完全限定的URI,并定义onMessage eventlistener方法。

I have now been trying to solve this for a few days, and I simply can't get my head around it. 我现在已经尝试解决了几天,但我实在无法解决。 When using the Tyrus Annotated endpoint approach I would say that the code is quite clean, and understandable, but everything seems completely sandboxed. 当使用Tyrus带注释的终结点方法时,我会说代码很干净并且可以理解,但是所有内容似乎都被沙盒化了。 I can't introduce my own constructor with the parameters I want, and I can't figure out how to set the server URI programatically. 我无法用所需的参数介绍自己的构造函数,也无法弄清楚如何以编程方式设置服务器URI。 When I can't introduce my own existing objects in the server endpoint, I can't propagate messages to my event listener method from above (webSocketServerEvent). 当我无法在服务器端点中引入自己的现有对象时,就无法从上方将消息传播到事件监听器方法(webSocketServerEvent)。 Additionally, the URI needs to be hardcoded as a parameter in the annotation (@ServerEndpoint(value = "/game")), which is a nice feature, but to me this would only be useful in very specific situations, and not the common use case. 此外,URI需要在​​注释(@ServerEndpoint(value =“ / game”))中作为参数硬编码,这是一个不错的功能,但是对我来说,这仅在非常特殊的情况下有用,而不是常见的情况用例。

My preliminary conclusion on the annotation approach is that it is very sandboxed, and primarily developed for standalone situations, where there is no need to be integrated into an other Java project. 我对注释方法的初步结论是,它非常沙盒化,主要是针对不需要集成到其他Java项目中的独立情况而开发的。 I'm fully aware that this conclusion can't be correct, which is exactly why I'm asking you guys to help me understand how to go about the issue. 我完全知道这个结论是不正确的,这就是为什么我要你们帮助我理解如何解决这个问题。

I have also been looking into the programmatic approach to Tyrus, and is seems like it is actually possible to configure your server to your needs. 我也一直在研究Tyrus的编程方法,似乎实际上可以根据需要配置服务器。 But this is where it becomes completely confusing for me - this is where the entire Java Web socket approach becomes unnecessarily complex and cumbersome (comparing to other languages). 但这对我来说完全令人困惑-这就是整个Java Web套接字方法不必要地变得复杂和繁琐(与其他语言相比)。

First of all, I have a class extending Endpoint, then I need to create a new class implementing ServerApplicationConfig, and I finally need to create a ServerEndpointConfig. 首先,我有一个扩展Endpoint的类,然后我需要创建一个实现ServerApplicationConfig的新类,最后我需要创建一个ServerEndpointConfig。 I then need to have a main class somewhere else, to actually initiate the entire stack. 然后,我需要在其他地方有一个主类,以实际启动整个堆栈。 I do recognise that this is typical Java behavior, but why do I need to create these completely arbitrary configuration classes and objects? 我确实认识到这是典型的Java行为,但是为什么我需要创建这些完全任意的配置类和对象? I now run into an other complexity on defining the server endpoint URI, since I now have to define the base URI and port in the instantiation of the web socket server, and I then need to add a suffix in the ServerEndpointConfig (ServerEndpointConfig.Builder.create(MyEndpoint.class, "/websocket")), meaning that I now have two places to define a single URI. 我现在在定义服务器端点URI时遇到了另一种复杂性,因为我现在必须在Web套接字服务器的实例化中定义基本URI和端口,然后需要在ServerEndpointConfig(ServerEndpointConfig.Builder中添加后缀。 create(MyEndpoint.class,“ / websocket”)),这意味着我现在有两个地方可以定义单个URI。

On top of the above I now run into the same issues as with the annotation approach regarding the class that extends Endpoint. 最重要的是,我现在遇到了与注释方法有关扩展Endpoint类的相同问题。 This class defines all the events like onOpen, onClose etc., which is quite nice from a devision of code perspective. 此类定义了所有事件,例如onOpen,onClose等,从代码角度来看,这是非常不错的。 The issue is that it seems like I can't provide my own constructor here as well. 问题在于,似乎我也无法在此处提供自己的构造函数。 When instantiating the web socket server I can only refer to the Endpoint class and not provide any user data. 实例化Web套接字服务器时,我只能引用Endpoint类,而不能提供任何用户数据。 I have found two different examples of this: 我发现了两个不同的例子:

container.connectToServer(MyClient.class, URI.create(uri));

Server server = new Server("localhost", 8025, "/websockets", WordgameServerEndpoint.class);

As a final remark to the programmatic endpoint, it seems completely unnecessary to force the definition of the onMessage event to happen through an addMessageHandler that often times seems to be called from within onOpen: 作为对编程端点的最后说明,似乎完全没有必要通过通常似乎是从onOpen内调用的addMessageHandler来强制onMessage事件的定义:

@Override
  public void onOpen(final Session session, EndpointConfig ec) {
    session.addMessageHandler(new MessageHandler.Whole<String>() {

      @Override
      public void onMessage(String text) {
        try {
          session.getBasicRemote().sendText(text);
        } catch (IOException ex) {
          Logger.getLogger(MyEndpoint.class.getName()).log(Level.SEVERE, null, ex);
        }
      }
  });

I have tried quite a few different approaches now, and my preliminary code is as follows: 我现在尝试了许多不同的方法,而我的初步代码如下:

package websockets;

import java.lang.reflect.Method;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.glassfish.tyrus.server.Server;
import processing.core.PApplet;

public class WebsocketServer3 {
    private PApplet parent;
    private Method webSocketServerEvent;
    private Session userSession = null;

    public WebsocketServer3(PApplet parent, String domain, int port, String uri){
        this.parent=parent;
        this.parent.registerMethod("dispose", this);

        Server server = new Server(domain, port, uri, WebsocketServerInstance.class);
        try {
            server.start();
        } catch (DeploymentException e) {
            e.printStackTrace();
        }

        try {
            webSocketServerEvent = parent.getClass().getMethod("webSocketServerEvent", String.class);
        } catch (Exception e) {
            // no such method, or an error.. which is fine, just ignore
        }
    }

    public void dispose(){
        // Anything in here will be called automatically when 
        // the parent sketch shuts down. For instance, this might
        // shut down a thread used by this library.
    }

    public void sendMessage(String mes){
        userSession.getAsyncRemote().sendText(mes);
    }


    @ServerEndpoint("/")
    public class WebsocketServerInstance {

        @OnOpen
        public void onOpen(Session session) {
            userSession=session;
            System.out.println("HI!!");
        }

        @OnMessage
        public void onMessage(String message, Session session) {
            if (webSocketServerEvent != null) {
                try {
                    webSocketServerEvent.invoke(parent, message);
                } catch (Exception e) {
                    System.err.println("Disabling webSocketServerEvent() because of an error.");
                    e.printStackTrace();
                    webSocketServerEvent = null;
                }
            }
        }

        @OnClose
        public void onClose(Session session, CloseReason closeReason) {
            System.out.println(String.format("Session %s closed because of %s",
                    session.getId(), closeReason));
        }
    }
}

I have tried to make the ServerEndpoint into an inner class, which doesn't work (I know there exists an other post on this issue, but I haven't been able to solve my issue from that post). 我试图使ServerEndpoint成为一个内部类,这是行不通的(我知道关于此问题还有另一篇文章,但是我无法从那篇文章中解决我的问题)。 The reason why I post the above code, is to provide an example of how I would like the coding procedure to be. 我发布上述代码的原因是为了提供一个示例,说明我希望编码过程如何。

All-in-all, I'm quite confused about how to create a simple web socket server with Tyrus (or any of the other Java implementations for that matter - the one I got furthest with is the one from TallNate, but there seems to be some issues with that library as well - it's quite understandable, but the issues arise when i call the run method - different discussion though). 总而言之,我对于如何用Tyrus(或其他任何Java实现)创建一个简单的Web套接字服务器感到非常困惑-我最了解的是TallNate的实现,但是似乎该库也有一些问题-这是可以理解的,但是当我调用run方法时会出现问题-尽管讨论不同。

My overall question is if anyone could point me in a direction, on how to solve my problem? 我的总体问题是,是否有人可以指出我的方向,如何解决我的问题?

Thanks in advance 提前致谢

Lasse 拉塞

I finally found a solution to my problem, with great inspiration from these two posts: 我终于从这两个帖子中得到了启发,找到了解决问题的方法:

My Maven POM file: 我的Maven POM文件:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>my.group.id</groupId>
    <artifactId>websockets</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.13</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.13</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
            <version>9.3.6.v20151106</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>9.3.6.v20151106</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-server</artifactId>
            <version>9.3.6.v20151106</version>
        </dependency>

        <dependency>
            <groupId>org.eclipse.jetty.websocket</groupId>
            <artifactId>websocket-client</artifactId>
            <version>9.3.6.v20151106</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.5.1</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <includeScope>runtime</includeScope>
                            <outputDirectory>${project.build.directory}/dependency-jars/</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

My final server code is as follows: 我的最终服务器代码如下:

WebsocketServer.java WebsocketServer.java

package websockets;

import java.lang.reflect.Method;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

import processing.core.PApplet;

public class WebsocketServer {
    private PApplet parent;
    private Method websocketServerEvent;
    private WebsocketServerController serverController;

    public WebsocketServer(PApplet parent, int port, String uri){
        this.parent=parent;
        this.parent.registerMethod("dispose", this);

        try {
            websocketServerEvent = parent.getClass().getMethod("webSocketServerEvent", String.class);
        } catch (Exception e) {
            // no such method, or an error.. which is fine, just ignore
        }

        Server server = new Server(port);
        serverController = new WebsocketServerController(parent, websocketServerEvent);

        WebSocketHandler wsHandler = new WebSocketHandler(){

                @Override
                public void configure(WebSocketServletFactory factory){
                    factory.setCreator(new WebsocketServerCreator(serverController));
                }
        };

        ContextHandler contextHandler = new ContextHandler(uri);
        contextHandler.setAllowNullPathInfo(true); // disable redirect from /ws to /ws/
        contextHandler.setHandler(wsHandler);

        server.setHandler(contextHandler);

        try {
            server.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //TODO: Add possibility for sending back to specific client
    public void sendMessage(String message){
        serverController.writeAllMembers(message);

    }

    public void dispose(){
        // Anything in here will be called automatically when 
        // the parent sketch shuts down. For instance, this might
        // shut down a thread used by this library.
    }
}

WebsocketServerCreator.java WebsocketServerCreator.java

package websockets;

import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;

public class WebsocketServerCreator implements WebSocketCreator{
    private WebsocketServerController ctrl;

    public WebsocketServerCreator(WebsocketServerController ctrl){
        this.ctrl = ctrl;
    }

    public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response){
        return new WebsocketServerEvents(ctrl);
    }
}

WebsocketServerController WebsocketServerController

package websockets;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import processing.core.PApplet;

public class WebsocketServerController {

    private List<WebsocketServerEvents> members = new ArrayList<>();
    private PApplet parent;
    private Method serverEvent;

    public WebsocketServerController(PApplet p, Method serverEvent){
        parent=p;
        this.serverEvent=serverEvent;
    }

    public void join(WebsocketServerEvents socket) {
        members.add(socket);
    }

    public void remove(WebsocketServerEvents socket) {
        members.remove(socket);
    }

    public void writeAllMembers(String message) {
        for (WebsocketServerEvents member : members) {
            member.session.getRemote().sendStringByFuture(message);
        }
    }

    public void writeSpecificMember(String memberName, String message) {
        WebsocketServerEvents member = findMemberByName(memberName);
        member.session.getRemote().sendStringByFuture(message);
    }

    public WebsocketServerEvents findMemberByName(String memberName) {
        return null;
    }

    public void sendToOnMessageListener(String message){
        if (serverEvent != null) {
            try {
                serverEvent.invoke(parent, message);
            } catch (Exception e) {
                System.err.println("Disabling webSocketEvent() because of an error.");
                e.printStackTrace();
                serverEvent = null;
            }
        }
    }
}

WebsocketServerEvents WebsocketServerEvents

package websockets;

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;

@WebSocket
public class WebsocketServerEvents {

    public Session session;
    public WebsocketServerController ctrl;

    public WebsocketServerEvents(WebsocketServerController ctrl) {
        this.ctrl=ctrl;
    }

    @OnWebSocketConnect
    public void handleConnect(Session session) {
        this.session = session;
        ctrl.join(this);
    }

    @OnWebSocketClose
    public void handleClose(int statusCode, String reason) {
        ctrl.remove(this);
    }

    @OnWebSocketMessage
    public void handleMessage(String message) {
        ctrl.sendToOnMessageListener(message);
    }

    @OnWebSocketError
    public void handleError(Throwable error) {
        error.printStackTrace();    
    }
}

It might not be the best of solutions, but I does the job. 它可能不是最好的解决方案,但我可以做到。 The most confusing and obscure part is definitely the idea of handlers. 最令人困惑和晦涩的部分肯定是处理程序的概念。 There are quite a few of them, and it's really hard to figure out which to use when and why. 其中有很多,并且很难弄清楚何时以及为什么使用哪个。

After my latest experiences with Web sockets in Java I must sat that, I would not encourage anyone to use Java as foundation for this kind of applications. 在使用Java使用Web套接字的最新经验之后,我必须坐下来,我不鼓励任何人使用Java作为此类应用程序的基础。 If possible you should definitely look into node.js, Python or the like (way easier to understand and use). 如果可能的话,您绝对应该研究node.js,Python或类似工具(更易于理解和使用)。 If you still want / are forced to use Java, then I have found the Jetty implementation to be the best suited (including documentation and example wise). 如果您仍然想/被迫使用Java,那么我发现Jetty实现是最合适的(包括文档和示例)。

Regards 问候

Lasse 拉塞

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM