65.9K
CodeProject 正在变化。 阅读更多。
Home

为C/C++应用程序添加Web界面的快速简便方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (29投票s)

2010年5月25日

CPOL

7分钟阅读

viewsIcon

132435

downloadIcon

1580

netstat命令的Web化,浏览器中的netstat

browser.jpg

引言

最近,我们需要一种方法来监控远程系统的IPv4-TCP连接表,而无需定期登录系统并运行“netstat”命令。我提出的解决方案是一个轻量级的netstat类服务器,它提供实时连接监控,可从任何互联网浏览器查看。该项目是演示如何为仅具有文本UI的应用程序添加Web界面的一个很好的例子。

背景

我们使用Microsoft函数GetTcpTable来收集连接数据。一个Microsoft示例,如下所示,演示了如何使用该函数,提供了一个很好的起点。

1    // Need to link with Iphlpapi.lib and Ws2_32.lib
 2    #include <winsock2.h>
 3    #include <ws2tcpip.h>
 4    #include <iphlpapi.h>
 5    #include <stdio.h>
 6    
 7    #pragma comment(lib, "iphlpapi.lib")
 8    #pragma comment(lib, "ws2_32.lib")
 9    
10    #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
11    #define FREE(x) HeapFree(GetProcessHeap(), 0, (x))
12    
13    /* Note: could also use malloc() and free() */
14    
15    int main()
16    {
17    
18        // Declare and initialize variables
19        PMIB_TCPTABLE pTcpTable;
20        DWORD dwSize = 0;
21        DWORD dwRetVal = 0;
22    
23        char szLocalAddr[128];
24        char szRemoteAddr[128];
25    
26        struct in_addr IpAddr;
27    
28        int i;
29    
30        pTcpTable = (MIB_TCPTABLE *) MALLOC(sizeof (MIB_TCPTABLE));
31        if (pTcpTable == NULL) {
32            printf("Error allocating memory\n");
33            return 1;
34        }
35    
36        dwSize = sizeof (MIB_TCPTABLE);
37    // Make an initial call to GetTcpTable to
38    // get the necessary size into the dwSize variable
39        if ((dwRetVal = GetTcpTable(pTcpTable, &dwSize, TRUE)) ==
40            ERROR_INSUFFICIENT_BUFFER) {
41            FREE(pTcpTable);
42            pTcpTable = (MIB_TCPTABLE *) MALLOC(dwSize);
43            if (pTcpTable == NULL) {
44                printf("Error allocating memory\n");
45                return 1;
46            }
47        }
48    // Make a second call to GetTcpTable to get
49    // the actual data we require
50        if ((dwRetVal = GetTcpTable(pTcpTable, &dwSize, TRUE)) == NO_ERROR) {
51            printf("\tNumber of entries: %d\n", (int) pTcpTable->dwNumEntries);
52            for (i = 0; i < (int) pTcpTable->dwNumEntries; i++) {
53                IpAddr.S_un.S_addr = (u_long) pTcpTable->table[i].dwLocalAddr;
54                strcpy_s(szLocalAddr, sizeof (szLocalAddr), inet_ntoa(IpAddr));
55                IpAddr.S_un.S_addr = (u_long) pTcpTable->table[i].dwRemoteAddr;
56                strcpy_s(szRemoteAddr, sizeof (szRemoteAddr), inet_ntoa(IpAddr));
57    
58                printf("\n\tTCP[%d] State: %ld - ", i,
59                       pTcpTable->table[i].dwState);
60                switch (pTcpTable->table[i].dwState) {
61                case MIB_TCP_STATE_CLOSED:
62                    printf("CLOSED\n");
63                    break;
64                case MIB_TCP_STATE_LISTEN:
65                    printf("LISTEN\n");
66                    break;
67                case MIB_TCP_STATE_SYN_SENT:
68                    printf("SYN-SENT\n");
69                    break;
70                case MIB_TCP_STATE_SYN_RCVD:
71                    printf("SYN-RECEIVED\n");
72                    break;
73                case MIB_TCP_STATE_ESTAB:
74                    printf("ESTABLISHED\n");
75                    break;
76                case MIB_TCP_STATE_FIN_WAIT1:
77                    printf("FIN-WAIT-1\n");
78                    break;
79                case MIB_TCP_STATE_FIN_WAIT2:
80                    printf("FIN-WAIT-2 \n");
81                    break;
82                case MIB_TCP_STATE_CLOSE_WAIT:
83                    printf("CLOSE-WAIT\n");
84                    break;
85                case MIB_TCP_STATE_CLOSING:
86                    printf("CLOSING\n");
87                    break;
88                case MIB_TCP_STATE_LAST_ACK:
89                    printf("LAST-ACK\n");
90                    break;
91                case MIB_TCP_STATE_TIME_WAIT:
92                    printf("TIME-WAIT\n");
93                    break;
94                case MIB_TCP_STATE_DELETE_TCB:
95                    printf("DELETE-TCB\n");
96                    break;
97                default:
98                    printf("UNKNOWN dwState value\n");
99                    break;
100                }
101                printf("\tTCP[%d] Local Addr: %s\n", i, szLocalAddr);
102                printf("\tTCP[%d] Local Port: %d \n", i,
103                       ntohs((u_short)pTcpTable->table[i].dwLocalPort));
104                printf("\tTCP[%d] Remote Addr: %s\n", i, szRemoteAddr);
105                printf("\tTCP[%d] Remote Port: %d\n", i,
106                       ntohs((u_short)pTcpTable->table[i].dwRemotePort));
107            }
108        } else {
109            printf("\tGetTcpTable failed with %d\n", dwRetVal);
110            FREE(pTcpTable);
111            return 1;
112        }
113    
114        return 0;
115    }

Web化

出于本次讨论的目的,我们将Web化定义为将应用程序的对外接口转换为Web启用接口的过程。为了避免客户端/服务器开发的复杂性,我们使用Snorkel SDK来Web化Microsoft示例。

在这个项目中,我们的Web界面是基于HTTP的,我们使用HTML表格来显示IPv4-TCP连接表。

.
.   
.
39    #define MALLOC(x) snorkel_mem_alloc ((x))
40    #define FREE(x) snorkel_mem_free ((x))
.    
.    
.    
61    /*
62     Source for MainPage based on MSDN example 
63     msdn.microsoft.com/en-us/library/aa366026(VS.85).aspx
64     */
65    call_status_t
66    MainPage (snorkel_obj_t http,
67              snorkel_obj_t outstream
68      )
69    {
70    
71      PMIB_TCPTABLE pTcpTable;
.      
.      
.      
75      char szquery[2048];
76      char szLocalAddr[128];
77      char szRemoteAddr[128];
78      u_short localPort = 0;
79      u_short remotePort = 0;
80      u_short filter = 0;
81      int header = 0;
82    
83      struct in_addr IpAddr;
84    
85      int i;
86    
87    
88    
89      pTcpTable =
90        (MIB_TCPTABLE *)
91        MALLOC (sizeof (MIB_TCPTABLE));
92      if (pTcpTable == NULL)
93        {
94          return
95            ERROR_STRING ("Error allocating memory");
96        }
97    
98      dwSize = sizeof (MIB_TCPTABLE);
99    
100    /* Make an initial call to GetTcpTable to
101       get the necessary size into the dwSize variable */
102      if ((dwRetVal =
103           GetTcpTable (pTcpTable, &dwSize,
104                        TRUE)) ==
105          ERROR_INSUFFICIENT_BUFFER)
106        {
107          FREE (pTcpTable);
108          pTcpTable =
109            (MIB_TCPTABLE *) MALLOC (dwSize);
110          if (pTcpTable == NULL)
111            {
112              return
113                ERROR_STRING
114                ("Error allocating memory\n");
115            }
116        }
117    
118      szquery[0] = 0;
119      snorkel_obj_get (http, snorkel_attrib_header,
120                       "QUERY", szquery,
121                       (int) sizeof (szquery));
122    
123      if (strlen (szquery) > 0)
124        filter = atoi (szquery);
125    
126    /* Make a second call to GetTcpTable to get
127       the actual data we require */
128      if ((dwRetVal =
129           GetTcpTable (pTcpTable, &dwSize,
130                        TRUE)) == NO_ERROR)
131        {
132    
133          if (filter)
134            snorkel_printf (outstream,
135                "<html><header><meta http-equiv=\"refresh\""
136                "content=\"%d\"></header><body><h2>"
137                "MONITORING PORT %d</h2><hr>\r\n",
138                g_autoRefreshInSeconds,
139                filter);
140          else
141            snorkel_printf (outstream,
142                 "<html><header><meta http-equiv=\"refresh\""
143                 "content=\"%d\"></header><body><h2>"
144                 "MONITORING ALL PORTS</h2><hr>\r\n",
145                 g_autoRefreshInSeconds);
146    
147    
148          for (i = 0;
149               i < (int) pTcpTable->dwNumEntries; i++)
150            {
151              IpAddr.S_un.S_addr =
152                (u_long) pTcpTable->table[i].
153                dwLocalAddr;
154              strcpy_s (szLocalAddr,
155                        sizeof (szLocalAddr),
156                        inet_ntoa (IpAddr));
157              IpAddr.S_un.S_addr =
158                (u_long) pTcpTable->table[i].
159                dwRemoteAddr;
160              strcpy_s (szRemoteAddr,
161                        sizeof (szRemoteAddr),
162                        inet_ntoa (IpAddr));
163    
164              localPort = ntohs ((u_short) pTcpTable->
165                                 table[i].
166                                 dwLocalPort);
167              remotePort =
168                ntohs ((u_short) pTcpTable->table[i].
169                       dwRemotePort);
170    
171              if (filter &&
172                  (filter != localPort
173                   && filter != remotePort))
174                continue;
175    
176              if (!header)
177                {
178                  snorkel_printf (outstream,
179                          "<div style=\"overflow:auto;  width: 100%%; height: 80%%;"
180                                  "padding:0px; margin: 0px\">\r\n");
181                  snorkel_printf (outstream,
182                                  "<table width=\"90%%\" cellpadding=\"0\">\r\n");
183                  snorkel_printf (outstream,
184                                  "<tr><th align=\"left\">Protocol</th>"
185                                  "<th align=\"left\">State</th>"
186                                  "<th align=\"left\">Local Address</th>"
187                                  "<th align=\"left\">Local Port</th>"
188                                  "<th align=\"left\">Remote Address</th>"
189                                  "<th align=\"left\">Remote Port</th></tr>\r\n");
190                  header = 1;
191                }
192    
193              snorkel_printf (outstream,
194                       "<tr><td align=\"left\">tcp</td><td align=\"left\">%02ld - ",
195                              pTcpTable->table[i].
196                              dwState);
197    
198    
199              switch (pTcpTable->table[i].dwState)
200                {
201                case MIB_TCP_STATE_CLOSED:
202                  snorkel_printf (outstream,
203                                  "CLOSED</td>");
204                  break;
205                case MIB_TCP_STATE_LISTEN:
206                  snorkel_printf (outstream,
207                                  "LISTEN</td>");
208                  break;
.                
.                  
.            
249                default:
250                  snorkel_printf (outstream,
251                                  "UNKNOWN dwState value</td>");
252                  break;
253                }
254    
255    
256              snorkel_printf (outstream,
257                              "<td align=\"left\">%s</td>",
258                              szLocalAddr);
259              snorkel_printf (outstream,
260                              "<td align=\"left\">%d</td>",
261                              localPort);
262              snorkel_printf (outstream,
263                              "<td align=\"left\">%s</td>",
264                              szRemoteAddr);
265              snorkel_printf (outstream,
266                              "<td align=\"left\">%d</td></tr>\r\n",
267                              remotePort);
268    
269    
270            }
271    
272        }
273      else
274        {
275          char szError[80];
276          sprintf_s (szError, sizeof (szError),
277                     "GetTcpTable failed with %d",
278                     dwRetVal);
279          FREE (pTcpTable);
280          return ERROR_STRING (szError);
281        }
282    
283    
284    /* udp stuff */
285      pUdpTable =
286        (MIB_UDPTABLE *)
287        MALLOC (sizeof (MIB_UDPTABLE));
288    
.      
.        
.         
292            return
293              ERROR_STRING
294              ("Error allocating memory\n");
295          return HTTP_SUCCESS;
296        }
297    
298      dwSize = sizeof (MIB_UDPTABLE);
299    
300    /* Make an initial call to GetUdpTable to
301       get the necessary size into the dwSize variable */
302      if ((dwRetVal =
303           GetUdpTable (pUdpTable, &dwSize,
304                        TRUE)) ==
305          ERROR_INSUFFICIENT_BUFFER)
306        {
307          FREE (pUdpTable);
308          pUdpTable =
309            (MIB_UDPTABLE *) MALLOC (dwSize);
310          if (pUdpTable == NULL)
311            {
312              if (!header)
313                return
314                  ERROR_STRING
315                  ("Error allocating memory\n");
316              else
317                return HTTP_SUCCESS;
318            }
319        }
320    
321    
322    /* Make a second call to GetTcpTable to get
323       the actual data we require */
324      if ((dwRetVal =
325           GetUdpTable (pUdpTable, &dwSize,
326                        TRUE)) == NO_ERROR)
327        {
328    
329    
330    
331          for (i = 0;
332               i < (int) pUdpTable->dwNumEntries; i++)
333            {
334              IpAddr.S_un.S_addr =
335                (u_long) pUdpTable->table[i].
336                dwLocalAddr;
337              strcpy_s (szLocalAddr,
338                        sizeof (szLocalAddr),
339                        inet_ntoa (IpAddr));
340    
341              localPort = ntohs ((u_short) pUdpTable->
342                                 table[i].
343                                 dwLocalPort);
344    
345              if (filter && (filter != localPort))
346                continue;
347    
348              if (!header)
349                {
350                  snorkel_printf (outstream,
351                          "<div style=\"overflow:auto;  width: 100%%; height: 80%%;"
352                                  "padding:0px; margin: 0px\">\r\n");
353                  snorkel_printf (outstream,
354                                  "<table width=\"90%%\" cellpadding=\"0\">\r\n");
355                  snorkel_printf (outstream,
356                                  "<tr><th align=\"left\">Protocol</th>"
357                                  "<th align=\"left\">State</th>"
358                                  "<th align=\"left\">Local Address</th>"
359                                  "<th align=\"left\">Local Port</th>"
360                                  "<th align=\"left\">Remote Address</th>"
361                                  "<th align=\"left\">Remote Port</th></tr>\r\n");
362                  header = 1;
363                }
364    
365              snorkel_printf (outstream,
366                    "<tr><td align=\"left\">udp</td>"
367                    "<td align=\"left\">** - ****");
368    
369              snorkel_printf (outstream,
370                              "<td align=\"left\">%s</td>",
371                              szLocalAddr);
372              snorkel_printf (outstream,
373                              "<td align=\"left\">%d</td>",
374                              localPort);
375              snorkel_printf (outstream,
376                              "<td align=\"left\">*****</td>");
377              snorkel_printf (outstream,
378                              "<td align=\"left\">*****</td></tr>\r\n");
379    
380    
381            }
382        }
383      else
384        {
385          FREE (pUdpTable);
386        }
387    
388      /*
389       *
390       * the following must be included to adhere to Snorkel License usage agreement
391       *
392       */
393      if (!header && filter)
394        {
395          snorkel_printf (outstream,
396                          "<h3>There are no services listening on port %d</h3>",
397                          filter);
398          snorkel_printf (outstream,
399                          "<hr><address>Server powered by Snorkel</address>"
400                          "</body></html>\r\n");
401        }
402      else
403        snorkel_printf (outstream,
404                        "</table></div><hr><address>Server powered by Snorkel"
405                        "</address></body></html>\r\n");
406    
407      return HTTP_SUCCESS;
408    }
409

我们从前几行开始,用使用Snorkel API snorkel_mem_allocsnorkel_mem_free的版本分别替换示例中的MALLOCFREE。这些函数使用线程堆存储而不是进程堆存储,后者是HeapAlloc使用的存储类型。Snorkel API调用提供无锁内存分配和释放,以及线程并发性,从而提供更好的整体性能。您可以在网上找到更多关于局部性和NUMA架构的信息。

我们把示例中的main改成了名为MainPage的子例程回调。回调接收指向HTTP头和连接对象(输出流)的指针作为其参数。我们不直接调用MainPage;连接处理器调用该函数。连接处理器是Snorkel运行时分配用于处理传入HTTP请求的线程。不深入细节,Snorkel为每个HTTP请求分配一个单独的连接处理器。MainPage是Snorkel术语中称为重载URI的配对的一部分。重载URI是与函数关联的URI。在main中,我们将函数MainPage与URI“/index.html”和“/”关联。当用户在浏览器中输入“http://server_address/”或“http://server_address/index.html”时,关联的连接处理器调用MainPage来满足请求。

为了提供端口过滤功能,即根据提供的端口号过滤内容,我们利用HTTP查询字符串。查询字符串是出现在URI后面,由“?”引导的字符串。例如,URI“https://:8082?7188”将输出限制为与端口7188关联的连接,而URI“https://:8082”则列出IPv4-TCP表中所有活动的连接。Snorkel将查询字符串存储在HTTP变量QUERY中。在第119-121行,我们使用函数snorkel_obj_get来检索查询值(如果存在)。

在例程的其余部分,我们将对printf函数的调用替换为对Snorkel API snorkel_printf的调用。两个函数使用相同的格式分隔符;然而,与将输出定向到显示器的printf函数不同,snorkel_printf将输出定向到HTTP响应页面。

我们依赖宏ERROR_STRINGHTP_ERRORHTTP_SUCCESS来将回调的状态传回给处理程序线程。Snorkel API提供了两种返回回调错误的方法:宏ERROR_STRING或宏HTTP_ERRORERROR_STRING宏允许开发人员指定自己的错误消息以便在客户端浏览器中显示,而HTTP_ERROR宏则使用通用消息。如果没有错误,我们返回HTTP_SUCCESS

411    void
412    main (int argc, char *argv[])
413    {
414      int port = 8082;
415      int i = 0;
416      char *pszLog = 0;
417      char szExit[20];
418      snorkel_obj_t http = 0, logobj = 0;
419    
420      fprintf (stderr,
421               "\nnetstat server -- a remote port monitoring server\n\n");
422    
423      for (i = 1; i < argc; i++)
424        {
425          if (argv[i][0] == '-')
426            {
427              switch (argv[i][1])
428                {
429                case 'l':
430                  if (i + 1 >= argc)
431                    syntax (argv[0]);
432                  pszLog = argv[i + 1];
433                  i++;
434                  break;
435                case 'r':
436                  if (i + 1 >= argc)
437                    syntax (argv[0]);
438                  g_autoRefreshInSeconds =
439                    atoi (argv[i + 1]);
440                  i++;
441                  break;
442                default:
443                  syntax (argv[0]);
444                  break;
445                }
446            }
447          else
448            {
449              port = atoi (argv[i]);
450              break;
451            }
452        }
453    
454    
455      if (pszLog)
456        {
457          logobj =
458            snorkel_obj_create (snorkel_obj_log,
459                                pszLog);
460          if (!logobj)
461            {
462              perror ("could not create log file\n");
463              exit (1);
464            }
465          snorkel_debug (1);
466        }
467    
468      if (snorkel_init () != SNORKEL_SUCCESS)
469        {
470          perror ("could not initialize snorkel\n");
471          exit (1);
472        }
473    
474      http =
475        snorkel_obj_create (snorkel_obj_server, 2,
476                            NULL);
477      if (!http)
478        {
479          perror
480            ("could not create server object!\n");
481          exit (1);
482        }
483    
484      if (snorkel_obj_set (http,    /* server object */
485                           snorkel_attrib_listener, /* attribute   */
486                           port,    /* port number */
487                           0 /* SSL support */ )
488          != SNORKEL_SUCCESS)
489        {
490          fprintf (stderr,
491                   "could not create listener\n");
492          snorkel_obj_destroy (http);
493          exit (1);
494        }
495    
496      /*
497       *
498       * overload the URI http://index.html
499       *
500       */
501      if (snorkel_obj_set (http,    /* server object */
502                           snorkel_attrib_uri,      /* attribute type */
503                           GET,     /* method */
504                           "/index.html",   /* uri */
505                           encodingtype_text,       /* encoding */
506                           MainPage) !=
507          SNORKEL_SUCCESS)
508        {
509          perror ("could not overload index.html");
510          snorkel_obj_destroy (http);
511          exit (1);
512        }
513    
514      if (snorkel_obj_set
515          (http, snorkel_attrib_ipvers, IPVERS_IPV4,
516           SOCK_SET) != SNORKEL_SUCCESS)
517        {
518          fprintf (stderr,
519                   "error could not set ip version\n");
520          exit (1);
521        }
522    
523      /*
524       *
525       * start the server
526       *
527       */
528      fprintf (stderr,
529               "\n\n[HTTP] starting embedded server\n");
530      if (snorkel_obj_start (http) != SNORKEL_SUCCESS)
531        {
532          perror ("could not start server\n");
533          snorkel_obj_destroy (http);
534          exit (1);
535        }
536    
537      /*
538       *
539       * do something while server runs
540       * as a separate thread
541       *
542       */
543      fprintf (stderr, "\n[HTTP] started.\n\n"
544               "--hit enter to terminate--\n");
545      fgets (szExit, sizeof (szExit), stdin);
546    
547      fprintf (stderr, "[HTTP] bye\n");
548    
549      /*
550       *
551       * graceful clean up
552       *
553       */
554      snorkel_obj_destroy (http);
555      exit (0);
556    }

我们的main源自Snorkel样板代码(参见Snorkel开发者指南)。在第468 - 482行,我们初始化Snorkel API并创建服务器对象,指定线程池大小为二(snorkel_obj_create的第二个参数)。

接着,在484-494行,我们创建一个监听器,将其绑定到用户定义的端口或默认端口号8082。在501-512行,我们将MainPage回调映射到“index.html”,并在530行启动服务器。由于嵌入式服务器在其自己的线程上运行,我们使用fgets提示用户输入以防止程序立即退出。

运行服务器

您可以从命令行运行服务器,或者在资源管理器中双击它。如果未指定端口号,则使用端口8082。HTML页面的默认刷新率为五秒。您可以通过在命令行上指定-r选项来更改刷新率。

语法

netstatsrv [port] [-h] [-r refresh_rate_in_seconds]

要在本地查看 IPv4-TCP 表,请启动服务器并打开网页浏览器。在浏览器中,输入 URI "https:///8082"。要按端口号过滤,请输入 URI,后跟一个 '?' 和端口号。例如,"https:///8082?8080" 将只显示与端口 8080 相关的表条目。

最终评论

此项目的源文件,netstatsrv.c,位于附加包的“c”目录中。

我正在向CodeProject成员免费提供此项目中使用的Snorkel SDK的有限版本。提供的SDK包含适用于SunOS、Linux和Microsoft Windows平台的二进制文件。它还包括此示例以及其他示例和关于如何使用Snorkel SDK的完整文档。尽管Snorkel支持SSL,但我由于美国贸易法而删除了SSL版本的运行时库。开发者指南位于附加包的doc文件夹中,您可能希望在试用此项目之前阅读该指南。如果您对Snorkel SDK有任何疑问或额外兴趣,请随时通过wcapers64@gmail.com与我联系。

相关文章

历史

  • 6.18.2010

    对语法函数进行了小修补,并将Snorkel更新到最新版本1.0。Snorkel 1.0 包括

    • 性能优化增强
    • 支持keep-alive
    • 支持零拷贝(sendfile)
    • 暴露了线程管理器过载功能——现在用户可以通过过载管理器来创建比核心更多的处理器
    • 次要 bug 修复
    • 添加了内置页面以显示有关嵌入式服务器的信息。要访问该页面,请在根URL后附加/about/snorkel
  • 6.21.2010

    我通常不会这么快发布库的更新,但这次我无法抵挡诱惑。由于文件系统信息缓存方式的改变,版本1.0.1在上周末在Windows端获得了显著的性能提升。这些改变通过减少I/O阻塞,显著提高了每秒请求数和平均传输速率。当使用Apache的ab测试与其他Web服务器进行基准测试时,性能差异足以证明这次早期更新。

  • 6.21.2010

    发现并纠正了UNIX系统上的缺陷

  • 2010年6月25日——Snorkel 1.0.2 更新
    • 修复了线程堆分配器在线程堆之外分配的轻微缺陷
    • 增加了线程堆完整性检查和自动修复功能
    • 完成了测试并启用了1.0.1中已引入但被禁用的额外性能增强

    致Snorkel的采用者:Snorkel每天都经过严格测试,并且通常在现场发现问题之前就已经检测并修复了错误。在Snorkel更新有官方网站之前,我将使与本文和其他使用该库的文章捆绑的版本与API的最新版本保持同步。

  • 2010年7月8日——Snorkel 1.0.4 更新
    • 纠正了wintel的bin目录和lib目录中运行时库文件之间的文件不匹配问题
    • 我添加了在非HTTP流中切换snorkel_printf传输大小字段的功能。要启用或禁用该功能(非HTTP流默认开启),请使用以下命令:
      snorkel_obj_set (snorkel_obj_t non_http_stream, 
      	snorkel_attrib_cbprintf, int state (enabled=1,disabled=0))
    • 添加了邮件功能。语法
      snorkel_smtp_message (char *smtp_server, int port, 
      	char *from, char *to, char *format_string, arg1, arg2,...argN)

      注意:该函数与printf工作方式相同。

    • 暴露了更多属性
    • 次要 bug 修复
  • 7.20.2010
    • 更新了源代码
  • 2011年1月3日 – Snorkel 2.0.0.1 更新(仍然免费)
    • 全新改进的API
    • 更快的性能
    • 新增API:Aqua (服务SDK) 和 Sailfish (C/C++ 应用服务器)
    • 平台支持:MAC OSX、SunOS、Debian Linux、Windows
    • 新下载网站:http://snorkelembedded.webs.com
© . All rights reserved.