程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java服務器應用

java服務器應用

編輯:關於JAVA

現在討論一下服務器應用(程序)的問題,我把它叫作NameCollecor(名字收集器)。假如多名用戶同時嘗試提交他們的E-mail地址,那麼會發生什麼情況呢?若NameCollector使用TCP/IP套接字,那麼必須運用早先介紹的多線程機制來實現對多個客戶的並發控制。但所有這些線程都試圖把數據寫到同一個文件裡,其中保存了所有E-mail地址。這便要求我們設立一種鎖定機制,保證多個線程不會同時訪問那個文件。一個“信號機”可在這裡幫助我們達到目的,但或許還有一種更簡單的方式。
如果我們換用數據報,就不必使用多線程了。用單個數據報即可“偵聽”進入的所有數據報。一旦監視到有進入的消息,程序就會進行適當的處理,並將答復數據作為一個數據報傳回原先發出請求的那名接收者。若數據報半路上丟失了,則用戶會注意到沒有答復數據傳回,所以可以重新提交請求。
服務器應用收到一個數據報,並對它進行解讀的時候,必須提取出其中的電子函件地址,並檢查本機保存的數據文件,看看裡面是否已經包含了那個地址(如果沒有,則添加之)。所以我們現在遇到了一個新的問題。Java 1.0似乎沒有足夠的能力來方便地處理包含了電子函件地址的文件(Java 1.1則不然)。但是,用C輕易就可以解決這個問題。因此,我們在這兒有機會學習將一個非Java程序同Java程序連接的最簡便方式。程序使用的Runtime對象包含了一個名為exec()的方法,它會獨立機器上一個獨立的程序,並返回一個Process(進程)對象。我們可以取得一個OutputStream,它同這個單獨程序的標准輸入連接在一起;並取得一個InputStream,它則同標准輸出連接到一起。要做的全部事情就是用任何語言寫一個程序,只要它能從標准輸入中取得自己的輸入數據,並將輸出結果寫入標准輸出即可。如果有些問題不能用Java簡便與快速地解決(或者想利用原有代碼,不想改寫),就可以考慮采用這種方法。亦可使用Java的“固有方法”(Native Method),但那要求更多的技巧,大家可以參考一下附錄A。

1. C程序
這個非Java應用是用C寫成,因為Java不適合作CGI編程;起碼啟動的時間不能讓人滿意。它的任務是管理電子函件(E-mail)地址的一個列表。標准輸入會接受一個E-mail地址,程序會檢查列表中的名字,判斷是否存在那個地址。若不存在,就將其加入,並報告操作成功。但假如名字已在列表裡了,就需要指出這一點,避免重復加入。大家不必擔心自己不能完全理解下列代碼的含義。它僅僅是一個演示程序,告訴你如何用其他語言寫一個程序,並從Java中調用它。在這裡具體采用何種語言並不重要,只要能夠從標准輸入中讀取數據,並能寫入標准輸出即可。

 

//: Listmgr.c
// Used by NameCollector.java to manage 
// the email list file on the server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BSIZE 250

int alreadyInList(FILE* list, char* name) {
  char lbuf[BSIZE];
  // Go to the beginning of the list:
  fseek(list, 0, SEEK_SET);
  // Read each line in the list:
  while(fgets(lbuf, BSIZE, list)) {
    // Strip off the newline:
    char * newline = strchr(lbuf, '\n');
    if(newline != 0) 
      *newline = '\0';
    if(strcmp(lbuf, name) == 0)
      return 1;
  }
  return 0;
}

int main() {
  char buf[BSIZE];
  FILE* list = fopen("emlist.txt", "a+t");
  if(list == 0) {
    perror("could not open emlist.txt");
    exit(1);
  }
  while(1) {
    gets(buf); /* From stdin */
    if(alreadyInList(list, buf)) {
      printf("Already in list: %s", buf);
      fflush(stdout);
    }
    else {
      fseek(list, 0, SEEK_END);
      fprintf(list, "%s\n", buf);
      fflush(list);
      printf("%s added to list", buf);
      fflush(stdout);
    }
  }
} ///:~


該程序假設C編譯器能接受'//'樣式注釋(許多編譯器都能,亦可換用一個C++編譯器來編譯這個程序)。如果你的編譯器不能接受,則簡單地將那些注釋刪掉即可。
文件中的第一個函數檢查我們作為第二個參數(指向一個char的指針)傳遞給它的名字是否已在文件中。在這兒,我們將文件作為一個FILE指針傳遞,它指向一個已打開的文件(文件是在main()中打開的)。函數fseek()在文件中遍歷;我們在這兒用它移至文件開頭。fgets()從文件list中讀入一行內容,並將其置入緩沖區lbuf——不會超過規定的緩沖區長度BSIZE。所有這些工作都在一個while循環中進行,所以文件中的每一行都會讀入。接下來,用strchr()找到新行字符,以便將其刪掉。最後,用strcmp()比較我們傳遞給函數的名字與文件中的當前行。若找到一致的內容,strcmp()會返回0。函數隨後會退出,並返回一個1,指出該名字已經在文件裡了(注意這個函數找到相符內容後會立即返回,不會把時間浪費在檢查列表剩余內容的上面)。如果找遍列表都沒有發現相符的內容,則函數返回0。
在main()中,我們用fopen()打開文件。第一個參數是文件名,第二個是打開文件的方式;a+表示“追加”,以及“打開”(或“創建”,假若文件尚不存在),以便到文件的末尾進行更新。fopen()函數返回的是一個FILE指針;若為0,表示打開操作失敗。此時需要用perror()打印一條出錯提示消息,並用exit()中止程序運行。
如果文件成功打開,程序就會進入一個無限循環。調用gets(buf)的函數會從標准輸入中取出一行(記住標准輸入會與Java程序連接到一起),並將其置入緩沖區buf中。緩沖區的內容隨後會簡單地傳遞給alreadyInList()函數,如內容已在列表中,printf()就會將那條消息發給標准輸出(Java程序正在監視它)。fflush()用於對輸出緩沖區進行刷新。
如果名字不在列表中,就用fseek()移到列表末尾,並用fprintf()將名字“打印”到列表末尾。隨後,用printf()指出名字已成功加入列表(同樣需要刷新標准輸出),無限循環返回,繼續等候一個新名字的進入。
記住一般不能先在自己的計算機上編譯此程序,再把編譯好的內容上載到Web服務器,因為那台機器使用的可能是不同類的處理器和操作系統。例如,我的Web服務器安裝的是Intel的CPU,但操作系統是Linux,所以必須先下載源碼,再用遠程命令(通過telnet)指揮Linux自帶的C編譯器,令其在服務器端編譯好程序。

2. Java程序
這個程序先啟動上述的C程序,再建立必要的連接,以便同它“交談”。隨後,它創建一個數據報套接字,用它“監視”或者“偵聽”來自程序片的數據報包。

 

//: NameCollector.java
// Extracts email names from datagrams and stores
// them inside a file, using Java 1.02.
import java.net.*;
import java.io.*;
import java.util.*;

public class NameCollector {
  final static int COLLECTOR_PORT = 8080;
  final static int BUFFER_SIZE = 1000;
  byte[] buf = new byte[BUFFER_SIZE];
  DatagramPacket dp = 
    new DatagramPacket(buf, buf.length);
  // Can listen & send on the same socket:
  DatagramSocket socket;
  Process listmgr;
  PrintStream nameList;
  DataInputStream addResult;
  public NameCollector() {
    try {
      listmgr =
        Runtime.getRuntime().exec("listmgr.exe");
      nameList = new PrintStream(
        new BufferedOutputStream(
          listmgr.getOutputStream()));
      addResult = new DataInputStream(
        new BufferedInputStream(
          listmgr.getInputStream()));

    } catch(IOException e) {
      System.err.println(
        "Cannot start listmgr.exe");
      System.exit(1);
    }
    try {
      socket =
        new DatagramSocket(COLLECTOR_PORT);
      System.out.println(
        "NameCollector Server started");
      while(true) {
        // Block until a datagram appears:
        socket.receive(dp);
        String rcvd = new String(dp.getData(),
            0, 0, dp.getLength());
        // Send to listmgr.exe standard input:
        nameList.println(rcvd.trim());
        nameList.flush();
        byte[] resultBuf = new byte[BUFFER_SIZE];
        int byteCount = 
          addResult.read(resultBuf);
        if(byteCount != -1) {
          String result = 
            new String(resultBuf, 0).trim();
          // Extract the address and port from 
          // the received datagram to find out 
          // where to send the reply:
          InetAddress senderAddress =
            dp.getAddress();
          int senderPort = dp.getPort();
          byte[] echoBuf = new byte[BUFFER_SIZE];
          result.getBytes(
            0, byteCount, echoBuf, 0);
          DatagramPacket echo =
            new DatagramPacket(
              echoBuf, echoBuf.length,
              senderAddress, senderPort);
          socket.send(echo);
        }
        else
          System.out.println(
            "Unexpected lack of result from " +
            "listmgr.exe");
      }
    } catch(SocketException e) {
      System.err.println("Can't open socket");
      System.exit(1);
    } catch(IOException e) {
      System.err.println("Communication error");
      e.printStackTrace();
    }
  }
  public static void main(String[] args) {
    new NameCollector();
  }
} ///:~


NameCollector中的第一個定義應該是大家所熟悉的:選定端口,創建一個數據報包,然後創建指向一個DatagramSocket的句柄。接下來的三個定義負責與C程序的連接:一個Process對象是C程序由Java程序啟動之後返回的,而且那個Process對象產生了InputStream和OutputStream,分別代表C程序的標准輸出和標准輸入。和Java IO一樣,它們理所當然地需要“封裝”起來,所以我們最後得到的是一個PrintStream和DataInputStream。
這個程序的所有工作都是在構建器內進行的。為啟動C程序,需要取得當前的Runtime對象。我們用它調用exec(),再由後者返回Process對象。在Process對象中,大家可看到通過一簡單的調用即可生成數據流:getOutputStream()和getInputStream()。從這個時候開始,我們需要考慮的全部事情就是將數據傳給數據流nameList,並從addResult中取得結果。
和往常一樣,我們將DatagramSocket同一個端口連接到一起。在無限while循環中,程序會調用receive()——除非一個數據報到來,否則receive()會一起處於“堵塞”狀態。數據報出現以後,它的內容會提取到String rcvd裡。我們首先將該字串兩頭的空格剔除(trim),再將其發給C程序。如下所示:
nameList.println(rcvd.trim());
之所以能這樣編碼,是因為Java的exec()允許我們訪問任何可執行模塊,只要它能從標准輸入中讀,並能向標准輸出中寫。還有另一些方式可與非Java代碼“交談”,這將在附錄A中討論。
從C程序中捕獲結果就顯得稍微麻煩一些。我們必須調用read(),並提供一個緩沖區,以便保存結果。read()的返回值是來自C程序的字節數。若這個值為-1,意味著某個地方出現了問題。否則,我們就將resultBuf(結果緩沖區)轉換成一個字串,然後同樣清除多余的空格。隨後,這個字串會象往常一樣進入一個DatagramPacket,並傳回當初發出請求的那個同樣的地址。注意發送方的地址也是我們接收到的DatagramPacket的一部分。
記住盡管C程序必須在Web服務器上編譯,但Java程序的編譯場所可以是任意的。這是由於不管使用的是什麼硬件平台和操作系統,編譯得到的字節碼都是一樣的。就是Java的“跨平台”兼容能力。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved