Kommunikation Java -> C#

skee

Mitglied
Hallo zusammen,
Ich habe ein Problem, welches mich gerade ein bisschen überfordert. Vielleicht hat ja einer von euch einen Lösungsansatz dazu, der mir weiterhilft:

Ich habe einen Java Socket Server und einen Client in C#.
Aktuell erledige ich die Kommunikation über einfache Strings, was meiner Meinung nach aber etwas unperformant ist und auch nicht wirklich sauber ist.
Am liebsten wäre mir es, wenn ich im Server einfach Klassen hätte, in denen ich die Infos, die zum CLient müssen, speichere und diese dann irgendwie binär zum Client schicke und dort wieder zusammensetze.
Ich habe ein bisschen gegoogelt, aber leider nicht wirklich brauchbare Ergebnisse gefunden, bzw die Info, dass das wohl nicht so einfach ist, wie ich mir das vorstelle...

Die zweite Alternative wäre, die Java Objekte in XML umzuwandeln, an den Client zu schicken und dort wieder in ein Objekt umzuwandeln. Soweit ich das verstanden habe, könnte das sogar funktionieren, aber ein XML ist ja auch wieder nur ein String, und soweit ich das erkennen kann, wäre der String aufgeblasener als mein aktell verwendeter...

Leider hörts da auch schon wieder auf.

Hat vielleicht jemand hier eine Idee, wie ich das Problem angehen könnte?

Danke
Skee
 
Hallo,

eine Möglichkeit wäre ein kleiner (SOAP oder REST) Webservice oder die Kommunikation über
RMI/IIOP (via IIOP.net). Eine weitere Möglichkeit wäre die Verwendung von Google Protocol Bufffers (Protobuf):
(hier mal die Variante .Net Webservice aus Java aufrufen mit JAX-WS)
Die Gegenrichtung lässt sich entsprechend analog aufbauen.
http://www.tutorials.de/java/305539-net-webservice-java-ansprechen.html

Hier mal Beispiel(e) für einen REST Webservice mit JAX-RS
http://www.tutorials.de/enterprise-...-webservice-mit-jersey-json-ueber-jquery.html
http://www.tutorials.de/enterprise-java-jee-j2ee-spring-co/387570-resourcen-liste-jax-rs-jersey.html

Hier die Variante via IIOP.Net (.Net Remoting <-> RMI/IIOP)
http://iiop-net.sourceforge.net/rmiAdderDNClient.html

Zu Protobuf siehe:
http://code.google.com/p/protobuf/
http://code.google.com/p/protobuf-net/

Gruß Tom
 
Es gibt eine große Menge an Cross-Language-ICP's. Hier eine prima Liste: http://en.wikipedia.org/wiki/Inter-process_communication

Hervorstechend sind die üblichen Verdächtigen:

- JSON-RPC
- XML-RPC
- Corba

Was ich auch mal ausprobieren will ist Thrift: http://en.wikipedia.org/wiki/Thrift_(protocol) Es sieht sehr leichgewichtig aus (2MB für alle im Wiki-Artikel erwähnten Sprachen). Wenn du mich fragst, wäre es mal einen Versuch wert. Wenn du es wirklich ausprobieren willst, kannst du gern deine Erfahrungen mitteilen, wäre daran echt interessiert :)
 
Danke für den Input.
Ich habe mich jetzt aber doch entschieden, einen ByteStream zu erzeugen und meine Werte darüber an den C#-Client zu schicken. Das war kurzfristig jetzt erstmal einfacher.
Ihr habt mir aber gute Lösungsansätze gegeben und ich werde da sicher noch was gebrauchen können und dann auch ein entsprechendes Feedback geben.

Danke erstmal
 
Hallo,

das könnte doch ein interessantes Thema für ein Codegolf Projekt sein :)
Baue ein minimales Client / Server System (mit einer der oben aufgelisteten) Remoting Technologien auf, dass den String "ping" an den Server sendet und den String "pong" als Antwort erhält.

Damit hätte man dann einen schönen Überblick über Setup / Bootstrap und zugegebenermaßen "minimale" Verwendung der Remoting Technologie ;-)

Gruß Tom
 
Hi.
das könnte doch ein interessantes Thema für ein Codegolf Projekt sein :)

Baue ein minimales Client / Server System (mit einer der oben aufgelisteten) Remoting Technologien auf, dass den String "ping" an den Server sendet und den String "pong" als Antwort erhält.
Here we go.. ;)

ZeroMQ

Server in C#

  1. Neues C# Projekt in VS 2010 anlegen
  2. clrzmq mit NuGet installieren
  3. Code einfügen und ausführen...

zu 2)
Code:
PM> Install-Package clrzmq
Successfully installed 'clrzmq 2.2.3'.
Successfully added 'clrzmq 2.2.3' to ZeroMsg.
zu 3)
C#:
using System;
using System.Text;
using ZMQ; 

namespace ZeroMsg
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new Context())
            using (var server = context.Socket(SocketType.REP))
            {
                server.Bind("tcp://*:108080");

                while (true) {
                    string message = server.Recv(Encoding.Unicode);
                    Console.WriteLine("Received request: {0}", message);

                    if (message == "ping") server.Send("pong", Encoding.Unicode);
                }
            }
        }
    }
}
Up and running in no time :)

Java Scala Client

[Das ist mühsam...]

  1. ZeroMQ Windows Installer runterladen und installieren.
  2. Quelldateien von jzmq auschecken/runterladen und erstellen laut http://www.zeromq.org/bindings:java#toc5 (ich verwende Windows7 64bit).
  3. Scala SBT Projekt mit giter8 erstellen
  4. zmq.jar und jzmq.dll ins lib Verzeichnis kopieren
  5. sicherstellen, dass libzmq-v100-mt.dll im PATH ist
  6. Code einfügen
  7. sbt run

zu 3)
Code:
$ g8 typesafehub/scala-sbt

Scala Project Using sbt

organization [org.example]: de.tutorials.examples
name [Scala Project]: PingPongClient
scala_version [2.9.2]:
version [0.1-SNAPSHOT]:

Applied typesafehub/scala-sbt.g8 in pingpongclient
zu 6)
Java:
// Scala !
package de.tutorials.examples

object Pingpongclient extends App {
  import org.zeromq.ZMQ

  private val encoding = "UTF-16LE"

  private val context = ZMQ.context(1 /* single threaded */)
  val socket = context.socket(ZMQ.REQ /* a requestor == client */)

  socket.connect("tcp://localhost:108080")

  for (i <- 1 to 10) {
    if (socket.send("ping".getBytes(encoding), 0)) {
      val buf = socket.recv(0)
      val msg = new String(buf, 0, buf.length, encoding)

      println(msg)
    }
    Thread.sleep(1000)
  }
  socket.close()
}

zu 7)
Code:
$ sbt run
[info] Loading global plugins
[info] Loading project definition from pingpongclient\project
[info] Set current project to PingPongClient
[info] Updating pingpongclient...
[info] Resolving org.scala-lang#scala-library;2.9.2 ...
[info] Done updating.
[info] Compiling 1 Scala source to target\scala-2.9.2\classes...
[info] Running de.tutorials.examples.Pingpongclient
pong
pong
pong
pong
pong
pong
pong
pong
pong
pong

Ausgabe vom Server:
Code:
Received request: ping
Received request: ping
Received request: ping
Received request: ping
Received request: ping
Received request: ping
Received request: ping
Received request: ping
Received request: ping
Received request: ping
Es spielt übrigens keine Rolle in welcher Reihenfolge Server und Client gestartet werden.

Gruß
 
Zuletzt bearbeitet:
Ok, ich versuch es mal mit Thrift:

1. Installation der notwendigen Libraries

- Das ist recht mühsam. Ich habe es für C#, Java, PHP und C/++ mittels Cygwin bauen können - es sind ein paar kleinere Patches notwendig. Wenn Bedarf besteht, hänge ich es hier gern an.

- Benötigt wird für C# eine Datei Thrift.dll
- Für Java sind gleich mehrere Libs im Classpath notwendig: commons-codec, commons-lang, commons-logging, httpclient, httpcore, junit (optional), libthrift (+javadoc-jar), log4j, servlet-api, slfj-api, slfj-log4j

2. IDL erstellen

für einen einfachen Ping-Pong-Service genügt folgendes .thrift-File:

Code:
namespace java de.tutorials
namespace csharp de.tutorials

service PingPongService
{
	string ping(1:string text);
}

Das jagt man durch den thrift-Generator (kann ich ebenfalls anhängen, oder man besorgt ihn sich bei apache.org) und generiert jeweils die Java- und C#-Klasse:

Code:
$ thrift.exe --gen csharp service.thrift
$ thrift.exe --gen java service.thrift

3. C#-/Java-Projekt

Als nächstes erstellt man eine C#-Solution (ich habe Sharpdevelop verwendet) und fügt die Thrift.dll-Assembly und System.Web hinzu. Achtung! Ich habe zunächst den Fehler gemacht und das falsche Target-Framework ausgewählt. Abhängig davon, wie man die Thrift.dll gebaut hat (ich hab es als .Net Framework 4.0 Assembly - !nicht Client Profile!) muss man seine Solution entsprechend konfigurieren.

In Eclipse habe ich ein neues Java-Projekt erstellt und zunächst die og. Libraries hinzugefügt.

4. Einbindung des generierten Codes

Die in 2. generierten Klassen bindet man in dafür erstellte C#-Solution bzw. in ein Java-Projekt ein. Am besten kopiert man den Inhalt von gen-csharp in das C#-Solution-Verzeichnis und den Inhalt von gen-java in das Java-Projekt (src-Folder).

5. Anschließend baut man zunächst mal den C#-Service:

C#:
using System;
using de.tutorials;
using Thrift;
using Thrift.Server;
using Thrift.Transport;

namespace thrift_pingpong
{
	public class PingPongHandler : PingPongService.Iface
	{
		public PingPongHandler()
		{
			
		}
		
		public string ping(string text)
		{
			Console.WriteLine("Incoming: " + text);
			return "Pong: " + text;
		}
	}
	
	class Program
	{
		public static void Main(string[] args)
		{
			try
			{
				PingPongHandler handler = new PingPongHandler();
				PingPongService.Processor processor = new PingPongService.Processor(handler);
				
				TServerTransport serverTransport = new TServerSocket(9090);
				TServer server = new TSimpleServer(processor, serverTransport);
				
				server.Serve();
			}
			catch(Exception ex)
			{
				Console.WriteLine(ex.StackTrace);
			}
		}
	}
}

6. Und natürlich den Java-Client:

Java:
package de.tutorials;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import de.tutorials.PingPongService;

public class ThriftJavaClient {

  /**
   * @param args
   */
  public static void main(String[] args)
  {
    try
    {
      TTransport transport;
      transport = new TSocket("localhost", 9090);
      transport.open();
      
      TProtocol protocol = new  TBinaryProtocol(transport);
      
      PingPongService.Client client = new PingPongService.Client(protocol);
      
      perform(client);
      
      transport.close();
    }
    catch(TException ex)
    {
      System.out.println(ex);
      ex.printStackTrace();
    }
  }
  
  private static void perform(PingPongService.Client client) throws TException
  {
    String hello = "Hello world!";
    
    String result = client.ping(hello);
    
    System.out.println(result);
  }
}


Gedauert hat das generieren und programmieren des Client/Servers ca. 15 Min nach dem ich 3 Stunden mit Cygwin die Library gebaut habe!! Ich brauch unbedingt ne SSD im Laptop ;-)

Es gibt im Thrift-Tarball sehr gute Beispiele, wie man das machen muss.
 
Hallo,

hier ein Beispiel mit zum Aufruf eines Java Webservices mit C# mittels JAX-WS auf der Java Seite.
Vorteile dieses Ansatzes sind, dass man u.a. keine Third-Party Bibliotheken braucht
und einen etablierten Standard (JAX-WS) verwendet. Darüber hinaus ist die Implementierung IMHO relativ einfach.

Unser PingWebServiceInterface:
Java:
package de.tutorials.training.jaxws;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public interface PingWebService {
  @WebMethod
  String process(String request);
}

Unsere Webservice Implementierung:
Java:
package de.tutorials.training.jaxws;

import javax.jws.WebService;

@WebService(endpointInterface = "de.tutorials.training.jaxws.PingWebService")
public class DefaultPingWebService implements PingWebService {
  public String process(String request) {
    System.out.println("Got Request: " + request);

    String response = null;
    if("ping".equals(request)){
      response = "pong";  
    }else{
      response = "unknownRequest: " + request; 
    }
    return response;
  }
}

Unser Java "Server":
Java:
package de.tutorials.training.jaxws;

import java.net.InetAddress;

import javax.xml.ws.Endpoint;

public class PingWebServiceExample {

  public static void main(String[] args) throws Exception {
    String address = "http://" + InetAddress.getLocalHost().getHostName() + ":44444/ping";
    Endpoint endpoint = Endpoint.publish(address, new DefaultPingWebService());
    System.out.println("Service Published @ " + address );
    System.out.println("Service WSDL @ " + address +"?wsdl");
  }

}

Unser .net Client:

1) Neues Visual C# (Konsolen)-Projekt erstellen.
2) Dienstverweis hinzufügen
3) URL: http://_HOSTNAME_:44444/ping?wsdl angeben -> Gehe zu.
in der Konfigurationsdatei app.config kann man unter configuration/system.serviceModel/client/endpoint -> über das address Attribut den Hostname anpassen.
4) Namespace De.Tutorials.WS angeben. Nach dem klick auf "Ok" wird eine entsprechende Klassenstruktur (je nach Konfiguration WebService Proxy, Request / Responses) für den Webservice Client generiert.
5) Anschließend folgenden Code in Progam.cs hinterlegen.
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Training.De.Tutorials.WS;

namespace Training
{
    class Program
    {
        static void Main(string[] args)
        {
            PingWebServiceClient service = new PingWebServiceClient();

            Console.WriteLine("Got Response: " + service.process("ping"));
        }
    }
}

... wenn ein WebService auf der Java Seite erweitert wurde, kann man über das Kontext-Menü (nach Selektion des entsprechenden Dienst-Verweises), den Dienst-Verweis aktualisieren - dabei werden dann ggf. auch neue Klassen generiert.

... du kannst natürlich auch komplexere Objekt Strukturen als Parameter / Rückgabe verwenden.
Dabei werden dann für die Strukturen vom JAX-WS Laufzeit System dynamisch entsprechende XML Schema Definitionen generiert.
hier mal anhand eines weiteren WebServices der einen System Status ermittelt und dazu entsprechende Request / Response Strukturen definiert.

Unser StatusWebService interface:
Java:
package de.tutorials.training.jaxws;

import javax.jws.WebMethod;

@WebService
public interface StatusWebService {
  @WebMethod
  StatusResponse getStatus(StatusRequest request);
}

Unsere StatusWebService Implementierung:
Java:
package de.tutorials.training.jaxws;

import javax.jws.WebService;

@WebService(endpointInterface="de.tutorials.training.jaxws.StatusWebService")
public class DefaultStatusWebService implements StatusWebService{

  @Override
  public StatusResponse getStatus(StatusRequest request) {
    System.out.println("Sending Server status to client: " + request.getClientId());
    StatusResponse response = new StatusResponse();
    response.setSystemStatus(querySystemStatus());
    return response;
  }

  private SystemStatusCode querySystemStatus() {
    return SystemStatusCode.READY;
  }
}

Request:
Java:
package de.tutorials.training.jaxws;

import java.io.Serializable;

public class Request implements Serializable{
  private static final long serialVersionUID = 1L;
}

StatusRequest:
Java:
package de.tutorials.training.jaxws;

public class StatusRequest extends Request{
  private static final long serialVersionUID = 1L;

  private String clientId;

  public String getClientId() {
    return clientId;
  }

  public void setClientId(String clientId) {
    this.clientId = clientId;
  }
}

Response:
Java:
package de.tutorials.training.jaxws;

import java.io.Serializable;

public class Response implements Serializable {
  private static final long serialVersionUID = 1L;
}

StatusResponse:
Java:
package de.tutorials.training.jaxws;

public class StatusResponse extends Response{
  private static final long serialVersionUID = 1L;

  private SystemStatusCode systemStatus;

  public SystemStatusCode getSystemStatus() {
    return systemStatus;
  }

  public void setSystemStatus(SystemStatusCode systemStatus) {
    this.systemStatus = systemStatus;
  }
}

SystemStatusCode Enum:
Java:
package de.tutorials.training.jaxws;

public enum SystemStatusCode {
  STARTING,READY, STOPPING;
}

Den StatusWebService können wir gemeinsam mit dem Ping Webservice betreiben:
Java:
package de.tutorials.training.jaxws;

import java.net.InetAddress;

public class WebServiceExample {

  public static void main(String[] args) throws Exception {
    
    String address = "http://" + InetAddress.getLocalHost().getHostName() + ":44444/ping";
    Endpoint.publish(address, new DefaultPingWebService());
    System.out.println("Service Published @ " + address );
    System.out.println("Service WSDL @ " + address +"?wsdl");

    
    address = "http://" + InetAddress.getLocalHost().getHostName() + ":44444/status";
    Endpoint.publish(address, new DefaultStatusWebService());
    System.out.println("Service Published @ " + address);
    System.out.println("Service WSDL @ " + address +"?wsdl");
  }

}

Hier der angepasste .Net Client in C#.
C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Training.De.Tutorials.WS;
using Training.De.Tutorials.WS.Status;

namespace Training
{
    class Program
    {
        static void Main(string[] args)
        {
            PingWebServiceClient service = new PingWebServiceClient();
            Console.WriteLine("Got Response: " + service.process("ping"));

            StatusWebServiceClient statusService = new StatusWebServiceClient();
            statusRequest request = new statusRequest();
            request.clientId = Guid.NewGuid().ToString();
            statusResponse response = statusService.getStatus(request);
            Console.WriteLine("System Status: "+ response.systemStatus);
        }
    }
}

Btw. die Namen für die generierten Klassen / Attribute kann man auf der Java Seite über entsprechende JAXB Annotationen (@XmlType, @XmlElement, etc.) beeinflussen.
Die Namen der WebService Klassen / Methoden lassen sich über die @WebService / @WebMethod Annotation steuern.
;-)

Gruß Tom
 
Hi.

[kleines Update]

(und als Notiz für mich selbst, da ich gerade dem Reflex widerstehen mußte für ein neues Projekt Avro einzusetzen)

Ich hatte mir nämlich vor einiger Zeit das (von mir erwähnte) Projekt Avro angeschaut. (Nun brauchte ich für mein Projekt etwas in der Art und deshalb der oben erwähnte Reflex...)

Auf dem Papier sieht das auch alles vernünftig aus. Interoperabiltät - ganz problemlos.

Die Realität sieht leider anders aus.

Avro unterstützt verschiedene sogenannte "Transports" oder "Transceiver". Das Problem ist nun, das diese in verschiedenen Sprachen ganz unterschiedlich umgesetzt sind (oder auch gar nicht).

Mein Versuch einen C# Avro Server zu schreiben schlug fehl, da die C# API keine RPC Unterstützung bietet. Siehe https://cwiki.apache.org/confluence/display/AVRO/Supported+Languages

Da der Client in Java bereits fertig war, dachte ich mir versuche ich wenigsten einen Ruby Server umzusetzen. Die Umsetzung selbst war kein Problem, dennoch können sich die beiden Programme nicht verständigen.

Warum habe ich ich auch nach recht intensivem Code-Review nicht nachvollziehen können.

In Ruby hatte ich den Avro::IPC::SocketTransport verwendet. In Java hatte ich den IPC, Netty, Socket und Datagram Transceiver versucht. (wobei ich logischerweise das beste Ergebnis - nämlich einen einzelnen Schleifendurchlauf - mit dem SocketTransceiver erreicht habe, danach warf Ruby eine Ausnahme und Java natürlich ebenfalls)

Ruby zu Ruby funktioniert.

Java zu Java sicherlich auch.

Aber Interoperabilität sieht anders aus. Es ist also zu Vorsicht geraten.

Gruß
 
Zuletzt bearbeitet:
Zurück