使用 libevent 和 libev 提升網絡應用性能

使用 libevent 和 libev 提升網絡應用性能

Martin C. Brown, 做家, Freelance

簡介: 構建現代的服務器應用程序須要以某種方法同時接收數百、數千甚至數萬個事件,不管它們是內部請求仍是網絡鏈接,都要有效地處理它們的操做。有許多解決方案,可是 libevent 庫和 libev 庫可以大大提升性能和事件處理能力。在本文中,咱們要討論在 UNIX® 應用程序中使用和部署這些解決方案所用的基本結構和方法。libev 和 libevent 均可以在高性能應用程序中使用,包括部署在 IBM Cloud 或 Amazon EC2 環境中的應用程序,這些應用程序須要支持大量併發客戶端或操做。 html

簡介前端

許多服務器部署(尤爲是 web 服務器部署)面對的最大問題之一是必須可以處理大量鏈接。不管是經過構建基於雲的服務來處理網絡通訊流,仍是把應用程序分佈在 IBM Amazon EC 實例上,仍是爲網站提供高性能組件,都須要可以處理大量併發鏈接。linux

一個好例子是,web 應用程序最近愈來愈動態了,尤爲是使用 AJAX 技術的應用程序。若是要部署的系統容許數千客戶端直接在網頁中更新信息,好比提供事件或問題實時監視的系統,那麼提供信息的速度就很是重要了。在網格或雲環境中,可能有來自數千客戶端的持久鏈接同時打開着,必須可以處理每一個客戶端的請求並作出響應。web

在討論 libevent 和 libev 如何處理多個網絡鏈接以前,咱們先簡要回顧一下處理這類鏈接的傳統解決方案。數據庫

 

處理多個客戶端後端

處理多個鏈接有許多不一樣的傳統方法,可是在處理大量鏈接時它們每每會產生問題,由於它們使用的內存或 CPU 太多,或者達到了某個操做系統限制。數組

使用的主要方法以下:緩存

  • 循環:早期系統使用簡單的循環選擇解決方案,即循環遍歷打開的網絡鏈接的列表,判斷是否有要讀取的數據。這種方法既緩慢(尤爲是隨着鏈接數量增長愈來愈慢),又低效(由於在處理當前鏈接時其餘鏈接可能正在發送請求並等待響應)。在系統循環遍歷每一個鏈接時,其餘鏈接不得不等待。若是有 100 個鏈接,其中只有一個有數據,那麼仍然必須處理其餘 99 個鏈接,才能輪到真正須要處理的鏈接。
  • poll、epoll 和變體:這是對循環方法的改進,它用一個結構保存要監視的每一個鏈接的數組,當在網絡套接字上發現數據時,經過回調機制調用處理函數。poll 的問題是這個結構會很是大,在列表中添加新的網絡鏈接時,修改結構會增長負載並影響性能。
  • 選擇select() 函數調用使用一個靜態結構,它事先被硬編碼爲至關小的數量(1024 個鏈接),所以不適用於很是大的部署。

在各類平臺上還有其餘實現(好比 Solaris 上的 /dev/poll 或 FreeBSD/NetBSD 上的 kqueue),它們在各自的 OS 上性能可能更好,可是沒法移植,也不必定可以解決處理請求的高層問題。ruby

上面的全部解決方案都用簡單的循環等待並處理請求,而後把請求分派給另外一個函數以處理實際的網絡交互。關鍵在於循環和網絡套接字須要大量管理代碼,這樣才能監聽、更新和控制不一樣的鏈接和接口。

處理許多鏈接的另外一種方法是,利用現代內核中的多線程支持監聽和處理鏈接,爲每一個鏈接啓動一個新線程。這把責任直接交給操做系統,可是會在 RAM 和 CPU 方面增長至關大的開銷,由於每一個線程都須要本身的執行空間。另外,若是每一個線程都忙於處理網絡鏈接,線程之間的上下文切換會很頻繁。最後,許多內核並不適於處理如此大量的活躍線程。

 

libevent 方法

libevent 庫實際上沒有更換 select()poll() 或其餘機制的基礎。而是使用對於每一個平臺最高效的高性能解決方案在實現外加上一個包裝器。

爲了實際處理每一個請求,libevent 庫提供一種事件機制,它做爲底層網絡後端的包裝器。事件系統讓爲鏈接添加處理函數變得很是簡便,同時下降了底層 I/O 複雜性。這是 libevent 系統的核心。

libevent 庫的其餘組件提供其餘功能,包括緩衝的事件系統(用於緩衝發送到客戶端/從客戶端接收的數據)以及 HTTP、DNS 和 RPC 系統的核心實現。

建立 libevent 服務器的基本方法是,註冊當發生某一操做(好比接受來自客戶端的鏈接)時應該執行的函數,而後調用主事件循環event_dispatch()。執行過程的控制如今由 libevent 系統處理。註冊事件和將調用的函數以後,事件系統開始自治;在應用程序運行時,能夠在事件隊列中添加(註冊)或刪除(取消註冊)事件。事件註冊很是方便,能夠經過它添加新事件以處理新打開的鏈接,從而構建靈活的網絡處理系統。

例如,能夠打開一個監聽套接字,而後註冊一個回調函數,每當須要調用 accept() 函數以打開新鏈接時調用這個回調函數,這樣就建立了一個網絡服務器。清單 1 所示的代碼片斷說明基本過程:


清單 1. 打開監聽套接字,註冊一個回調函數(每當須要調用  accept() 函數以打開新鏈接時調用它),由此建立網絡服務器
				
int main(int argc, char **argv)
{
...
    ev_init();

    /* Setup listening socket */

    event_set(&ev_accept, listen_fd, EV_READ|EV_PERSIST, on_accept, NULL);
    event_add(&ev_accept, NULL);

    /* Start the event loop. */
    event_dispatch();
}

event_set() 函數建立新的事件結構,event_add() 在事件隊列機制中添加事件。而後,event_dispatch() 啓動事件隊列系統,開始監聽(並接受)請求。

清單 2 給出一個更完整的示例,它構建一個很是簡單的回顯服務器:


清單 2. 構建簡單的回顯服務器
				
#include <event.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define SERVER_PORT 8080
int debug = 0;

struct client {
  int fd;
  struct bufferevent *buf_ev;
};

int setnonblock(int fd)
{
  int flags;

  flags = fcntl(fd, F_GETFL);
  flags |= O_NONBLOCK;
  fcntl(fd, F_SETFL, flags);
}

void buf_read_callback(struct bufferevent *incoming,
                       void *arg)
{
  struct evbuffer *evreturn;
  char *req;

  req = evbuffer_readline(incoming->input);
  if (req == NULL)
    return;

  evreturn = evbuffer_new();
  evbuffer_add_printf(evreturn,"You said %s\n",req);
  bufferevent_write_buffer(incoming,evreturn);
  evbuffer_free(evreturn);
  free(req);
}

void buf_write_callback(struct bufferevent *bev,
                        void *arg)
{
}

void buf_error_callback(struct bufferevent *bev,
                        short what,
                        void *arg)
{
  struct client *client = (struct client *)arg;
  bufferevent_free(client->buf_ev);
  close(client->fd);
  free(client);
}

void accept_callback(int fd,
                     short ev,
                     void *arg)
{
  int client_fd;
  struct sockaddr_in client_addr;
  socklen_t client_len = sizeof(client_addr);
  struct client *client;

  client_fd = accept(fd,
                     (struct sockaddr *)&client_addr,
                     &client_len);
  if (client_fd < 0)
    {
      warn("Client: accept() failed");
      return;
    }

  setnonblock(client_fd);

  client = calloc(1, sizeof(*client));
  if (client == NULL)
    err(1, "malloc failed");
  client->fd = client_fd;

  client->buf_ev = bufferevent_new(client_fd,
                                   buf_read_callback,
                                   buf_write_callback,
                                   buf_error_callback,
                                   client);

  bufferevent_enable(client->buf_ev, EV_READ);
}

int main(int argc,
         char **argv)
{
  int socketlisten;
  struct sockaddr_in addresslisten;
  struct event accept_event;
  int reuse = 1;

  event_init();

  socketlisten = socket(AF_INET, SOCK_STREAM, 0);

  if (socketlisten < 0)
    {
      fprintf(stderr,"Failed to create listen socket");
      return 1;
    }

  memset(&addresslisten, 0, sizeof(addresslisten));

  addresslisten.sin_family = AF_INET;
  addresslisten.sin_addr.s_addr = INADDR_ANY;
  addresslisten.sin_port = htons(SERVER_PORT);

  if (bind(socketlisten,
           (struct sockaddr *)&addresslisten,
           sizeof(addresslisten)) < 0)
    {
      fprintf(stderr,"Failed to bind");
      return 1;
    }

  if (listen(socketlisten, 5) < 0)
    {
      fprintf(stderr,"Failed to listen to socket");
      return 1;
    }

  setsockopt(socketlisten,
             SOL_SOCKET,
             SO_REUSEADDR,
             &reuse,
             sizeof(reuse));

  setnonblock(socketlisten);

  event_set(&accept_event,
            socketlisten,
            EV_READ|EV_PERSIST,
            accept_callback,
            NULL);

  event_add(&accept_event,
            NULL);

  event_dispatch();

  close(socketlisten);

  return 0;
}

下面討論各個函數及其操做:

  • main():主函數建立用來監聽鏈接的套接字,而後建立 accept() 的回調函數以便經過事件處理函數處理每一個鏈接。
  • accept_callback():當接受鏈接時,事件系統調用此函數。此函數接受到客戶端的鏈接;添加客戶端套接字信息和一個 bufferevent 結構;在事件結構中爲客戶端套接字上的讀/寫/錯誤事件添加回調函數;做爲參數傳遞客戶端結構(和嵌入的 eventbuffer 和客戶端套接字)。每當對應的客戶端套接字包含讀、寫或錯誤操做時,調用對應的回調函數。
  • buf_read_callback():當客戶端套接字有要讀的數據時調用它。做爲回顯服務,此函數把 "you said..." 寫回客戶端。套接字仍然打開,能夠接受新請求。
  • buf_write_callback():當有要寫的數據時調用它。在這個簡單的服務中,不須要此函數,因此定義是空的。
  • buf_error_callback():當出現錯誤時調用它。這包括客戶端中斷鏈接。在出現錯誤的全部場景中,關閉客戶端套接字,從事件列表中刪除客戶端套接字的事件條目,釋放客戶端結構的內存。
  • setnonblock():設置網絡套接字以開放 I/O。

當客戶端鏈接時,在事件隊列中添加新事件以處理客戶端鏈接;當客戶端中斷鏈接時刪除事件。在幕後,libevent 處理網絡套接字,識別須要服務的客戶端,分別調用對應的函數。

爲了構建這個應用程序,須要編譯 C 源代碼並添加 libevent 庫:$ gcc -o basic basic.c -levent

從客戶端的角度來看,這個服務器僅僅把發送給它的任何文本發送回來(見 清單 3)。


清單 3. 服務器把發送給它的文本發送回來
				
$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!
You said Hello!

這樣的網絡應用程序很是適合須要處理多個鏈接的大規模分佈式部署,好比 IBM Cloud 系統。

很難經過簡單的解決方案觀察處理大量併發鏈接的狀況和性能改進。可使用嵌入的 HTTP 實現幫助瞭解可伸縮性。

 

使用內置的 HTTP 服務器

若是但願構建本機應用程序,可使用通常的基於網絡的 libevent 接口;可是,愈來愈常見的場景是開發基於 HTTP 協議的應用程序,以及裝載或動態地從新裝載信息的網頁。若是使用任何 AJAX 庫,客戶端就須要 HTTP,即便您返回的信息是 XML 或 JSON。

libevent 中的 HTTP 實現並非 Apache HTTP 服務器的替代品,而是適用於與雲和 web 環境相關聯的大規模動態內容的實用解決方案。例如,能夠在 IBM Cloud 或其餘解決方案中部署基於 libevent 的接口。由於可使用 HTTP 進行通訊,服務器能夠與其餘組件集成。

要想使用 libevent 服務,須要使用與主要網絡事件模型相同的基本結構,可是還必須處理網絡接口,HTTP 包裝器會替您處理。這使整個過程變成四個函數調用(初始化、啓動 HTTP 服務器、設置 HTTP 回調函數和進入事件循環),再加上發送回數據的回調函數。清單 4 給出一個很是簡單的示例:


清單 4. 使用 libevent 服務的簡單示例
				
#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <event.h>
#include <evhttp.h>

void generic_request_handler(struct evhttp_request *req, void *arg)
{
  struct evbuffer *returnbuffer = evbuffer_new();

  evbuffer_add_printf(returnbuffer, "Thanks for the request!");
  evhttp_send_reply(req, HTTP_OK, "Client", returnbuffer);
  evbuffer_free(returnbuffer);
  return;
}

int main(int argc, char **argv)
{
  short          http_port = 8081;
  char          *http_addr = "192.168.0.22";
  struct evhttp *http_server = NULL;

  event_init();
  http_server = evhttp_start(http_addr, http_port);
  evhttp_set_gencb(http_server, generic_request_handler, NULL);

  fprintf(stderr, "Server started on port %d\n", http_port);
  event_dispatch();

  return(0);
}

應該能夠經過前面的示例看出代碼的基本結構,不須要解釋。主要元素是 evhttp_set_gencb() 函數(它設置當收到 HTTP 請求時要使用的回調函數)和 generic_request_handler() 回調函數自己(它用一個表示成功的簡單消息填充響應緩衝區)。

HTTP 包裝器提供許多其餘功能。例如,有一個請求解析器,它會從典型的請求中提取出查詢參數(就像處理 CGI 請求同樣)。還能夠設置在不一樣的請求路徑中要觸發的處理函數。經過設置不一樣的回調函數和處理函數,可使用路徑 '/db/' 提供到數據庫的接口,或使用 '/memc' 提供到 memcached 的接口。

libevent 工具包的另外一個特性是支持通用計時器。能夠在指定的時間段以後觸發事件。能夠經過結合使用計時器和 HTTP 實現提供輕量的服務,從而自動地提供文件內容,在修改文件內容時更新返回的數據。例如,之前要想在新聞頻發的活動期間提供即時更新服務,前端 web 應用程序就須要按期從新裝載新聞稿,而如今能夠輕鬆地提供內容。整個應用程序(和 web 服務)都在內存中,所以響應很是快。

這就是 清單 5 中的示例的主要用途:


清單 5. 使用計時器在新聞頻發的活動期間提供即時更新服務
				
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <event.h>
#include <evhttp.h>

#define RELOAD_TIMEOUT 5
#define DEFAULT_FILE "sample.html"

char *filedata;
time_t lasttime = 0;
char filename[80];
int counter = 0;

void read_file()
{
  int size = 0;
  char *data;
  struct stat buf;

  stat(filename,&buf);

  if (buf.st_mtime > lasttime)
    {
      if (counter++)
        fprintf(stderr,"Reloading file: %s",filename);
      else
        fprintf(stderr,"Loading file: %s",filename);

      FILE *f = fopen(filename, "rb");
      if (f == NULL)
        {
          fprintf(stderr,"Couldn't open file\n");
          exit(1);
        }

      fseek(f, 0, SEEK_END);
      size = ftell(f);
      fseek(f, 0, SEEK_SET);
      data = (char *)malloc(size+1);
      fread(data, sizeof(char), size, f);
      filedata = (char *)malloc(size+1);
      strcpy(filedata,data);
      fclose(f);


      fprintf(stderr," (%d bytes)\n",size);
      lasttime = buf.st_mtime;
    }
}

void load_file()
{
  struct event *loadfile_event;
  struct timeval tv;

  read_file();

  tv.tv_sec = RELOAD_TIMEOUT;
  tv.tv_usec = 0;

  loadfile_event = malloc(sizeof(struct event));

  evtimer_set(loadfile_event,
              load_file,
              loadfile_event);

  evtimer_add(loadfile_event,
              &tv);
}

void generic_request_handler(struct evhttp_request *req, void *arg)
{
  struct evbuffer *evb = evbuffer_new();

  evbuffer_add_printf(evb, "%s",filedata);
  evhttp_send_reply(req, HTTP_OK, "Client", evb);
  evbuffer_free(evb);
}

int main(int argc, char *argv[])
{
  short          http_port = 8081;
  char          *http_addr = "192.168.0.22";
  struct evhttp *http_server = NULL;

  if (argc > 1)
    {
      strcpy(filename,argv[1]);
      printf("Using %s\n",filename);
    }
  else
    {
      strcpy(filename,DEFAULT_FILE);
    }

  event_init();

  load_file();

  http_server = evhttp_start(http_addr, http_port);
  evhttp_set_gencb(http_server, generic_request_handler, NULL);

  fprintf(stderr, "Server started on port %d\n", http_port);
  event_dispatch();
}

這個服務器的基本原理與前面的示例相同。首先,腳本設置一個 HTTP 服務器,它只響應對基本 URL 主機/端口組合的請求(不處理請求 URI)。第一步是裝載文件 (read_file())。在裝載最初的文件時和在計時器觸發回調時都使用此函數。

read_file() 函數使用 stat() 函數調用檢查文件的修改時間,只有在上一次裝載以後修改了文件的狀況下,它才從新讀取文件的內容。此函數經過調用 fread() 裝載文件數據,把數據複製到另外一個結構中,而後使用 strcpy() 把數據從裝載的字符串轉移到全局字符串中。

load_file() 函數是觸發計時器時調用的函數。它經過調用 read_file() 裝載內容,而後使用 RELOAD_TIMEOUT 值設置計時器,做爲嘗試裝載文件以前的秒數。libevent 計時器使用 timeval 結構,容許按秒和毫秒指定計時器。計時器不是週期性的;當觸發計時器事件時設置它,而後從事件隊列中刪除事件。

使用與前面的示例相同的格式編譯代碼:$ gcc -o basichttpfile basichttpfile.c -levent

如今,建立做爲數據使用的靜態文件;默認文件是 sample.html,可是能夠經過命令行上的第一個參數指定任何文件(見 清單 6)。


清單 6. 建立做爲數據使用的靜態文件
				
$ ./basichttpfile
Loading file: sample.html (8046 bytes)
Server started on port 8081

如今,程序能夠接受請求了,從新裝載計時器也啓動了。若是修改 sample.html 的內容,應該會從新裝載此文件並在日誌中記錄一個消息。例如,清單 7 中的輸出顯示初始裝載和兩次從新裝載:


清單 7. 輸出顯示初始裝載和兩次從新裝載
				
$ ./basichttpfile
Loading file: sample.html (8046 bytes)
Server started on port 8081
Reloading file: sample.html (8047 bytes)
Reloading file: sample.html (8048 bytes)

注意,要想得到最大的收益,必須確保環境沒有限制打開的文件描述符數量。可使用 ulimit 命令修改限制(須要適當的權限或根訪問)。具體的設置取決與您的 OS,可是在 Linux® 上能夠用 -n 選項設置打開的文件描述符(和網絡套接字)的數量:


清單 8. 用  -n 選項設置打開的文件描述符數量
				
$ ulimit -n
1024

經過指定數字提升限制:$ ulimit -n 20000

可使用 Apache Bench 2 (ab2) 等性能基準測試應用程序檢查服務器的性能。能夠指定併發查詢的數量以及請求的總數。例如,使用 100,000 個請求運行基準測試,併發請求數量爲 1000 個:$ ab2 -n 100000 -c 1000 http://192.168.0.22:8081/

使用服務器示例中所示的 8K 文件運行這個示例系統,得到的結果爲大約每秒處理 11,000 個請求。請記住,這個 libevent 服務器在單一線程中運行,並且單一客戶端不太可能給服務器形成壓力,由於它還受到打開請求的方法的限制。儘管如此,在交換的文檔大小適中的狀況下,這樣的處理速率對於單線程應用程序來講仍然使人吃驚。

 

使用其餘語言的實現

儘管 C 語言很適合許多系統應用程序,可是在現代環境中不常用 C 語言,腳本語言更靈活、更實用。幸運的是,Perl 和 PHP 等大多數腳本語言是用 C 編寫的,因此能夠經過擴展模塊使用 libevent 等 C 庫。

例如,清單 9 給出 Perl 網絡服務器腳本的基本結構。accept_callback() 函數與 清單 1 所示核心 libevent 示例中的 accept 函數相同。


清單 9. Perl 網絡服務器腳本的基本結構
				
my $server = IO::Socket::INET->new(
    LocalAddr       => 'localhost',
    LocalPort       => 8081,
    Proto           => 'tcp',
    ReuseAddr       => SO_REUSEADDR,
    Listen          => 1,
    Blocking        => 0,
    ) or die $@;

my $accept = event_new($server, EV_READ|EV_PERSIST, \&accept_callback);

$main->add;

event_mainloop();

用這些語言編寫的 libevent 實現一般支持 libevent 系統的核心,可是不必定支持 HTTP 包裝器。所以,對腳本編程的應用程序使用這些解決方案會比較複雜。有兩種方法:要麼把腳本語言嵌入到基於 C 的 libevent 應用程序中,要麼使用基於腳本語言環境構建的衆多 HTTP 實現之一。例如,Python 包含功能很強的 HTTP 服務器類 (httplib/httplib2)。

應該指出一點:在腳本語言中沒有什麼東西是沒法用 C 從新實現的。可是,要考慮到開發時間的限制,並且與現有代碼集成可能更重要。

 

libev 庫

與 libevent 同樣,libev 系統也是基於事件循環的系統,它在 poll()select() 等機制的本機實現的基礎上提供基於事件的循環。到我撰寫本文時,libev 實現的開銷更低,可以實現更好的基準測試結果。libev API 比較原始,沒有 HTTP 包裝器,可是 libev 支持在實現中內置更多事件類型。例如,一種 evstat 實現能夠監視多個文件的屬性變更,能夠在 清單 4 所示的 HTTP 文件解決方案中使用它。

可是,libevent 和 libev 的基本過程是相同的。建立所需的網絡監聽套接字,註冊在執行期間要調用的事件,而後啓動主事件循環,讓 libev 處理過程的其他部分。

例如,可使用 Ruby 接口按照與清單 1 類似的方式提供回顯服務器,見 清單 10


清單 10. 使用 Ruby 接口提供回顯服務器
				
require 'rubygems'
require 'rev'

PORT = 8081

class EchoServerConnection < Rev::TCPSocket
  def on_read(data)
    write 'You said: ' + data
  end
end

server = Rev::TCPServer.new('192.168.0.22', PORT, EchoServerConnection)
server.attach(Rev::Loop.default)

puts "Listening on localhost:#{PORT}"
Rev::Loop.default.run

Ruby 實現尤爲出色,由於它爲許多經常使用的網絡解決方案提供了包裝器,包括 HTTP 客戶端、OpenSSL 和 DNS。其餘腳本語言實現包括功能全面的 Perl 和 Python 實現,您能夠試一試。

 

結束語

libevent 和 libev 都提供靈活且強大的環境,支持爲處理服務器端或客戶端請求實現高性能網絡(和其餘 I/O)接口。目標是以高效(CPU/RAM 使用量低)的方式支持數千甚至數萬個鏈接。在本文中,您看到了一些示例,包括 libevent 中內置的 HTTP 服務,可使用這些技術支持基於 IBM Cloud、EC2 或 AJAX 的 web 應用程序。


參考資料

學習

  • C10K problem 對處理 10,000 個鏈接的問題作了精彩的概述。 

  • IBM Cloud Computing 網站提供不一樣雲實現的相關信息。 

  • 閱讀 系統管理工具包: 標準化您的 UNIX 命令行工具(Martin Brown,developerWorks,2006 年 5 月),學習如何跨多臺機器使用相同的命令。 

  • 讓 UNIX 和 Linux 一塊兒工做(Martin Brown,developerWorks,2006 年 4 月)講解如何讓傳統的 UNIX 發行版和 Linux 一塊兒工做。 

  • 揭祕雲計算(Brett McLaughlin,developerWorks,2009 年 3 月):幫助您根據本身的應用程序需求選擇最好的雲計算平臺。 

  • 閱讀 用 Amazon Web Services 進行雲計算(Prabhakar Chaganti,developerWorks,2008 年 7 月):詳細講解如何使用 Amazon Web Services。 

  • 能夠經過 developerWorks Cloud Computing Resource Center 使用適用於 Amazon EC2 平臺的 IBM 產品。 

  • developerWorks Cloud Computing Resource Center 使用適用於 Amazon EC2 平臺的 IBM 產品。 

  • 在 developerWorks 的 雲開發人員資源 中,發現和共享應用程序和服務開發人員構建其雲部署項目的知識和經驗。 

  • AIX and UNIX 專區:developerWorks 的「AIX and UNIX 專區」提供了大量與 AIX 系統管理的全部方面相關的信息,您能夠利用它們來擴展本身的 UNIX 技能。

  • AIX and UNIX 新手入門:訪問「AIX and UNIX 新手入門」頁面可瞭解更多關於 AIX 和 UNIX 的內容。

  • AIX and UNIX 專題彙總:AIX and UNIX 專區已經爲您推出了不少的技術專題,爲您總結了不少熱門的知識點。咱們在後面還會繼續推出不少相關的熱門專題給您,爲了方便您的訪問,咱們在這裏爲您把本專區的全部專題進行彙總,讓您更方便的找到您須要的內容。

  • AIX and UNIX 下載中心:在這裏你能夠下載到能夠運行在 AIX 或者是 UNIX 系統上的 IBM 服務器軟件以及工具,讓您能夠提早免費試用他們的強大功能。

  • IBM Systems Magazine for AIX 中文版:本雜誌的內容更加關注於趨勢和企業級架構應用方面的內容,同時對於新興的技術、產品、應用方式等也有很深刻的探討。IBM Systems Magazine 的內容都是由十分資深的業內人士撰寫的,包括 IBM 的合做夥伴、IBM 的主機工程師以及高級管理人員。因此,從這些內容中,您能夠了解到更高層次的應用理念,讓您在選擇和應用 IBM 系統時有一個更好的認識。

  • 在 developerWorks 播客 上收聽面向軟件開發人員的有趣訪談和討論。 

  • developerWorks 技術活動網絡廣播:隨時關注 developerWorks 技術活動和網絡廣播。 

得到產品和技術

  • 獲取 libev 庫,包括下載和文檔。 

  • 獲取 libevent 庫。 

  • ruby libev (rev) 庫和文檔。 

  • Memcached 是用於存儲和處理數據的 RAM 緩存(其核心使用 libevent,也可使用其餘 libevent 服務器)。 

  • 使用 IBM 試用軟件 改進您的下一個開放源碼開發項目,這些軟件能夠經過下載或從 DVD 得到。 

討論

關於做者

Martin Brown 成爲專業做家已有八年多的時間了。他是題材普遍的衆多書籍和文章的做者。他的專業技術涉及各類開發語言和平臺 — Perl、 Python、Java、JavaScript、Basic、Pascal、Modula-二、C、C++、Rebol、Gawk、 Shellscript、Windows、Solaris、Linux、BeOS、Mac OS/X 等等 — 還涉及 Web 編程、系統管理和集成。Martin 是 Microsoft 的主題專家(SME),而且是 ServerWatch.com、LinuxToday.com 和 IBM developerWorks 的按期投稿人,他仍是 Computerworld、The Apple Blog 和其餘站點的正式博客。您能夠經過他的 Web 站點 http://www.mcslp.com 與他聯絡。

相關文章
相關標籤/搜索