چارچوبی برای محاسبات شبكه‌ای

 

ساخت برنامه محاسبات شبكه‌ای را با كمك ابزارهای open-source فرا بگيريد

 

 

نويسنده: Anthony Karre

مترجم: شهناز پيروزفر

 

چكيده

چارچوب‌هاي محاسباتي شبكه‌اي در مقياس بزرگ را مي‌توان براي ايجاد زير‌ساختارهاي شبكه‌اي محاسباتي بكار برد، اما پيچيدگي اين چارچوبها مي‌تواند مانعي براي طراحان نرم‌افزار در آزمايش محاسبات شبكه‌اي سطح ورودي باشد.

اين مقاله با كمك ClassLoader سفارشي جاوا، كانتينر سرولت Tomcat، Axis SOAP (Simple object Access protocol)، چارچوب ساده‌اي را براي پشتيباني از حداقل نيازمندهاي شبكه‌اي (گرير) نظير استقلال ماشين، توزيع محاسبه فعاليت انتزاعي شده و اجرا، و زيرساختار امن و اندازه‌پذيري، تشريح مي‌كند.

محاسبات اينترنتي اصطلاحي است كه براي توصيف كاربرد چند كامپيوتر شبكه‌بندي شده براي اجراي برنامه‌هاي محاسباتي انحصاري بكار مي‌رود. پروژه The search for Extraterrestrial Intelligence SETI مشهورترين مثال اين مورد است كه در آن پروژه، نيم ميليون كاربر خانگي و سازماني از توان پردازشي براي تحليل داده‌هاي تلسكوپ راديويي براي SETI استفاده مي‌كردند.

پروژه SETI نمونه‌اي از قلمرو محاسبات اينترنتي است كه برنامه انحصاري و انسجام عمودي دارد. به عبارتي استفاده عمومي از منابع محاسباتي وجود ندارد و جريان داده‌اي بين منبع محاسبات و سرور داده‌اي از APIهاي اختصاصي استفاده مي‌كند. هنگامي كه ارتقا يك برنامه لازم است، بايد در فواصلي اجراهاي جديد download شوند، و در واقع هيچ لايه مديريتي براي قرار دادن ديناميكي فعاليت‌هاي محاسباتي جديد وجود ندارد.

تبديل: اينترنت به محاسبات شبكه‌اي

با اين تبديل كه حاصل موفقيت پروژه SETI است، محققان تصميم گرفته‌اند براي بهره‌برداري از حجم عظيم منابع محاسبات متصل به اينترنت، اما به گونه‌اي امن، قابل مديريت و قابل توسعه، كارهايي را انجام دهند. در چند سال گذشته، چارچوب‌هاي متعددي با تاكيد بر مديريت منابع اشتراكي،‌ توسعه يافتند. اين چارچوبها گونه‌هاي زير را فراهم مي‌آورند:

- استقلال ماشين در حين استفاده از جاوا و سرويس‌هاي وب

- امنيت در پياده‌سازي‌هاي سرويس وب نظير SOAP با تركيب پيش ساخته HTTP و كنترل اعتبار كاربر

- تجرد فعاليت، كه قابليت سيستم مديريت براي سپردن وظايف اختياري به منابع مبتني بر شبكه است.

- مديريت منابعي به جز پردازنده نظير فضاي ذخيره‌سازي فايل

- مديريت پوياي همه موارد فوق با ارزيابي بيدرنگ وضعيت شبكه

بعضي از چارچوب‌ها در اين زمينه موفق عمل كردند به طوري كه طراحان اينگونه چارچوبها، آنها را در قالب تول‌كيت عرضه نمودند. Globus Toolkit 3.0 نمونه‌اي از اين تول‌كيت‌هاست. اين تول‌كيت توسعه OGSA (Open Grid Services Architecture) را تحت تاثير قرار مي‌دهد. محققان سان ميكروسيستمز نيز چارچوب محاسبات ديگر مبتني بر API ارتباطي jxta peer-to-peer توسعه داده‌اند. اين چارچوبها با وجود توانمندي كه دارند نيازمند دانش بسار بالايي براي بهره‌برداري موفقيت‌آميزشان هستند. اين امر مانعي براي افراد يا گروه‌هايي است كه قصد دارند از اين طريق تجربه بيندوزند. اين چارچوب‌ها براي برنامه‌نويسان جديد همانند استفاده از struts براي اولين برنامه وب است.

 

شكل 1. توالي ساده اجراي گريد

 

 

 

 

 

 

حداقل چارچوب محاسبات شبكه‌اي

من يك چارچوب كوچكي را در اين مقاله نشان مي‌دهم كه به سادگي محيط محاسباتي شبكه‌اي را مي‌سازد. اين چارچوب را مي‌توان به سرعت با كمك TomCat، Axis توسعه داد. چارچوب مذكور با وجود سادگي، حداقل نيازمنديهاي شبكه را برآورده مي‌سازد:

 

شكل 2. خروجي فرمان IS در يونيكس

 

 

- استقلال ماشين از طريق به كار‌گيري جاوا، Tomcat و Axis حاصل مي‌شود.

- زيرساختار امن و اندازه‌پذير كه با بكارگيري سرويس‌هاي وب مبتني بر SOAP براي ارتباطات كلاينت سرور حاصل مي‌شود.

- اجراي فعاليت انتزاعي كه با بكارگيري ClassLoader به دست مي‌آيد. كلاينت با كمك ClassLoader مي‌تواند كلاسهاي thread اختياري جاوا را شناسايي و اجرا كند.

شكل 1 نماي ساده‌اي از معماري چارچوب و توالي اجرا را به تصوير مي‌كشد.

 

 

 

ساخت يك برنامه نمونه

من در اين مقاله بر ساخت و آزمايش چارچوب مذكور متمركز مي‌شوم و مثال‌هايي را به ترتيب زير ارائه مي‌دهم:

1. ساخت سرويس Server-side SOAP با كمك TomCat و Axis

2. برقراري stubs ارتباطي براي پشتيباني از كاربرد client-side سرويس SOAP.

3. ساخت ClassLoader سفارشي Client-side

4. ساخت برنامه اصلي كلاينت

5. ساخت فعاليت محاسباتي براي تمرين كردن ClassLoader

 

 

شكل 3. مشاهده WSDL از اينترنت اكسپلورر

 

ساخت سرويس SOAP

سرويس SOAP كه در اين مقاله مي‌سازم، رابطه تنگاتنگي با لايه مديريت دارد. سرويس SOAP به برنامه محاسباتي شبكه‌اي امكان مي‌دهد تا كلاسهاي مورد نياز خود را از سرور SOAP بگيرد. در حاليكه مثال من يك فايل Jar خاص را ارائه مي‌دهد، نسخه واقعي اين سرويس به چند فايل jar (كه هر يك حاوي فعاليت محاسباتي متفاوتي هستند) دسترسي خواهد داشت و منطقي براي كنترل JAR دارد.

 

شكل 4. نتايج WSDL2Java

 

 

اولين گام در فراهم آوردن سرويس SOAP، تنظيم زيرساختار SOAP است. من TomCot را به عنوان سرور كانتينر سرولت /HTTP برگزيدم، زيرا يك پروژه open-source

 قابل اعتماد و كاربرد آن آسان است. من Axis را به عنوان سرويس‌دهنده SOAP برگزيدم، زيرا از Service installer با كاربرد آن drap and drop پشتيباني مي‌كند و همراه با ابزاري است كه SOAP client-side stubs را از فايلهاي (web services Description Language) WSDL مي‌سازد.

پس از download و نصب Tomcat 4.0.6 و Axis 1.0، كلاس Grid Connection سرويس SOAP را نوشتم. اين سرويس فايل Jar شناخته شده‌اي را استخراج، آن را در آرايه بايت بارگذاري نموده و آرايه بايت را به فراخوان باز مي‌گرداند، كد زير فايل كلي Grid connection Java است:

 

////  GridConnection.java
//
import java.util.*;
import java.io.* ;

public class GridConnection {

  public byte[] getJarBytes () {

    byte[] jarBytes = null ;
          
    try {
      FileInputStream fi = new FileInputStream("/Users/tkarre/MySquare/build/MySquare.jar");
      jarBytes = new byte[fi.available()];
      fi.read(jarBytes);
      fi.close() ;
    }
    catch(Exception e) {}
    
    return jarBytes ;
  }
}

نصب سرويس SOAP

طرح نصب (Axis Java web Service) JWS، نصب سرويس جديد ما را آسان مي‌سازد. شما مي‌توانيد يك سرويس ساده (مانند كلاس GridConnection فوق) را با Axis بنويسيد، پسوند java. فايل را به JWS تغيير دهيد و آن را به دايكتوري Axis Webapp، درگ و drop كنيد. Axis، .jws جديد را شناسايي و و تفسير نموده و آن را به صورت نقطه انتهايي SOAP، در دسترس قرار مي‌دهد. شكل 2 اين فايل و فايل‌هاي ساخته شده كلاس JWS را نشان مي‌دهد. در خروجي فرمان IS يونيكس، فايل GridConnection .JWS را بيابيد.

 

ساخت Stubهاي ارتباطي Client-side

به محض اينكه سرويس جديد SOAP را نصب نمودم، بايد كد Client-side را براي استفاده از سرويس توليد مي‌كردم. من از ابزار خط فرمان Axis WSDL2Java براي توليد فايلهاي جاوا استفاده نمودم. اين فايل‌ها، stubهاي لازم براي درخواست سرويس SOAP را فراهم مي‌آورند.

اين ابزار فرايند كد‌نويسي دستي كلاسهاي مورد نياز براي پياده‌سازي client-side واسط SOAP را حذف مي‌كند. ابزار WSDL2Java به فايل WSDL نياز دارد، لذا از گونه ديگر Axis براي سرويسمان استفاده مي‌كنم. Axis مي‌تواند WSDL قابل رويت را با استفاده از مرورگر وب براي هر گونه سرويسي كه به طور محلي نصب شده است، توليد كند. من در كلاس GridConnection از URL.

http://pcworldi.ipowermysql.com:8080/axis/GridConnection.JWS?wsdl استفاده نمودم.

شكل 3، WSDL را كه Axis براي مشاهده در اينترنت اكسپلورر توليد مي‌كند، نشان مي‌دهد. شما مي‌توانيد پس از ذخيره فايل WSDL (بدين منظور مي‌تواند از فرمان View Source استفاده و آن را ذخيره نماييد)، ساخت stub را ادامه دهيد. من WSDL را به صورت Grid Connection.wsdl ذخيره و سپس فرمان زير را انجام دادم.

 

Java org. apache. axis. wsdl. WSDL2Java - 0 -pcom. perficient. grid. client Grid Connection.wsdl

 

پارامتر –p به WSDL2Java مي‌گويد كه ساختار پكيج را براي com.perficient.grid.client بسازد. من براي ساخت برنامه واقعي كلاينت، از اين پكيج استفاده نمودم. شكل 4 اجراي اين فرمان را نشان مي‌دهد. (توجه نماييد كه مسير كلاس را به گونه‌اي تنظيم نماييد كه شامل JARهاي متعدد نصب شده با Axis باشد.) WSDL2Java در شكل 4، ساختار دايركتوري پكيج و چهار فايل جاوا ساخته است. محتواي چهار فايل‌ جاوا كه با ابزار WSDL2Java ساخته شده، در ذيل آمده است:

/**
* GridConnection.java
*
* This file was auto-generated from WSDL
* by the Apache Axis WSDL2Java emitter.
*/

package com.perficient.grid.client;

public interface GridConnection extends java.rmi.Remote {
  public byte[] getJarBytes() throws java.rmi.RemoteException;
}


/**
* GridConnectionService.java
*
* This file was auto-generated from WSDL
* by the Apache Axis WSDL2Java emitter.
*/

package com.perficient.grid.client;

public interface GridConnectionService extends javax.xml.rpc.Service {
  public java.lang.String getGridConnectionAddress();

  public com.perficient.grid.client.GridConnection getGridConnection() throws javax.xml.rpc.ServiceException;

  public com.perficient.grid.client.GridConnection getGridConnection(java.net.URL portAddress) throws javax.xml.rpc.ServiceException;
}


/**
* GridConnectionServiceLocator.java
*
* This file was auto-generated from WSDL
* by the Apache Axis WSDL2Java emitter.
*/

package com.perficient.grid.client;

public class GridConnectionServiceLocator extends org.apache.axis.client.Service implements com.perficient.grid.client.GridConnectionService {

  // Use to get a proxy class for GridConnection.
  private final java.lang.String GridConnection_address = "http://pcworldi.ipowermysql.com:8080/axis/GridConnection.jws";

  public java.lang.String getGridConnectionAddress() {
    return GridConnection_address;
  }

  // The WSDD service name defaults to the port name.
  private java.lang.String GridConnectionWSDDServiceName = "GridConnection";

  public java.lang.String getGridConnectionWSDDServiceName() {
    return GridConnectionWSDDServiceName;
  }

  public void setGridConnectionWSDDServiceName(java.lang.String name) {
    GridConnectionWSDDServiceName = name;
  }

  public com.perficient.grid.client.GridConnection getGridConnection() throws javax.xml.rpc.ServiceException {
    java.net.URL endpoint;
      try {
        endpoint = new java.net.URL(GridConnection_address);
      }
      catch (java.net.MalformedURLException e) {
        return null; // unlikely as URL was validated in WSDL2Java
      }
      return getGridConnection(endpoint);
  }

  public com.perficient.grid.client.GridConnection getGridConnection(java.net.URL portAddress) throws javax.xml.rpc.ServiceException {
    try {
      com.perficient.grid.client.GridConnectionSoapBindingStub _stub = new com.perficient.grid.client.GridConnectionSoapBindingStub(portAddress, this);
      _stub.setPortName(getGridConnectionWSDDServiceName());
      return _stub;
    }
    catch (org.apache.axis.AxisFault e) {
      return null;
    }
  }

  /**
   * For the given interface, get the stub implementation.
   * If this service has no port for the given interface,
   * then ServiceException is thrown.
   */
  public java.rmi.Remote getPort(Class serviceEndpointInterface) throws javax.xml.rpc.ServiceException {
    try {
      if (com.perficient.grid.client.GridConnection.class.isAssignableFrom(serviceEndpointInterface)) {
        com.perficient.grid.client.GridConnectionSoapBindingStub _stub = new com.perficient.grid.client.GridConnectionSoapBindingStub(new java.net.URL(GridConnection_address), this);
        _stub.setPortName(getGridConnectionWSDDServiceName());
        return _stub;
      }
    }
    catch (java.lang.Throwable t) {
      throw new javax.xml.rpc.ServiceException(t);
    }
      throw new javax.xml.rpc.ServiceException("There is no stub implementation for the interface:  " + (serviceEndpointInterface == null ? "null" : serviceEndpointInterface.getName()));
  }

  /**
   * For the given interface, get the stub implementation.
   * If this service has no port for the given interface,
   * then ServiceException is thrown.
   */
  public java.rmi.Remote getPort(javax.xml.namespace.QName portName, Class serviceEndpointInterface) throws javax.xml.rpc.ServiceException {
    java.rmi.Remote _stub = getPort(serviceEndpointInterface);
    ((org.apache.axis.client.Stub) _stub).setPortName(portName);
    return _stub;
  }

  public javax.xml.namespace.QName getServiceName() {
    return new javax.xml.namespace.QName("http://pcworldi.ipowermysql.com:8080/axis/GridConnection.jws", "GridConnectionService");
  }

  private java.util.HashSet ports = null;

  public java.util.Iterator getPorts() {
    if (ports == null) {
      ports = new java.util.HashSet();
      ports.add(new javax.xml.namespace.QName("GridConnection"));
    }
    return ports.iterator();
  }

}


/**
* GridConnectionSoapBindingStub.java
*
* This file was auto-generated from WSDL
* by the Apache Axis WSDL2Java emitter.
*/

package com.perficient.grid.client;

public class GridConnectionSoapBindingStub extends org.apache.axis.client.Stub implements com.perficient.grid.client.GridConnection {
  private java.util.Vector cachedSerClasses = new java.util.Vector();
  private java.util.Vector cachedSerQNames = new java.util.Vector();
  private java.util.Vector cachedSerFactories = new java.util.Vector();
  private java.util.Vector cachedDeserFactories = new java.util.Vector();

  public GridConnectionSoapBindingStub() throws org.apache.axis.AxisFault {
    this(null);
  }

  public GridConnectionSoapBindingStub(java.net.URL endpointURL, javax.xml.rpc.Service service) throws org.apache.axis.AxisFault {
    this(service);
    super.cachedEndpoint = endpointURL;
  }

  public GridConnectionSoapBindingStub(javax.xml.rpc.Service service) throws org.apache.axis.AxisFault {
    if (service == null) {
      super.service = new org.apache.axis.client.Service();
    } else {
      super.service = service;
    }
  }

  private org.apache.axis.client.Call createCall() throws java.rmi.RemoteException {
    try {
      org.apache.axis.client.Call _call =
        (org.apache.axis.client.Call) super.service.createCall();
      if (super.maintainSessionSet) {
        _call.setMaintainSession(super.maintainSession);
      }
      if (super.cachedUsername != null) {
        _call.setUsername(super.cachedUsername);
      }
      if (super.cachedPassword != null) {
        _call.setPassword(super.cachedPassword);
      }
      if (super.cachedEndpoint != null) {
        _call.setTargetEndpointAddress(super.cachedEndpoint);
      }
      if (super.cachedTimeout != null) {
        _call.setTimeout(super.cachedTimeout);
      }
      if (super.cachedPortName != null) {
        _call.setPortName(super.cachedPortName);
      }
      java.util.Enumeration keys = super.cachedProperties.keys();
      while (keys.hasMoreElements()) {
        java.lang.String key = (java.lang.String) keys.nextElement();
        if(_call.isPropertySupported(key))
          _call.setProperty(key, super.cachedProperties.get(key));
        else
          _call.setScopedProperty(key, super.cachedProperties.get(key));
      }
      return _call;
    }
    catch (java.lang.Throwable t) {
      throw new org.apache.axis.AxisFault("Failure trying to get the Call object", t);
    }
  }

  public byte[] getJarBytes() throws java.rmi.RemoteException {
    if (super.cachedEndpoint == null) {
      throw new org.apache.axis.NoEndPointException();
    }
    org.apache.axis.client.Call _call = createCall();
    _call.setReturnType(new javax.xml.namespace.QName("http://www.w3.org/2001/XMLSchema", "base64Binary"), byte[].class);
    _call.setUseSOAPAction(true);
    _call.setSOAPActionURI("");
    _call.setOperationStyle("rpc");
    _call.setOperationName(new javax.xml.namespace.QName("http://pcworldi.ipowermysql.com:8080/axis/GridConnection.jws", "getJarBytes"));

    java.lang.Object _resp = _call.invoke(new java.lang.Object[] {});

    if (_resp instanceof java.rmi.RemoteException) {
      throw (java.rmi.RemoteException)_resp;
    }
    else {
      try {
        return (byte[]) _resp;
      } catch (java.lang.Exception _exception) {
        return (byte[]) org.apache.axis.utils.JavaUtils.convert(_resp, byte[].class);
      }
    }
  }

}

اين كدها بايد با برنامه كلاينت لحاظ شوند آنها كلاسهاي لازم براي اتصال به انتهاي SOAP را فراهم مي‌آورند و ClassLoader سفارشي از آنها استفاده خواهد نمود.

 

ساخت ClassLoader سفارشي

ClassLoader سفارشي مهمترين جز برنامه كلاينت گريد است - كه فايل jar را از طريق سرويس SOAP بازيابي نموده و سپس كلاسهاي لحاظ شده در JAR را براي اجرا در دسترس قرار مي‌دهد. من با ساختار ClassLoader مستحكمي كه در ابتدا توسط كن‌مك‌كراري در Creat a Custom Java 1.2- style ClassLoader منتشر گرديد، آغاز نمودم. سپس ClassLoader را با كد ويژه‌اي براي برنامه كامل كردم. كد اصلي Grid Connection ClassLoader.Java در ذيل آمده است.

 

//
//  GridConnectionClassLoader.java
//
//  Created by Anthony Karre on
Wed Dec 11 2002.
//  Copyright (c) 2002 Perficient. All rights reserved, except for Ken McCrary stuff.
//
//  Structure of this class was kick-started by published Ken McCrary examples:
//  Here is his copyright notice:
//
/* Copyright (c) 1999 Ken McCrary, All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for NON-COMMERCIAL purposes and without
* fee is hereby granted provided that this copyright notice
* appears in all copies.
*
* KEN MCCRARY MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE
* SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING
* BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. KEN MCCRARY
* SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT
* OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
*/


package com.perficient.grid.client ;

import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.zip.* ;
import java.util.jar.Manifest;
import java.util.jar.JarInputStream ;
import java.util.jar.JarFile ;
import java.util.jar.JarEntry ;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import java.util.Hashtable ;
import java.io.* ;
import java.net.URL ;

public class GridConnectionClassLoader extends ClassLoader {

  // Provide normal constructor.
  
  GridConnectionClassLoader() {
    super();
    init();
  }
  
  // Provide delegation constructor.

  GridConnectionClassLoader(ClassLoader parent) {
    super(parent);
    init();
  }

در اينجا اولين متد آمده است: .init() اين متد با استفاده از SOAP stubs، سعي دارد به انتهاي SOAP متصل شود. بدين ترتيب آرايه بابت محتوي فايل Jar را استخراج مي‌كند. اين متد دو فعاليت ديگر را نيز انجام مي‌دهد. مشخص نمودن اندازه هر منبع JAR به بايت و شناسايي كلاس اصلي كه بايد اجرا شود.

در اين طراحي بايد كلاس اصلي آبجكت thread جاوا باشد. و از طريق صفت Task-Thread (مشابه با مفهوم صفت Main-Class كه در كلاسهاي اصلي ايستا به كار مي‌رود) شناسايي شود.

 

  //******************************************
  // Initialize the ClassLoader by using Web

  // services (SOAP) to fetch our JAR.
  // This jar file will contain the classes that

  // compose our grid task.
  // *****************************************
  
  private void init() {
  
    byte[] localjarbytes = null ;
    File tempfile = null ;
    FileInputStream tempfis = null ;
    JarInputStream jis = null ;
    
    try {
    
      // Make a SOAP service to start the process of fetching the JAR.
        
      com.perficient.grid.client.GridConnectionService service =
        new com.perficient.grid.client.GridConnectionServiceLocator() ;
          
      // Now use the service to get a stub to the SOAP service itself.
        
      com.perficient.grid.client.GridConnection gc = service.getGridConnection() ;
        
      // Make the actual call and fetch the bytes.
          
      localjarbytes = gc.getJarBytes() ;
          
      //  If localjarbytes is null, then the grid connection failed.  One possibility
      //  is that a jar file could not be found or loaded by the grid server.
      //  Print a message and exit.
          
      if (localjarbytes == null) {
        System.out.println("Class Loader: The grid connection returned no bytes.  No Jar file is currently available.") ;
        return ;
      }
      
      //  At this point we can store our bytes into a temp file.  This temp file
      //  will be a repository for the JAR, allowing the classloader to fetch
      //  classes as necessary.
        
      System.out.println("Class Loader: the grid connection returned " + localjarbytes.length + " bytes") ;
      
      // Save the size of the JAR for later use.
      
      this.jarsize = localjarbytes.length ;
          
      try {
          
        //  Create a temp jar file for these bytes.  Having an actual file will
        //  allow us to retrieve various resources more easily.
          
        tempfile = File.createTempFile("grid", ".jar") ;
          
        //  Make sure our file is deleted when the VM exits.
          
        tempfile.deleteOnExit() ;
          
        // Get an output stream and dump our bytes to the file.
          
        FileOutputStream tempfileos = new FileOutputStream(tempfile) ;
        tempfileos.write(localjarbytes) ;
        tempfileos.close() ;
            
      }
      catch (IOException e) {
        System.out.println("Class Loader: Problem with temp Jar file: " + e.getMessage()) ;
      }
        
          
    }
    catch (Exception e) {
      System.out.println("Class Loader: a grid connection failure occurred:\n" + e.toString() + "\n" + e.getMessage()) ;
    }

      
    try {

      // Save the filename for later reference.
      
      jarfilename = new String(tempfile.getCanonicalPath()) ;
      
      // Try to get a manifest, if one exists.  This will
      // let us know what the main task thread class is.
      // The main task thread is the primary work unit
      // that we will be executing in our grid.
        
      tempfis = new FileInputStream(tempfile) ;
      jis = new JarInputStream(tempfis) ;
            
      manifest = jis.getManifest() ;
      Attributes attr = manifest.getMainAttributes();
            
      if (attr != null) {
        taskthread = attr.getValue("Task-Thread") ;
      }
        
      // Now scan all of the entries in the file and
      // get the size of each resource/class.  This is the
      // only way to get the true size when the JAR is compressed.
        
      ZipFile zipfile = new ZipFile(tempfile);
      enum = zipfile.entries() ;
                            
      while (enum.hasMoreElements()) {          
        ZipEntry ze = (ZipEntry) enum.nextElement() ;
        htSizes.put(ze.getName(), new Integer((int)ze.getSize())) ;
      }

    } // Try.
      
    catch (Exception e) {
      System.out.println("Class Loader: " + e.toString() + " : " + e.getMessage()) ;
    }
    finally {
      if ( tempfis != null ) {
        try {
          tempfis.close();
        }
        catch (Exception e){}
      }
      if ( jis != null ) {
        try {
          jis.close();
        }
        catch (Exception e){}
      }
        
    } // Finally.
        
  }

 

متد findclass() همانگونه كه در ذيل آمده است، توسط ClassLoader براي رديابي و بارگذاري كلاس خاصي بكار مي‌رود. من در اين پياده‌سازي كلاس مورد نظر را در فايل Jar جستجو مي‌كنم. در صورتي كه ان را نيابم، از ClassNot Found Exception استاندارد استفاده مي‌كنم.

 

  /**
   *  This is the method where the task of classloading
   *  is delegated to our custom loader.
   *
   * @param  name the name of the class
   * @return the resulting Class object
   * @exception ClassNotFoundException if the class could not be found
   */
  
  protected Class findClass(String name) throws ClassNotFoundException {
  
    FileInputStream fis = null ;
    ZipInputStream zis = null ;
  
    try {
    
      String path = name.replace('.', '/') + ".class" ;
      fis = new FileInputStream(jarfilename) ;
      zis = new ZipInputStream(fis) ;
      ZipEntry ze = null ;
      boolean classfound = false ;
      
      while ((ze = zis.getNextEntry()) != null) {
      
        if (path.equals(ze.getName())) {
        
          classfound = true ;
        
          // Get the size of the classfile so we know how many bytes
          // to read.  If the size appears to be -1, then the
          // jar file is compressed, and we need to consult our
          // hashtable to get the uncompressed size.
          
          int size = (int) ze.getSize() ;
          
          if (size == -1) {
            size = ((Integer) htSizes.get(path)).intValue() ;
          }
          
          // Now that we know the actual size of the class,
          // fetch the data.
          
          byte[] classBytes = new byte[size] ;
          int rb = 0 ;
          int chunk = 0 ;
          
          while ((size - rb) > 0) {
            chunk = zis.read(classBytes, rb, size - rb) ;
            if (chunk == -1) {break ;}
            rb += chunk ;
          }
          
          // Now that we have our class data, define the class.
          
          System.out.println ("Class Loader: Found class " + name) ;
          definePackage(name);
          return defineClass(name, classBytes, 0, classBytes.length);

        }
        
      }
      
      if (!classfound) {
        // We apparently failed to locate the class within the jar file,
        // so indicate the problem with an exception.
        
        throw new ClassNotFoundException(name) ;
      }
      
    }
    catch (Exception e) {
      // We could not find the class, so indicate the problem with an exception.
      throw new ClassNotFoundException(name);
    }
    finally {
      if ( fis != null ) {
        try {
          fis.close();
        }
        catch (Exception e){}
      }
      if ( zis != null ) {
        try {
          zis.close();
        }
        catch (Exception e){}
      }
    }
    
    return null ;
  }

 

متدهاي find Resource()، find Resurces() همانگونه كه در ذيل آمده‌اند، براي يافتن منابعي كه احتمالا در JAR هستند، به كار مي‌روند. از آنجاييكه همه منابع در JARهاي محصور شده، بسته‌بندي شده‌اند نه در فايل سيستم، در دسترس URL قرار دارند، لذا اين متدها همواره سودمند نيستند.

 

  /**
   *  Identify where to load a resource from.
   *  Normally the resources will be extracted

   * from a JAR and reside in a filesystem

   * somewhere.  Since we are loading our classes

   * directly from a jar file, they won't really

   * be available via URL.
   *
   *  We'll always return a null to indicate
   *  that the resource can't be found, i.e.,

   * unavailable.
   *  You could also dump all of the resources

   * into a local/temp filesystem and "find" them

   * there.
   *
   *  @param name the resource name
   *  @return URL for resource or null if not

   * found
   */
  
  protected URL findResource(String name) {
    return null;
  }

  /**
   *  Used for identifying resources from multiple URLS.
   *  We will return a null for the same reasons as above.
   *
   *  @param name the resource name
   *  @return Enumeration of one URL
   */
  
  protected Enumeration findResources(final String name) throws IOException {
    return null ;  
  }

 

متد defindpackage()، متد استاندارد نهايي را براي ClassLoader ما فراهم مي‌كند و از مثال كن مك كراري، بدون تغيير اقتباس شده است:

 

  /**
   *  Minimal package definition
   *
   */
  
  private void definePackage(String className) {
  
    // Extract the package name from the class name.
    
    String pkgName = className;
    int index = className.lastIndexOf('.');
    
    if (index != -1) {
      pkgName =  className.substring(0, index);
    }

    // Preconditions -- need a manifest and the package
    // is not previously defined.
    
    if ( null == manifest ||
      getPackage(pkgName) != null) {
        return;
    }

    String specTitle,
           specVersion,
           specVendor,
           implTitle,
           implVersion,
           implVendor;

    // Look up the versioning information.
    // This should really look for a named attribute.
    
    Attributes attr = manifest.getMainAttributes();

    if (attr != null) {
      specTitle   = attr.getValue(Name.SPECIFICATION_TITLE);
      specVersion = attr.getValue(Name.SPECIFICATION_VERSION);
      specVendor  = attr.getValue(Name.SPECIFICATION_VENDOR);
      implTitle   = attr.getValue(Name.IMPLEMENTATION_TITLE);
      implVersion = attr.getValue(Name.IMPLEMENTATION_VERSION);
      implVendor  = attr.getValue(Name.IMPLEMENTATION_VENDOR);

      definePackage(pkgName,
                    specTitle,
                    specVersion,
                    specVendor,
                    implTitle,
                    implVersion,
                    implVendor,
                    null); // No sealing for simplicity.
    }
  }

 

سه متدي كه در ذيل آمده‌اند، ClassLoaderمان را كامل مي‌كنند. متد get Task Thread Name() نام thread فعاليتي كه مايلم اجرا شود (مبتني بر اطلاعات manifest) باز مي‌گرداند. متد isloaded مشخص مي‌كند كه آيا ClassLoader با موفقيت واكنشي و در فايل Jar بارگذاري شده است يا خير. كلاس main  از اين متد براي تعيين در دسترس بودن thread فعاليت اصلي‌ كه بايد اجرا شود، استفاده مي‌كند. متد getResourceEntries()، شماره اسامي منبع را در JAR بر مي‌گرداند و براي خطايابي بكار مي‌رود.

 

  //  The getTaskThreadName is a getter for the taskthread field.
  
  public String getTaskThreadName() {
    return this.taskthread ;
  }
  
  // If a JAR has been loaded, then we will have
  // a nonzero jarsize.
  
  public boolean isLoaded() {
    return (this.jarsize > 0) ;
  }
  
  //  getResourceEntries returns an enumeration of the keys
  //  in the hashtable.  The keys are the names of the JAR
  //  entries we picked up earlier.  This is useful for
  //  printing the list of JAR resources.
  
  public Enumeration getResourceEntries() {
    return htSizes.keys() ;
  }
    
  
  
  private Manifest manifest = null ;
  private String taskthread = null ;
  private String jarfilename = null ;
  private int jarsize = 0 ;
  private Enumeration enum = null ;
  private Hashtable htSizes = new Hashtable() ;

 

ساخت برنامه اصلي كلاينت

اين برنامه مسوول آغاز نمودن فوري Classloader است، سپس از آن براي بارگذاري thread فعاليت اصلي استفاده مي‌كند. من در اين مثال thread را به يكباره بارگذاري و اجرا مي‌كنم. توليد برنامه تلاش براي آغاز مجدد thread به محض اينكه اجرا مي‌شود يا حتي آبجكت ClassLoader را خراب مي‌كند و بارگذاري thread جديد است. (احتمالا با واكنشي JAR جديد) كد برنامه اصلي در ذيل آمده است.

//
//  GridClientMain.java
//

package com.perficient.grid.client ;

import java.util.*;
import java.io.* ;

public class GridClientMain {

  public static void main (String args[]) {
    
    // Start by creating our custom classloader. The classloader, during
    // initialization, will use Web services to attempt to download a JAR
    // containing the grid task we will execute.
        
    try {
      GridConnectionClassLoader gcloader = new GridConnectionClassLoader () ;
          
      //  Check to see if any bytes of data were received.  If the classloader is not loaded,
      //  then the classloader was unable to initialize properly.
          
      if (!gcloader.isLoaded()) {
        System.out.println("GridClientMain: No bytes were loaded by the classloader, so no grid task is available to be executed.") ;
      }
          
      //  A JAR has been loaded by the classloader, but
      //  the JAR manifest may not have a registered Task-Thread attribute.  This means
      //  that we have no way of knowing which class we are supposed to run.
      //  If our classloader does not have a Task-Thread name, print a message,
      //  then print the list of resources found in the JAR for reference.
          
      else if (gcloader.getTaskThreadName() == null) {
        System.out.println("GridClientMain: The Jar manifest did not have a Task-Thread attribute.\nPlease identify the Task-Thread and re-jar the data.  Here are the known Jar resources:\n") ;
            
        Enumeration enum = gcloader.getResourceEntries() ;
    
        while (enum.hasMoreElements()) {
          System.out.println((String) enum.nextElement()) ;
        }
            
      }
      else {
          
        // We have a Task-Thread, so let's try to load it.  Again, note that we assume
        // that the Task-Thread class is a Thread class.  This is a design decision for building
        // the grid.
            
        System.out.println("GridClientMain: Attempting to load " + gcloader.getTaskThreadName()) ;
        Thread task = (Thread) gcloader.loadClass(gcloader.getTaskThreadName()).newInstance();
        System.out.println("GridClientMain: Attempting to start Thread...") ;
        task.start();
      }
    }
    catch (ClassNotFoundException e) {
      System.out.println("GridClientMain: Could not find class:\n" + e.toString() + "\n" + e.getMessage()) ;
    }
    catch (Exception e) {
      System.out.println("GridClientMain: exception:\n" + e.toString() + "\n" + e.getMessage()) ;
    }
                
  }  // Main.
    
}

ساخت thread محاسباتي آزمايشي

من براي آزمايش چارچوب گرير به thread محاسباتي آزمايشي نياز دارم. البته لازم نيست اين thread كاري انجام دهد، اما بايد به گونه‌اي ساخته شود كه بتواند Classloader را آزمايش كند. Thread، آزمايشي را به گونه‌اي طراحي نمودم كه از چند كلاس استفاده كند و حداقل يك كلاس داخلي داشته باشد. وقتي برنامه اصلي را اجرا مي‌كنم، بايد ClassLoader سفارشي در صورت لزوم درخواست شود. كد thread آزمايشي در ذيل آمده است.

//
//  TestThread.java
//

package com.perficient.tasks ;

import java.util.*;
import com.perficient.tasks.TestThreadHelper ;

public class TestThread extends Thread {

  public TestThread () {
    super() ;
  }

  public void run() {

    System.out.println("TestThread: You are now inside the TestThread thread.");
    System.out.println("TestThread: Accessing another class in this jar...") ;
      
    // Demonstrate the ability to access another class in the
    // same jar file.
      
    try {
      TestThreadHelper tth = new TestThreadHelper() ;      
      System.out.println("TestThread: tth.getDemoString() returned " + tth.getDemoString()) ;
    }
    catch (Exception e) {
      System.out.println("TestThread: Failed to create TestThreadHelper.") ;
      System.out.println(e.toString() + " : " + e.getMessage()) ;
    }
      
    System.out.println("TestThread: Exiting TestThread thread.") ;
  }

}

//
//  TestThreadHelper.java
//  TestThread
//
//  Created by Anthony Karre on Sun Jan 05 2003.
//  Copyright (c) 2003 Perficient. All rights reserved.
//

package com.perficient.tasks ;

public class TestThreadHelper {

  public String getDemoString () {
  
    // demonstrate correct inner class load
    
    TTHInnerClass myinner = new TTHInnerClass() ;    
    return myinner.getInnerClassString() ;
  }
  
  class TTHInnerClass {
  
    public String getInnerClassString () {
      return "innerclassworked" ;
    }
  
  }

}

به دليل اينكه از Apple Mac OS X Project Builder IDE براي ساخت اين برنامه استفاده نمودم، JAR آزمايشي حاوي، فايل متني ديگري به نام mainfest.txt است. شما بايد با اين ابزار به وضوح فايل مبدايي حاوي اطلاعات mainfest كه مثلا Project Builder قبلا آن را فراهم نكرده، تهيه كنيد. البته در صورتي كه بخواهيد داده‌هاي اضافي ديگري را در mainfest ادغام كنيد. اين فايل يك خطي به صورت ذيل است:

Task-Thread: com.perficient.tasks.TestThread

 

 

شكل 5. خروجي كنسول جاوا

 

 

آزمايش چارچوب محاسباتی شبكه‌ای

من اين چارچوب را با روشن كردن سرور Tomcat/Axis SOAP بر روي مكينتاش آزمايش نمودم سپس برنامه اصلي را از Apple Mac OS X project Builder IDE اجرا كردم. شرايط متعدد بروز خطا را نظير عدم وجود JARها، عدم وجود صفات Task-Thread و مشكلات. SOAP كه در اثر خطاهاي ارتباطي حاصل مي‌شوند را شبيه سازي كردم. Classloader در همه موارد همانگونه كه طراحي شده بود، رفتار كرد. شكل 5 اجراي عادي برنامه كلاينت گرير را نشان مي‌دهد. به نحوه درخواست ClassLoader براي پشتيباني از آغاز نمودن كلاس در thread توجه كنيد.

 

آغاز تجربه با محاسبات شبكه‌ای

در حاليكه تول‌كيت‌هاي محاسبات شبكه‌اي موجود و اثربخش هستند، پياده‌سازي آنها نيازمند تجربه است. شايد منحني يادگيري براي توسعه‌دهندگاني كه مي‌خواهند به سرعت معماري محاسباتي شبكه‌اي را آزمايش كنند، مانعي به شمار رود چارچوب محاسبات شبكه‌اي كه در اين مقاله تشريح شد، ابزار ساده‌اي براي تجربه اصول اينگونه محاسبات است. اين ابزار از مولفه‌هاي open-source متداول كه براي اكثر برنامه‌نويسان جاوا آشناست، استفاده مي‌كند، در حاليكه اصول اين محاسبات يعني: استقلال ماشين، اجراي فعاليت انتزاعي و زير ساختار امن و اندازه‌پذير را حفظ مي‌نمايد. t

 

 

منابع:

·         Learn about the Search for Extraterrestrial Intelligence (SETI@home) project:
http://setiathome.ssl.berkeley.edu/

·         Global Grid Forum:
http://www.ggf.org/

·         Open Grid Services Architecture Working Group (OGSA WG):
http://www.ggf.org/ogsa-wg/

·         The Globus Project:
http://www.globus.org

·         Sun Microsystems Research Paper: "Framework for Peer-to-Peer Distributed Computing in a Heterogeneous, Decentralized Environment," Jerome Verbeke, Neelakanth Nadgir, Greg Ruetsch, Ilya Sharapov:
http://wwws.sun.com/software/jxta/mdejxta-paper.pdf

·         Project Jxta:
http://wwws.sun.com/software/jxta

·         Apache Tomcat homepage:
http://jakarta.apache.org/tomcat/

·         SOAP W3C (World Wide Web Consortium) specification:
http://www.w3.org/TR/SOAP/

·         The Apache Axis SOAP implementation:
http://ws.apache.org/axis/

·         "Create a Custom Java 1.2-Style ClassLoader," Ken McCrary (JavaWorld, March 2000):
http://www.javaworld.com/javaworld/jw-03-2000/jw-03-classload.html

·         The Apache Struts Web application framework:
http://jakarta.apache.org/struts/

·         Apple Mac OS X Project Builder IDE:
http://developer.apple.com/tools/projectbuilder/index.html

·         Learn more about Macintosh OS X development:
http://developer.apple.com/macosx/

·         More JavaWorld stories on Axis:

o        "Axis-Orizing Objects for SOAP," Mitch Gitman (April 2003)

o        "Axis: The Next Generation of Apache SOAP," Tarak Modi (January 2002)

·         For SOAP basics, read "Clean Up Your Wire Protocol with SOAP," Tarak Modi (JavaWorld)

o        "Part 1: An introduction to SOAP basics" (March 2001)

o        "Part 2: Use Apache SOAP to create SOAP-based applications" (April 2001)

o        "Part 3: Create SOAP services in Apache SOAP with JavaScript" (June 2001)

o        "Part 4: Dynamic proxies make Apache SOAP client development easy" (July 2001)

·         For an overview of grid computing, read "IBM's Grid Conversion," Robert McMillan (JavaWorld, September 2002):
http://www.javaworld.com/javaworld/jw-09-2002/jw-0906-grid.html

·         Browse the Enterprise Java section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-enterprise-index.shtml

·         Browse the Java and Web Services section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-webserv-index.shtml