一個工業級、跨平台、輕量級的 tcp 網絡服務框架:gevent_網頁設計公司

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

作為公司的公共產品,經常有這樣的需求:就是新建一個本地服務,產品線作為客戶端通過 tcp 接入本地服務,來獲取想要的業務能力。

與印象中動輒處理成千上萬連接的 tcp 網絡服務不同,這個本地服務是跑在客戶機器上的,Win32 上作為開機自啟動的 windows 服務運行;

Linux 上作為 daemon 在後台運行。總的說來就是用於接收幾個產品進程的連接,因此輕量化是其最重要的要求,在這個基礎上要能兼顧跨平台就可以了。

其實主要就是 windows,再兼顧一點兒 linux。

 

考察了幾個現有的開源網絡框架,從 ACE 、boost::asio 到 libevent,都有不盡於人意的地方:

a) ACE:太重,只是想要一個網絡框架,結果它扒拉扒拉一堆全提供了,不用還不行;

b) boost::asio:太複雜,牽扯到 boost 庫,並且引入了一堆 c++ 模板,需要高版本 c++ 編譯器支持;

c) libevent:這個看着不錯,當時確實用這個做底層封裝了一版,結果發版后發現一個比較致命的問題,導致在防火牆設置比較嚴格的機器上初始化失敗,這個後面我會詳細提到。

 

其它的就更不用說了,之前也粗略看過陳碩的 muddo,總的感覺吧,它是基於其它開源框架不足地方改進的一個庫,有相當可取的地方,但是這個改進的方向也主要是解決更大併發、更多連接,不是我的痛點,所以沒有繼續深入研究。

 

好了,與其在不同開源框架之間糾結,不如自己動手寫一個。

反正我的場景比較固定,不用像它們那樣面面俱,我給自己羅列了一些這個框架需要支持基本的功能:

1)同步寫、異步讀;

2)可同時監聽多路事件,基於 1)這裏只針對異步 READ 事件(包含連接進入、連接斷開),寫數據是同步的,因而不需要處理異步 WRITE 事件;

3)要有設置一次性和周期性定時器的能力 (業務決定的);

4)不需要處理信號 (windows 上也沒信號這一說,linux 自己搞搞 sigaction 就好啦);

……

 

雖然這個框架未來只會運行在用戶的單機上,但是我不希望它一出生就帶有性能缺陷,所以性能平平的 select 沒能進入我的法眼,我決定給它裝上最強大的心臟:

Windows 平台: iocp

Linux 平台:epoll

 

ok,從需求到底層技術路線,貌似都講清楚了,依照 libevent 我給它取名為 gevent,下面我們從代碼級別看下這個框架是怎麼簡化 tcp 服務搭建這類工作的。

首先看一下這個 tcp 服務框架的 sample:

svc_handler.h

 1 #include "EventBase.h"
 2 #include "EventHandler.h"
 3 
 4 class GMyEventBase : public GEventBase
 5 {
 6 public:
 7     GEventHandler* create_handler (); 
 8 }; 
 9 
10 
11 class svc_handler : public GJsonEventHandler
12 {
13 public:
14     virtual ~svc_handler () {}
15     virtual void on_read_msg (Json::Value const& val); 
16 };

epoll_svc.cpp

 1 #include <stdio.h>
 2 #include "svc_handler.h"
 3 #include <signal.h>
 4 
 5 GMyEventBase g_base; 
 6 GEventHandler* GMyEventBase::create_handler () 
 7 {
 8     return new svc_handler; 
 9 }
10 
11 void sig_int (int signo)
12 {
13     printf ("%d caught\n", signo); 
14     g_base.exit (1); 
15     printf ("exit ok\n"); 
16 }
17 
18 int main (int argc, char *argv[])
19 {
20     if (argc < 2)
21     {
22         printf ("usage: epoll_svc port\n"); 
23         return -1; 
24     }
25 
26     unsigned short port = atoi (argv[1]);
27 
28 #ifndef WIN32
29     struct sigaction act; 
30     act.sa_handler = sig_int; 
31     sigemptyset(&act.sa_mask);   
32     act.sa_flags = SA_RESTART; 
33     if (sigaction (SIGINT, &act, NULL) < 0)
34     {
35         printf ("install SIGINT failed, errno %d\n", errno); 
36         return -1; 
37     }
38     else
39         printf ("install SIGINT ok\n"); 
40 #endif
41 
42     // to test small message block
43     if (g_base.init (/*8, 10*/) < 0)
44         return -1; 
45 
46     printf ("init ok\n"); 
47     do
48     {
49         if (!g_base.listen (port))
50         {
51             g_base.exit (0); 
52             printf ("exit ok\n"); 
53             break; 
54         }
55 
56         printf ("listen ok\n"); 
57         g_base.run (); 
58         printf ("run  over\n"); 
59     } while (0); 
60 
61     g_base.fini (); 
62     printf ("fini ok\n"); 
63 
64     g_base.cleanup (); 
65     printf ("cleanup ok\n"); 
66     return 0; 
67 }

 

這個服務的核心是 GMyEventBase 類,它使用了框架中的 GEventBase 類,從後者派生而來,

只改寫了一個 create_handler 接口來提供我們的事件處理對象 svc_handler,它是從框架中的 GEventHandler 派生而來,

svc_handler 只改寫了一個 on_read_msg 來處理 Json 格式的消息輸入。

 

程序的運行就是分別調用 GMyEventBase(實際上是GEventBase)  的 init / listen / run / fini / cleaup 方法。

而與業務相關的代碼,都在 svc_handler 中處理:

svc_handler.cpp

 1 #include "svc_handler.h"
 2 
 3 void svc_handler::on_read_msg (Json::Value const& val)
 4 {
 5     int key = val["key"].asInt (); 
 6     std::string data = val["data"].asString (); 
 7     printf ("got %d:%s\n", key, data.c_str ()); 
 8 
 9     Json::Value root; 
10     Json::FastWriter writer; 
11     root["key"] = key + 1; 
12     root["data"] = data; 
13 
14     int ret = 0;
15     std::string resp = writer.write(root); 
16     resp = resp.substr (0, resp.length () - 1); // trim tailing \n
17     if ((ret = send (resp)) <= 0)
18         printf ("send response failed, errno %d\n", errno); 
19     else 
20         printf ("response %d\n", ret); 
21 }

 

它期待 Json 格式的數據,並且有兩個字段 key(int) 與 data (string),接收數據后將 key 增 1 后返回給客戶端。

再來看下客戶端 sample:

clt_handler.h

 1 #include "EventBaseAR.h"
 2 #include "EventHandler.h"
 3 
 4 class GMyEventBase : public GEventBaseWithAutoReconnect
 5 {
 6 public:
 7     GEventHandler* create_handler (); 
 8 }; 
 9 
10 
11 class clt_handler : public GJsonEventHandler
12 {
13 public:
14     virtual ~clt_handler () {}
15 #ifdef TEST_TIMER
16     virtual bool on_timeout (GEV_PER_TIMER_DATA *gptd); 
17 #endif
18     virtual void on_read_msg (Json::Value const& val); 
19 };

 

epoll_clt.cpp

  1 #include <stdio.h>
  2 #include "clt_handler.h"
  3 #include <signal.h>
  4 
  5 //#define TEST_READ
  6 //#define TEST_CONN
  7 //#define TEST_TIMER
  8 
  9 GMyEventBase g_base; 
 10 GEventHandler* GMyEventBase::create_handler () 
 11 {
 12     return new clt_handler; 
 13 }
 14 
 15 
 16 int sig_caught = 0; 
 17 void sig_int (int signo)
 18 {
 19     sig_caught = 1; 
 20     printf ("%d caught\n", signo); 
 21     g_base.exit (0); 
 22     printf ("exit ok\n"); 
 23 }
 24 
 25 void do_read (GEventHandler *eh, int total)
 26 {
 27     char buf[1024] = { 0 }; 
 28     int ret = 0, n = 0, key = 0, err = 0;
 29     char *ptr = nullptr; 
 30     while ((total == 0 ||  n++ < total) && fgets (buf, sizeof(buf), stdin) != NULL)
 31     {
 32         // skip \n
 33         buf[strlen(buf) - 1] = 0; 
 34         //n = sscanf (buf, "%d", &key); 
 35         key = strtol (buf, &ptr, 10); 
 36         if (ptr == nullptr)
 37         {
 38             printf ("format: int string\n"); 
 39             continue; 
 40         }
 41 
 42         Json::Value root; 
 43         Json::FastWriter writer; 
 44         root["key"] = key; 
 45         // skip space internal
 46         root["data"] = *ptr == ' ' ? ptr + 1 : ptr;  
 47 
 48         std::string req = writer.write (root); 
 49         req = req.substr (0, req.length () - 1); // trim tailing \n
 50         if ((ret = eh->send (req)) <= 0)
 51         {
 52             err = 1; 
 53             printf ("send %d failed, errno %d\n", req.length (), errno); 
 54             break; 
 55         }
 56         else 
 57             printf ("send %d\n", ret); 
 58     }
 59 
 60     if (total == 0)
 61         printf ("reach end\n"); 
 62 
 63     if (!err)
 64     {
 65         eh->disconnect (); 
 66         printf ("call disconnect to notify server\n"); 
 67     }
 68 
 69     // wait receiving thread 
 70     //sleep (3); 
 71     // if use press Ctrl+D, need to notify peer our break
 72 }
 73 
 74 #ifdef TEST_TIMER
 75 void test_timer (unsigned short port, int period_msec, int times)
 76 {
 77     int n = 0; 
 78     GEventHandler *eh = nullptr; 
 79 
 80     do
 81     {
 82         eh = g_base.connect (port); 
 83         if (eh == nullptr)
 84             break;
 85 
 86         printf ("connect ok\n"); 
 87         void* t = g_base.timeout (1000, period_msec, eh, NULL); 
 88         if (t == NULL)
 89         {
 90             printf ("timeout failed\n"); 
 91             break; 
 92         }
 93         else 
 94             printf ("set timer %p ok\n", t); 
 95 
 96         // to wait timer
 97         do
 98         {
 99             sleep (400); 
100             printf ("wake up from sleep\n"); 
101         } while (!sig_caught && n++ < times);
102 
103         g_base.cancel_timer (t); 
104     } while (0); 
105 }
106 #endif
107 
108 #ifdef TEST_CONN
109 void test_conn (unsigned short port, int per_read, int times)
110 {
111 #  ifdef WIN32
112     srand (GetCurrentProcessId()); 
113 #  else
114     srand (getpid ()); 
115 #  endif
116     int n = 0, elapse = 0; 
117     clt_handler *eh = nullptr; 
118 
119     do
120     {
121         eh = (clt_handler *)g_base.connect (port); 
122         if (eh == nullptr)
123             break;
124 
125         printf ("connect ok\n"); 
126 
127         do_read (eh, per_read); 
128 #  ifdef WIN32
129         elapse = rand() % 1000; 
130         Sleep(elapse); 
131         printf ("running  %d ms\n", elapse); 
132 #  else
133         elapse = rand () % 1000000; 
134         usleep (elapse); 
135         printf ("running  %.3f ms\n", elapse/1000.0); 
136 #  endif
137 
138     } while (!sig_caught && n++ < times);
139 }
140 #endif
141 
142 #ifdef TEST_READ
143 void test_read (unsigned short port, int total)
144 {
145     int n = 0; 
146     GEventHandler *eh = nullptr; 
147 
148     do
149     {
150         eh = g_base.connect (port); 
151         if (eh == nullptr)
152             break;
153 
154         printf ("connect ok\n"); 
155         do_read (eh, total); 
156     } while (0); 
157 }
158 #endif
159 
160 int main (int argc, char *argv[])
161 {
162     if (argc < 2)
163     {
164         printf ("usage: epoll_clt port\n"); 
165         return -1; 
166     }
167 
168     unsigned short port = atoi (argv[1]); 
169 
170 #ifndef WIN32
171     struct sigaction act; 
172     act.sa_handler = sig_int; 
173     sigemptyset(&act.sa_mask);   
174     // to ensure read be breaked by SIGINT
175     act.sa_flags = 0; //SA_RESTART;  
176     if (sigaction (SIGINT, &act, NULL) < 0)
177     {
178         printf ("install SIGINT failed, errno %d\n", errno); 
179         return -1; 
180     }
181 #endif
182 
183     if (g_base.init (2) < 0)
184         return -1; 
185 
186     printf ("init ok\n"); 
187 
188 #if defined(TEST_READ)
189     test_read (port, 0); // 0 means infinite loop until user break
190 #elif defined(TEST_CONN)
191     test_conn (port, 10, 100); 
192 #elif defined (TEST_TIMER)
193     test_timer (port, 10, 1000); 
194 #else
195 #  error please define TEST_XXX macro to do something!
196 #endif
197 
198     if (!sig_caught)
199     {
200         // Ctrl + D ?
201         g_base.exit (0); 
202         printf ("exit ok\n"); 
203     }
204     else 
205         printf ("has caught Ctrl+C\n"); 
206 
207     g_base.fini (); 
208     printf ("fini ok\n"); 
209 
210     g_base.cleanup (); 
211     printf ("cleanup ok\n"); 
212     return 0; 
213 }

 

客戶端同樣使用了 GEventBase 的派生類 GMyEventBase 來作為事件循環的核心,所不同的是(注意並非之前例子里的那個類,雖然同名),它提供了 clt_handler 來處理自己的業務代碼。

另外為了提供連接中斷後自動向服務重連的功能,這裏 GMyEventBase 派生自 GEventBase 類的子類 GEventBaseWithAutoReconnect (位於 EventBaseAR.h/cpp 中)。

程序的運行是分別調用 GEventBase 的 init / connect / fini / cleaup 方法以及 GEventHandler 的 send / disconnect 來測試讀寫與連接。

定義宏 TEST_READ 用來測試讀寫;定義宏 TEST_CONN 可以測試連接的通斷及讀寫;定義宏 TEST_TIMER 來測試周期性定時器及讀寫。它們是互斥的。

clt_handler 主要用來異步接收服務端的回送數據並打印:

clt_handler.cpp

 1 #include "clt_handler.h"
 2 
 3 #ifdef TEST_TIMER
 4 extern void do_read (clt_handler *, int); 
 5 bool clt_handler::on_timeout (GEV_PER_TIMER_DATA *gptd)
 6 {
 7     printf ("time out ! id %p, due %d, period %d\n", gptd, gptd->due_msec, gptd->period_msec); 
 8     do_read ((clt_handler *)gptd->user_arg, 1); 
 9     return true; 
10 }
11 #endif
12 
13 void clt_handler::on_read_msg (Json::Value const& val)
14 {
15     int key = val["key"].asInt (); 
16     std::string data = val["data"].asString (); 
17     printf ("got %d:%s\n", key, data.c_str ()); 
18 }

 

這個測試程序可以通過在控制台手工輸入數據來驅動,也可以通過測試數據文件來驅動,下面的 awk 腳本用來製造符合格式的測試數據:

epoll_gen.awk

 1 #! /bin/awk -f
 2 BEGIN {
 3         WORDNUM = 1000
 4         for (i = 1; i <= WORDNUM; i++) {
 5                 printf("%d %s\n", randint(WORDNUM), randword(20))
 6         }
 7 }
 8 
 9 # randint(n): return a random integer number which is >= 1 and <= n
10 function randint(n) {
11         return int(n *rand()) + 1
12 }
13 
14 # randlet(): return a random letter, which maybe upper, lower or number. 
15 function randlet() {
16         return substr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", randint(62), 1)
17 }
18 
19 # randword(LEN): return a rand word with a length of LEN
20 function randword(LEN) {
21         randw=""
22         for( j = 1; j <= LEN; j++) {
23                 randw=randw randlet()
24         }
25         return randw
26 }

 

生成的測試文件格式如下:

238 s0jKlYkEjwE4q3nNJugF
568 0cgNaSgDpP3VS45x3Wum
996 kRF6SgmIReFmrNBcCecj
398 QHQqCrB5fC61hao1BV2x
945 XZ6KLtA4jZTEnhcAugAM
619 WE95NU7FnsYar4wz279j
549 oVCTmD516yvmtuJB2NG3
840 NDAaL5vpzp8DQX0rLRiV
378 jONIm64AN6UVc7uTLIIR
251 EqSBOhc40pKXhCbCu8Ey

 

整個工程編譯的話就是一個 CMakeLists 文件,可以通過 cmake 生成對應的 Makefile 或 VS solution 來編譯代碼:

CMakeLists.txt

 1 cmake_minimum_required(VERSION 3.0)
 2 project(epoll_svc)
 3 include_directories(../core ../include)
 4 set(CMAKE_CXX_FLAGS "-std=c++11 -pthread -g -Wall ${CMAKE_CXX_FLAGS}")
 5 link_directories(${PROJECT_SOURCE_DIR}/../lib)
 6 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../bin)
 7 
 8 add_executable (epoll_svc epoll_svc.cpp svc_handler.cpp ../core/EventBase.cpp ../core/EventHandler.cpp ../core/log.cpp)
 9 IF (WIN32)
10 target_link_libraries(epoll_svc jsoncpp ws2_32)
11 ELSE ()
12 target_link_libraries(epoll_svc jsoncpp rt)
13 ENDIF ()
14 
15 add_executable (epoll_clt epoll_clt.cpp clt_handler.cpp ../core/EventBase.cpp ../core/EventBaseAR.cpp ../core/EventHandler.cpp ../core/log.cpp)
16 target_compile_definitions(epoll_clt PUBLIC -D TEST_READ)
17 IF (WIN32)
18 target_link_libraries(epoll_clt jsoncpp ws2_32)
19 ELSE ()
20 target_link_libraries(epoll_clt jsoncpp rt)
21 ENDIF ()
22 
23 add_executable (epoll_local epoll_local.cpp)
24 IF (WIN32)
25 target_link_libraries(epoll_local jsoncpp ws2_32)
26 ELSE ()
27 target_link_libraries(epoll_local jsoncpp rt)
28 ENDIF ()

 

 這個項目包含三個編譯目標,分別是 epoll_svc 、epoll_clt 與 epoll_local,其中前兩個可以跨平台編譯,后一個只能在 Linux 平台編譯,用來驗證 epoll 的一些特性。

編譯完成后,首先運行服務端:

>./epoll_svc 1025 

 然後運行客戶端:

>./epoll_clt 1025 < demo

測試多個客戶端同時連接,可以使用下面的腳本:

epoll_start.sh

1 #! /bin/bash
2 # /bin/sh -> /bin/dash, do not recognize our for loop
3 
4 for((i=0;i<10;i=i+1))
5 do
6     ./epoll_clt 1025 < demo &
7     echo "start $i"
8 done

 

可以同時啟動 10 個客戶端。

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

通過 Ctrl+C 退出服務端;通過 Ctrl+C 或 Ctrl+D 退出單個客戶端;

通過下面的腳本來停止多個客戶端與服務端:

epoll_stop.sh

1 #! /bin/sh
2 pkill -INT epoll_clt
3 sleep 1
4 pkill -INT epoll_svc

 

 

框架的用法介紹完之後,再簡單遊覽一下這個庫的各層級對外接口。

EventBase.h

  1 #pragma once
  2 
  3 
  4 #include "EventHandler.h" 
  5 #include <string>
  6 #include <map>
  7 #include <mutex> 
  8 #include <condition_variable>
  9 #include "thread_group.hpp"
 10 
 11 #define GEV_MAX_BUF_SIZE 65536
 12 
 13 class GEventBase : public IEventBase
 14 {
 15 public:
 16     GEventBase();
 17     ~GEventBase();
 18 
 19 #ifdef WIN32
 20     virtual HANDLE iocp () const; 
 21 #else
 22     virtual int epfd () const; 
 23 #endif
 24     virtual bool post_timer(GEV_PER_TIMER_DATA *gptd); 
 25     virtual GEventHandler* create_handler() = 0; 
 26 
 27     // thr_num : 
 28     //  =0 - no default thread pool, user provide thread and call run
 29     //  <0 - use max(|thr_num|, processer_num)
 30     //  >0 - use thr_num
 31     bool init(int thr_num = -8, int blksize = GEV_MAX_BUF_SIZE
 32 #ifndef WIN32
 33               , int timer_sig = SIGUSR1
 34 #endif
 35               ); 
 36 
 37     bool listen(unsigned short port, unsigned short backup = 10);
 38     GEventHandler* connect(unsigned short port, GEventHandler* exist_handler = NULL); 
 39     // PARAM
 40     // due_msec: first timeout milliseconds
 41     // period_msec: later periodically milliseconds
 42     // arg: user provied argument
 43     // exist_handler: reuse the timer handler
 44     //
 45     // RETURN
 46     //   NULL: failed
 47     void* timeout(int due_msec, int period_msec, void *arg, GEventHandler *exist_handler);
 48     bool cancel_timer(void* tid); 
 49     void fini();  
 50     void run(); 
 51     void exit(int extra_notify = 0); 
 52     void cleanup(); 
 53 
 54 protected:
 55 #ifdef WIN32
 56     bool do_accept(GEV_PER_IO_DATA *gpid); 
 57     bool do_recv(GEV_PER_HANDLE_DATA *gphd, GEV_PER_IO_DATA *gpid); 
 58     void do_error(GEV_PER_HANDLE_DATA *gphd); 
 59 
 60     int init_socket();
 61     bool issue_accept(); 
 62     bool issue_read(GEV_PER_HANDLE_DATA *gphd);
 63     bool post_completion(DWORD bytes, ULONG_PTR key, LPOVERLAPPED ol); 
 64 
 65 #else
 66     bool do_accept(int fd); 
 67     bool do_recv(conn_key_t key); 
 68     void do_error(conn_key_t key); 
 69 
 70     bool init_pipe(); 
 71     void close_pipe(); 
 72     bool post_notify (char ch, void* ptr = nullptr); 
 73     void promote_leader (std::unique_lock<std::mutex> &guard); 
 74 
 75     GEventHandler* find_by_key (conn_key_t key, bool erase); 
 76     GEventHandler* find_by_fd (int fd, conn_key_t &key, bool erase); 
 77 
 78 #  ifdef HAS_SIGTHR
 79     void sig_proc (); 
 80 #  endif
 81 #endif
 82 
 83     bool do_timeout(GEV_PER_TIMER_DATA *gptd); 
 84 
 85     virtual bool on_accept(GEV_PER_HANDLE_DATA *gphd);
 86     virtual bool on_read(GEventHandler *h, GEV_PER_IO_DATA *gpid); 
 87     virtual void on_error(GEventHandler *h);
 88     virtual bool on_timeout (GEV_PER_TIMER_DATA *gptd); 
 89     
 90 
 91 protected:
 92     volatile bool m_running = false;
 93     int m_thrnum = 0; 
 94     int m_blksize = GEV_MAX_BUF_SIZE; 
 95     std::thread_group m_grp; 
 96     SOCKET m_listener = INVALID_SOCKET;
 97 
 98     std::mutex m_mutex;  // protect m_map
 99     std::mutex m_tlock; // protect m_tmap
100     // timer_t may conflict when new timer created after old timer closed
101     //std::map <timer_t, GEventHandler *> m_tmap; 
102     std::map <GEV_PER_TIMER_DATA*, GEventHandler *> m_tmap; 
103 
104 #ifdef WIN32
105     LPFN_ACCEPTEX m_acceptex = nullptr; 
106     LPFN_GETACCEPTEXSOCKADDRS m_getacceptexsockaddrs = nullptr; 
107     HANDLE m_iocp = NULL; 
108     HANDLE m_timerque = NULL; 
109 
110     std::map<GEV_PER_HANDLE_DATA*, GEventHandler*> m_map; 
111 #else
112     int m_ep = -1; 
113     int m_pp[2]; 
114     int m_tsig = 0; // signal number for timer
115 
116     std::mutex m_lock;   // protect epoll
117     pthread_t m_leader = -1; 
118     std::map<conn_key_t, GEventHandler*> m_map; 
119 #  ifdef HAS_SIGTHR
120     // special thread only cares about signal
121     std::thread *m_sigthr = nullptr; 
122 #  endif
123 #endif
124 };

 

  • init,它在底層啟動 thr_num 個線程來跑 run 方法;每次 IO 的塊緩衝區大小由 blksize 指定;它內部還創建了對應的 iocp 或 epoll 對象,便於之后加入 socket 句柄進行處理。
  • exit,它通知線程池中的所有線程退出等待,windows 上是通過 PostQueuedCompletionStatus,Linux 上是通過在自建的一個 pipe 上寫數據以觸發 epoll 退出(這個 pipe 在 init 中創建並加入 epoll);
  • fini,它在所有工作線程退出后,關閉之前創建的對象,清理事件循環用到的資源;
  • cleanup,它清理之前建立的 fd-handler 映射,清理遺留的處理器並釋放資源;
  • run,它是線程池運行函數,windows 上是通過 GetQueuedCompletionStatus 在 iocp 上等待;在 linux 上是通過 epoll_wait 在 epoll 上等待事件。當有事件產生后,根據事件類型,分別調用 do_accept / on_accept、do_recv / on_read、do_error / on_error 回調來分派事件;
  • listen,創建偵聽 socket 並加入到 iocp 或 epoll 中;
  • connect,連接到遠程服務並將成功連接的 socket 加入到 iocp 或  epoll 中;
  • timeout,設置定時器事件,windows 上是通過 CreateTimerQueueTimer 實現定時器超時;linux 則是通過 timer_create 實現的,都是系統現成的東西,只不過在系統定時器到期后,給對應的 iocp 或 epoll 對象發送了一個通知而已,在 linux 上這個通知機制是上面提到過的 pipe 來實現的,因而有一定延遲,不能指定精度太小的定時器;
  • cancel_timer,取消之前設置的定時器。

 

然後看下 GEventHandler 提供的回調接口,應用可以從它派生並完成業務相關代碼:

EventHandler.h

  1 #pragma once
  2 #include "platform.h"
  3 
  4 #ifdef WIN32
  5 // must ensure <winsock2.h> precedes <widnows.h> included, to prevent winsock2.h conflict with winsock.h
  6 #  include <WinSock2.h>
  7 #  include <Windows.h>
  8 #  include <mswsock.h>  // for LPFN_ACCEPTEX & LPFN_GETACCEPTEXSOCKADDRS later in EventBase.h
  9 #else
 10 #  include <unistd.h> // for close
 11 #  include <sys/socket.h>
 12 #  include <sys/epoll.h>
 13 #  include <sys/time.h>
 14 #  include <netinet/in.h> // for struct sockaddr_in
 15 #  include <arpa/inet.h> // for inet_addr/inet_ntoa
 16 #  include <string.h> // for memset/memcpy
 17 #  include <signal.h>
 18 #endif
 19 
 20 #include <mutex>
 21 #include "jsoncpp/json.h"
 22 
 23 
 24 class GEventHandler; 
 25 struct GEV_PER_TIMER_DATA; 
 26 class IEventBase
 27 {
 28 public:
 29 #ifdef WIN32
 30     virtual HANDLE iocp () const = 0; 
 31 #else
 32     virtual int epfd () const = 0; 
 33 #endif
 34 
 35     virtual void* timeout(int due_msec, int period_msec, void *arg, GEventHandler *exist_handler) = 0; 
 36     virtual bool cancel_timer(void* tid) = 0; 
 37     virtual bool post_timer(GEV_PER_TIMER_DATA *gptd) = 0; 
 38 };
 39 
 40 
 41 #ifdef WIN32
 42 enum GEV_IOCP_OP
 43 {
 44     OP_TIMEOUT = 1, 
 45     OP_ACCEPT,
 46     OP_RECV,
 47 };
 48 #else 
 49 // the purpose of this key is to distinguish different connections with same fd !
 50 // (when connection break and re-established soon, fd may not change but port will change)
 51 struct conn_key_t
 52 {
 53     int fd; 
 54     unsigned short lport; 
 55     unsigned short rport; 
 56 
 57     conn_key_t (int f, unsigned short l, unsigned short r); 
 58     bool operator< (struct conn_key_t const& rhs) const; 
 59 }; 
 60 #endif
 61 
 62 
 63 struct GEV_PER_HANDLE_DATA
 64 {
 65     SOCKET so;
 66     SOCKADDR_IN laddr;
 67     SOCKADDR_IN raddr;
 68 
 69 #ifndef WIN32
 70     conn_key_t key () const; 
 71 #endif
 72 
 73     GEV_PER_HANDLE_DATA(SOCKET s, SOCKADDR_IN *l, SOCKADDR_IN *r); 
 74     virtual ~GEV_PER_HANDLE_DATA(); 
 75 };
 76 
 77 struct GEV_PER_IO_DATA
 78 {
 79     SOCKET so;
 80 #ifdef WIN32
 81     GEV_IOCP_OP op;
 82     OVERLAPPED ol;
 83     WSABUF wsa;         // wsa.len is buffer length
 84     DWORD bytes;        // after compeleted, bytes trasnfered
 85 #else
 86     char *buf; 
 87     int len; 
 88 #endif
 89 
 90     GEV_PER_IO_DATA(
 91 #ifdef WIN32
 92             GEV_IOCP_OP o, 
 93 #endif
 94             SOCKET s, int l); 
 95     virtual ~GEV_PER_IO_DATA(); 
 96 };
 97 
 98 struct GEV_PER_TIMER_DATA
 99 #ifdef WIN32
100        : public GEV_PER_IO_DATA
101 #endif
102 {
103     IEventBase *base; 
104     int due_msec; 
105     int period_msec; 
106     void *user_arg;
107     bool cancelled;
108 #ifdef WIN32
109     HANDLE timerque; 
110     HANDLE timer; 
111 #else
112     timer_t timer; 
113 #endif
114 
115     GEV_PER_TIMER_DATA(IEventBase *base, int due, int period, void *arg
116 #ifdef WIN32
117             , HANDLE tq);
118 #else
119             , timer_t tid); 
120 #endif
121 
122     virtual ~GEV_PER_TIMER_DATA(); 
123     void cancel (); 
124 };
125 
126 class GEventHandler
127 {
128 public:
129     GEventHandler();
130     virtual ~GEventHandler();
131 
132     GEV_PER_HANDLE_DATA* gphd(); 
133     GEV_PER_TIMER_DATA* gptd(); 
134     bool connected();
135     void disconnect(); 
136     void clear(); 
137     SOCKET fd(); 
138 
139     int send(char const* buf, int len);
140     int send(std::string const& str);
141     
142     virtual bool reuse();
143     virtual bool auto_reconnect();
144     virtual void arg(void *param) = 0;
145     virtual void reset(GEV_PER_HANDLE_DATA *gphd, GEV_PER_TIMER_DATA *gptd, IEventBase *base);
146     virtual bool on_read(GEV_PER_IO_DATA *gpid) = 0;
147     virtual void on_error(GEV_PER_HANDLE_DATA *gphd); 
148     // note when on_timeout called, handler's base may cleared by cancel_timer, use gptd->base instead if it is not null.
149     virtual bool on_timeout(GEV_PER_TIMER_DATA *gptd) = 0; 
150     virtual void cleanup(bool terminal);
151     void close(bool terminal);
152 
153 protected:
154     GEV_PER_HANDLE_DATA *m_gphd = nullptr; 
155     GEV_PER_TIMER_DATA *m_gptd = nullptr; 
156     IEventBase *m_base = nullptr;
157     // us so instead of m_gphd, 
158     // as the later one may destroyed during using..
159     SOCKET m_so;
160 };
161 
162 // a common handler to process json protocol.
163 class GJsonEventHandler : public GEventHandler
164 {
165 public:
166     //virtual void on_read();
167     virtual void arg(void *param);
168     virtual void reset(GEV_PER_HANDLE_DATA *gphd, GEV_PER_TIMER_DATA *gptd, IEventBase *base);
169     virtual bool on_read(GEV_PER_IO_DATA *gpid);
170     virtual void on_read_msg(Json::Value const& root) = 0;
171     virtual bool on_timeout(GEV_PER_TIMER_DATA *gptd);
172     virtual void cleanup(bool terminal);
173 
174 protected:
175     // protect m_stub to prevent multi-entry
176 #ifdef HAS_ET
177     std::mutex m_mutex; 
178 #endif
179 
180     std::string m_stub;
181 };

 

這裏主要有兩個類,GEventHandler 處理通用的基於流的數據;GJsonEventHandler 處理基於 json 格式的數據。

前者需要重寫 on_read 方法來處理塊數據;後者需要重寫 on_read_msg 方法來處理 json 數據。

目前 json 的解析是通過 jsoncpp 庫完成的,這個庫本身是跨平台的(本 git 庫僅提供 64 位 Linux 靜態鏈接庫及 VS2013 的 32 位 Release 版本 Windows 靜態庫)。

svc_handler 與 clt_handler  均從 GJsonEventHandler 派生。

如果有新的流格式需要處理 ,只需要從 GEventHandler 類派生新的處理類即可。

 

除了讀取連接上的數據,還有其它一些重要的回調接口,列明如下:

  • on_read,連接上有數據到達;
  • on_error,連接斷開;
  • on_tmeout,定時器事件;
  • ……

如果有新的事件需要處理 ,也可以在這裏擴展。

最後看下 GEventBaseWithAutoReconnect 提供的與自動重連相關的接口:

EventBaseAR.h

 1 #pragma once
 2 
 3 
 4 #include "EventBase.h"
 5 #include <thread>
 6 
 7 #define GEV_RECONNECT_TIMEOUT 2 // seconds
 8 #define GEV_MAX_RECONNECT_TIMEOUT 256 // seconds
 9 
10 class GEventBaseWithAutoReconnect : public GEventBase
11 {
12 public:
13     GEventBaseWithAutoReconnect(int reconn_min = GEV_RECONNECT_TIMEOUT, int reconn_max = GEV_MAX_RECONNECT_TIMEOUT);
14     ~GEventBaseWithAutoReconnect();
15 
16     bool do_connect(unsigned short port, void *arg);
17     GEventHandler* connector(); 
18 
19 protected:
20     virtual void on_error(GEventHandler *h);
21     virtual bool on_timeout(GEV_PER_TIMER_DATA *gptd);
22 
23     virtual void on_connect_break(); 
24     virtual bool on_connected(GEventHandler *app);
25 
26 protected:
27     void do_reconnect(void *arg);
28 
29 protected:
30     unsigned short m_port; 
31     GEventHandler* m_app;
32     GEventHandler* m_htimer; 
33     void* m_timer;
34     int m_reconn_min; 
35     int m_reconn_max; 
36     int m_reconn_curr;
37 };

 

其實比較簡單,只比 GEventBase 類多了一個  do_connect 方法,來擴展 connect 不能自動重連的問題。

底層的話,是通過定時器來實現指數後退重連算法的。

 

最後,如果你還是感到雲里霧裡的,可以參考一下下面的類結構圖:

 

 

黑色標註的是框架提供的類,紅色是服務端派生的類,藍色是客戶端派生的類,其實 GMyEventBase 的唯一作用就是將 svc_handler 與 clt_handler 分別引入各自的框架中,

所以用戶的關注點主要還是在派生自己的 GEventHandler 類,並在其中的回調接口中處理數據就可以了。

 

 

後記

這個框架已經應用到我司的公共產品中,併為數個 tcp 服務提供底層支撐,經過百萬級別用戶機器驗證,運行穩定性還是可以的,所以當得起“工業級”這三個字。

 

前面在說到開源庫的選型時還留了一個口子沒有交待,這裏一併說下。

其實最早的重構版本是使用 libevent 來實現的,但是發現它在 windows 上使用的是低效的 select,

而且為了增加、刪除句柄,它又使用了一種 self-pipe-trick 的技巧,簡單說來的就是下面的代碼序列:

listen (listen_fd, 1); 
……
connect (connect_fd, &addr, size); 
……
accept_fd = accept (listen_fd, &addr, &size); 

 

在缺乏 pipe 調用的 win32 環境製造了一個 socket 自連接,從而進行一些通知。

這一步是必要的,如果不能成功連接就會導致整個 libevent 初始化失敗,從而運行不起來。

不巧的是,在一些 windows 機器上(約佔用戶總量 10%),由於防火牆設置嚴格,上述 listen 與 connect 調用可以成功,

但是 accept 會失敗返回,從而導致整個服務退出 (防火牆會嚴格禁止不在白名單上偵聽的端口的連接)。

對於已知端口,可以通過在防火牆上設置白名單來避免,但是對於這種隨機 listen 的端口,真的是太難了,基本無解。

 

回頭考察了一下 asio,windows 上使用的是 iocp,自然沒有這個自連接;

ACE 有多種實現可供選擇,如果使用  ACE_Select_Reactor / ACE_TP_Reactor 是會有這個自連接,

但是你可以選擇其它實現,如基於 WaitForMultipleEvents 的 ACE_WFMO_Reactor(最大隻支持 62 個句柄,放棄),

或基於 iocp 的 ACE_Proactor (前攝式,與反應式在編程上稍有不同,更接近於 asio)就沒有這個自連接。

 

再說的深一點,其實公司最早的網絡庫使用的就是基於 boost 的 asio,大量的使用了 c++ 模板,

有時候產生了一些崩潰,但是根據 dump 完全無法定位崩潰點(各種冗長的模板展開名稱),

導致了一些頑固的已知 bug 一直找不到崩潰點而無法解決(雖然量不大),所以才有了要去重新選型網絡庫以及後來這一系列的東西。

 

本來一開始我是想用 ACE 的,因為我讀過這個庫的源碼,對裏面所有的東西都非常熟悉,

但是看看 ACE 小 5 MB 的 dll 尺寸,還是放棄了(產品本身安裝包也就這麼大吧),

對於一個公司底層的公共組件,被各種產品攜帶,需要嚴格控制“體重”

(後來聽說 ACE 按功能拆分了代碼模塊,你只需要選自己依賴的部分即可,不過我還沒有試過)。

 

使用這個庫代替之前的 boost::asio 后,我還有一個意外收穫,就是編譯出來的 dll 尺寸明顯小了很多,700 K -> 500 K 的樣子,看來所謂模板膨脹是真有其事……

 

最後奉上 gevent 的 github 鏈接,歡迎有相同需求的小夥伴前來“復刻” :

https://github.com/goodpaperman/gevent

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

6萬起月銷過1.5萬輛的大氣家轎 性價比高到離譜!_租車

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

m的峰值扭矩,1。5L的自然吸氣發動機搭配着CVT以及傳統的手動變速箱。艾瑞澤5的全系定價在5。89-9。79萬元之間,跨度不可為不大,新上市車型並不是傳說中搭載了奇瑞最新的渦輪增壓發動機的動力總成車型,依舊是現款艾瑞澤5的動力配置,只是在細分的配置下新增了這款名為“領跑版”定位中高級配置的車。

艾瑞澤5

要說最近火的一塌糊塗的轎車,除了奔馳的豪華中型車全新E級之外,最熱門的或許就是自主品牌的奇瑞艾瑞澤5了,上個月銷量也接近1.5萬台,10月15日,新的艾瑞澤5又迎來上市,這次儘管沒有出現傳說中的1.5T,但是產品線更豐富的艾瑞澤5該如何選擇,或許又會變成消費者心中的難題,小編這次就來分析分析,這台小車該如何選擇。

先簡單說說艾瑞澤5的身份,艾瑞澤5的外觀設計原創程度非常高,畢竟是作為純正向研發的產品之一,艾瑞澤5的設計並沒有什麼明顯的借鑒風格,這一點是值得觀眾朋友點贊的地方,自主品牌開始擺脫仿製,正向研發的車型代表了中國品牌的汽車正在逐漸成長。

前臉貫穿式的鍍鉻裝飾條簡潔明快而且辨識度極高,整車的線條描繪也非常緊湊,沒有拖泥帶水的設計感,車尾層次感也十分豐富,這也讓艾瑞澤5的外觀顯得簡約而不簡單,相反多了一種精緻感。

往期的艾瑞澤5是以紅色為主打顏色,充滿活力的鮮紅色符合當下年輕購車群體充滿朝氣的形象。新的領跑版上市增加了一款名為“鎏光橙”的配色,同樣是暖色的色調,

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

可以看出艾瑞澤5的目標消費人群指向性很明顯——就是年輕的家用車潛在買家。

內飾的設計也是非常具有活力和運動感,中控按鍵布局非常清晰,在行車過程中的使用具有較為良好的溝通感,座椅的包裹性尚可,但是坐墊較硬,承托性較為欠缺。不過作為一款十萬元以內的轎車,如此的內飾設計當屬難能可貴。

空間布局還算合理,車身尺寸為4572*1825*1482(mm);軸距達到了2670mm,已經達到了主流的緊湊型轎車應有的水準,後排空間實用性尚可,而且車身設計中規中矩,並沒有犧牲多少在後排的頭部空間。一般的家用需求得以很好的滿足。

動力總成並沒有進行更換,發動機的賬面參數是116ps的最大馬力,141N.m的峰值扭矩,1.5L的自然吸氣發動機搭配着CVT以及傳統的手動變速箱。

艾瑞澤5的全系定價在5.89-9.79萬元之間,跨度不可為不大,新上市車型並不是傳說中搭載了奇瑞最新的渦輪增壓發動機的動力總成車型,依舊是現款艾瑞澤5的動力配置,只是在細分的配置下新增了這款名為“領跑版”定位中高級配置的車。

以自動擋車型為例,在領跑版本出來之前,領銳版和領臻版的差價達到了1萬元人民幣,分別為7.79萬和8.79萬,這跨度對於一款主打性價比的車型來說著實大了一些。

編輯總結:如今的領跑版上市定價為8.29萬,無疑增加了此款車的性價比。所以小編主推的細分配置是新上市的CVT領跑版,相較於領銳版加價五千,增加了胎壓監測裝置,中控大屏的GpS導航以及增加了更新的智能互聯繫統,這些配置原來只有在8.79萬元的次頂配中搭載。儘管有人說艾瑞澤5的CVT變速箱調校得過於慵懶,但是作為一台十萬元以內,售價只在七八萬元左右的經濟型家轎來說,艾瑞澤5目前的配置和定價都算是比較親民而且高性價比的存在。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

Caviar 推出奢侈黄金版 AirPods Max 、PlayStation 5 ,前者要價 300 萬元起_網頁設計

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

月初我們才剛分享過 CAVIAR 這間來自俄羅斯的客製化品牌的 iPhone 12 Pro 鈦合金潛行款,隨著今年下半年兩款別具話題的產品 AirPods Max 與 PlayStation 5(PS5)開賣後, CAVIAR 這次也將腦筋動到了這兩款產品上。
一如既往, CAVIAR 選用了黃金和鱷魚皮作為客製化版本的材質,當然價格也不是一般消費者能負擔得起的程度,光是相對便宜的黃金版 AirPods Max 就要價約 300 萬元新台幣。

Caviar 推出奢侈黄金版 AirPods Max 、PlayStation 5 ,前者要價 300 萬元起

Caviar 近期宣布將於 2021 年推出度有純金的 AirPods Max ,售價高達 108,000 美元(約合新台幣 304.2 萬元)。據悉這款 AirPods Max 黃金版的耳罩部分將原本的鋁金屬材質改為 18K 金打造,頭帶部分則以鱷魚皮材質取代原本的網狀材質。

CAVIAR 將推出掰色和黑色兩種顏色的鱷魚皮搭配以黃金為主體的 AirPods Max 。雖然 CAVIAR 官方未公布黃金版本的 AirPods Max 機身重量有多重,不過回顧已經不算輕盈的標準版 AirPods Max  重量就有 384.8 公克,黃金版本的重量肯定沈重不少。

耳罩部分包括控制按鈕和旋鈕都以鍍金處理,更提升了整體的奢華感:

價格方面, AirPods Max 黃金版建議售價為 108,000 美元(約合新台幣 304.2 萬元),大約可等同 164 組標準版 AirPods Max 的價位。

與 AirPods Max 建議售價相當的 PlayStation 5 ,也同樣在 CAVIAR 這次黃金版客製化的名單中,黃金版 PS5 一樣採用 18K 黃金材質打造,總共使用了大約 20 公斤的黃金:

與以往 CAVIAR 客製化產品相同, PS5 黃金版主要在外殼上進行重新設計,不僅在機身換上兩塊 3D 打造的 18K 純金實心外殼:

CAVIAR 設計師表示這款 Golden Rock 的 PS5 靈感源自於金礦石的獨特幾何形狀和優美優美的岩石輪廓:

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

除此之外,在 Dual Sense 遊戲控制器除了黃金還有加入鱷魚皮等材質:

PS5 黃金版目前 CAVIAR 尚未公開價格,不過光是以黃金價格來估算就能推測他價格將非常驚人。

圖片/消息來源:CAVIAR

延伸閱讀:
iOS 應用小技巧:自動停止播放影片/音樂,睡前喜愛滑手機的人都必學!

Caviar 發表 iPhone 12 Pro 鈦合金「潛行款」,取消所有相機還加三倍價賣

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

DQN(Deep Q-learning)入門教程(六)之DQN Play Flappy-bird ,MountainCar_台北網頁設計

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

在DQN(Deep Q-learning)入門教程(四)之Q-learning Play Flappy Bird中,我們使用q-learning算法去對Flappy Bird進行強化學習,而在這篇博客中我們將使用神經網絡模型來代替Q-table,關於DQN的介紹,可以參考我前一篇博客:DQN(Deep Q-learning)入門教程(五)之DQN介紹

在這篇博客中將使用DQN做如下操作:

  • Flappy Bird
  • MountainCar-v0

再回顧一下DQN的算法流程:

項目地址:Github

MountainCar-v0

MountainCar的訓練好的Gif示意圖如下所示,汽車起始位置位於山的底部,最終目標是駛向右邊山的插旗的地方,其中,汽車的引擎不能夠直接駛向終點,必須藉助左邊的山體的重力加速度才能夠駛向終點。

MountainCar-v0由OpenAI提供,python包為gym,官網網站為https://gym.openai.com/envs/MountainCar-v0/。在Gym包中,提供了很多可以用於強化學習的環境(env):

在MountainCar-v0中,狀態有2個變量,car position(汽車的位置),car vel(汽車的速度),action一共有3種 Accelerate to the Left Don't accelerateAccelerate to the Right,然後當車達到旗幟的地方(position = 0.5)會得到\(reward = 1\)的獎勵,如果沒有達到則為\(-1\)。但是如果當你運行步驟超過200次的時候,遊戲就會結束。詳情可以參考源代碼(ps:官方文檔中沒有這些說明)。

下面介紹一下gym中幾個常用的函數:

  • env = gym.make("MountainCar-v0")
    

    這個就是創建一個MountainCar-v0的遊戲環境。

  • state = env.reset()
    

    重置環境,返回重置后的state

  • env.render()
    

    將運行畫面展示在屏幕上面,當我們在訓練的時候可以不使用這個來提升速度。

  • next_state, reward, done, _ = env.step(action)
    

    執行action動作,返回下一個狀態,獎勵,是否完成,info。

初始化Agent

初始化Agent直接使用代碼說明吧,這個還是比較簡單的:

import keras
import random
from collections import deque

import gym
import numpy as np
from keras.layers import Dense
from keras.models import Sequential


class Agent():
    def __init__(self, action_set, observation_space):
        """
        初始化
        :param action_set: 動作集合
        :param observation_space: 環境屬性,我們需要使用它得到state的shape
        """
        # 獎勵衰減
        self.gamma = 1.0
        # 從經驗池中取出數據的數量
        self.batch_size = 50
        # 經驗池
        self.memory = deque(maxlen=2000000)
        # 探索率
        self.greedy = 1.0
        # 動作集合
        self.action_set = action_set
        # 環境的屬性
        self.observation_space = observation_space
        # 神經網路模型
        self.model = self.init_netWork()

    def init_netWork(self):
        """
        構建模型
        :return: 模型
        """
        model = Sequential()
        # self.observation_space.shape[0],state的變量的數量
        model.add(Dense(64 * 4, activation="tanh", input_dim=self.observation_space.shape[0]))
        model.add(Dense(64 * 4, activation="tanh"))
        # self.action_set.n 動作的數量
        model.add(Dense(self.action_set.n, activation="linear"))
        model.compile(loss=keras.losses.mean_squared_error,
                      optimizer=keras.optimizers.RMSprop(lr=0.001))
        return model

我們使用隊列來保存經驗,這樣的話新的數據就會覆蓋遠古的數據。此時我們定義一個函數,專門用來將數據保存到經驗池中,然後定義一個函數用來更新\(\epsilon\)探索率。

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

def add_memory(self, sample):
    self.memory.append(sample)

def update_greedy(self):
    # 小於最小探索率的時候就不進行更新了。
    if self.greedy > 0.01:
        self.greedy *= 0.995

訓練模型

首先先看代碼:

def train_model(self):
    # 從經驗池中隨機選擇部分數據
    train_sample = random.sample(self.memory, k=self.batch_size)

    train_states = []
    next_states = []
    for sample in train_sample:
        cur_state, action, r, next_state, done = sample
        next_states.append(next_state)
        train_states.append(cur_state)

    # 轉成np數組
    next_states = np.array(next_states)
    train_states = np.array(train_states)
    # 得到next_state的q值
    next_states_q = self.model.predict(next_states)

    # 得到state的預測值
    state_q = self.model.predict_on_batch(train_states)

    # 計算Q現實
    for index, sample in enumerate(train_sample):
        cur_state, action, r, next_state, done = sample
        if not done:
            state_q[index][action] = r + self.gamma * np.max(next_states_q[index])
        else:
            state_q[index][action] = r
    
    self.model.train_on_batch(train_states, state_q)

大家肯定從上面的代碼發現一些問題,使用了兩個for循環,why?首先先說一下兩個for循環分別的作用:

  • 第一個for循環:得到train_statesnext_states,其中next_states是為了計算Q現實。
  • 第二個for循環:計算Q現實

可能有人會有一個疑問,為什麼我不寫成一個for循環呢?實際上寫成一個for循環是完全沒有問題的,很,但是寫成一個for循環意味着我們要多次調用model.predict_on_batch,這樣會耗費一定的時間(親身試驗過,這樣會比較慢),因此,我們寫成了兩個for循環,然後只需要調用一次predict

執行動作與選擇最佳動作

執行動作的代碼如下所示:

def act(self, env, action):
    """
    執行動作
    :param env: 執行環境
    :param action: 執行的動作
    :return: ext_state, reward, done
    """
    next_state, reward, done, _ = env.step(action)

    if done:
        if reward < 0:
            reward = -100
        else:
            reward = 10
    else:
        if next_state[0] >= 0.4:
            reward += 1

    return next_state, reward, done

其中,我們可以修改獎勵以加快網絡收斂。

選擇最好的動作的動作如下所示,會以一定的探索率隨機選擇動作。

def get_best_action(self, state):
    if random.random() < self.greedy:
        return self.action_set.sample()
    else:
        return np.argmax(self.model.predict(state.reshape(-1, 2)))

開始訓練

關於具體的解釋,在註釋中已經詳細的說明了:

if __name__ == "__main__":
    # 訓練次數
    episodes = 10000
    # 實例化遊戲環境
    env = gym.make("MountainCar-v0")
    # 實例化Agent
    agent = Agent(env.action_space, env.observation_space)
    # 遊戲中動作執行的次數(最大為200)
    counts = deque(maxlen=10)

    for episode in range(episodes):
        count = 0
        # 重置遊戲
        state = env.reset()

        # 剛開始不立即更新探索率
        if episode >= 5:
            agent.update_greedy()

        while True:
            count += 1
            # 獲得最佳動作
            action = agent.get_best_action(state)
            next_state, reward, done = agent.act(env, action)
            agent.add_memory((state, action, reward, next_state, done))
            # 剛開始不立即訓練模型,先填充經驗池
            if episode >= 5:
                agent.train_model()
            state = next_state
            if done:
                # 將執行的次數添加到counts中
                counts.append(count)
                print("在{}輪中,agent執行了{}次".format(episode + 1, count))

                # 如果近10次,動作執行的平均次數少於160,則保存模型並退出
                if len(counts) == 10 and np.mean(counts) < 160:
                    agent.model.save("car_model.h5")
                    exit(0)
                break

訓練一定的次數后,我們就可以得到模型了。然後進行測試。

模型測試

測試的代碼沒什麼好說的,如下所示:

import gym
from keras.models import load_model
import numpy as np
model = load_model("car_model.h5")

env = gym.make("MountainCar-v0")

for i in range(100):
    state = env.reset()
    count = 0
    while True:
        env.render()
        count += 1
        action = np.argmax(model.predict(state.reshape(-1, 2)))
        next_state, reward, done, _ = env.step(action)
        state = next_state
        if done:
            print("遊戲的次數:", count)
            break

部分的結果如下:

Flappy Bird

FlappyBird的代碼我就不過多贅述了,裏面的一些函數介紹可以參照這個來看:DQN(Deep Q-learning)入門教程(四)之Q-learning Play Flappy Bird,代碼思想與訓練Mountain-Car基本是一致的。

import random
from collections import deque

import keras
import numpy as np
from keras.layers import Dense
from keras.models import Sequential
from ple import PLE
from ple.games import FlappyBird


class Agent():
    def __init__(self, action_set):
        self.gamma = 1
        self.model = self.init_netWork()
        self.batch_size = 128
        self.memory = deque(maxlen=2000000)
        self.greedy = 1
        self.action_set = action_set

    def get_state(self, state):
        """
        提取遊戲state中我們需要的數據
        :param state: 遊戲state
        :return: 返回提取好的數據
        """
        return_state = np.zeros((3,))
        dist_to_pipe_horz = state["next_pipe_dist_to_player"]
        dist_to_pipe_bottom = state["player_y"] - state["next_pipe_top_y"]
        velocity = state['player_vel']
        return_state[0] = dist_to_pipe_horz
        return_state[1] = dist_to_pipe_bottom
        return_state[2] = velocity
        return return_state

    def init_netWork(self):
        """
        構建模型
        :return:
        """
        model = Sequential()
        model.add(Dense(64 * 4, activation="tanh", input_shape=(3,)))
        model.add(Dense(64 * 4, activation="tanh"))
        model.add(Dense(2, activation="linear"))

        model.compile(loss=keras.losses.mean_squared_error,
                      optimizer=keras.optimizers.RMSprop(lr=0.001))
        return model

    def train_model(self):
        if len(self.memory) < 2500:
            return

        train_sample = random.sample(self.memory, k=self.batch_size)
        train_states = []
        next_states = []

        for sample in train_sample:
            cur_state, action, r, next_state, done = sample
            next_states.append(next_state)
            train_states.append(cur_state)
        # 轉成np數組
        next_states = np.array(next_states)
        train_states = np.array(train_states)

        # 得到下一個state的q值
        next_states_q = self.model.predict(next_states)
        # 得到預測值
        state_q = self.model.predict_on_batch(train_states)

        for index, sample in enumerate(train_sample):
            cur_state, action, r, next_state, done = sample
            # 計算Q現實
            if not done:
                state_q[index][action] = r + self.gamma * np.max(next_states_q[index])
            else:
                state_q[index][action] = r
        self.model.train_on_batch(train_states, state_q)

    def add_memory(self, sample):
        self.memory.append(sample)

    def update_greedy(self):
        if self.greedy > 0.01:
            self.greedy *= 0.995

    def get_best_action(self, state):
        if random.random() < self.greedy:
            return random.randint(0, 1)
        else:
            return np.argmax(self.model.predict(state.reshape(-1, 3)))

    def act(self, p, action):
        """
        執行動作
        :param p: 通過p來向遊戲發出動作命令
        :param action: 動作
        :return: 獎勵
        """
        r = p.act(self.action_set[action])
        if r == 0:
            r = 1
        if r == 1:
            r = 100
        else:
            r = -1000
        return r


if __name__ == "__main__":
    # 訓練次數
    episodes = 20000
    # 實例化遊戲對象
    game = FlappyBird()
    # 類似遊戲的一個接口,可以為我們提供一些功能
    p = PLE(game, fps=30, display_screen=False)
    # 初始化
    p.init()
    # 實例化Agent,將動作集傳進去
    agent = Agent(p.getActionSet())
    max_score = 0
    scores = deque(maxlen=10)

    for episode in range(episodes):
        # 重置遊戲
        p.reset_game()
        # 獲得狀態
        state = agent.get_state(game.getGameState())
        if episode > 150:
            agent.update_greedy()
        while True:
            # 獲得最佳動作
            action = agent.get_best_action(state)
            # 然後執行動作獲得獎勵
            reward = agent.act(p, action)
            # 獲得執行動作之後的狀態
            next_state = agent.get_state(game.getGameState())
            agent.add_memory((state, action, reward, next_state, p.game_over()))
            agent.train_model()
            state = next_state
            if p.game_over():
                # 獲得當前分數
                current_score = p.score()
                max_score = max(max_score, current_score)
                scores.append(current_score)
                print('第%s次遊戲,得分為: %s,最大得分為: %s' % (episode, current_score, max_score))
                if len(scores) == 10 and np.mean(scores) > 150:
                    agent.model.save("bird_model.h5")
                    exit(0)
                break

該部分相比較於Mountain-Car需要更長的時間,目前的我還沒有訓練出比較好的效果,截至寫完這篇博客,最新的數據如下所示:

emm,我又不想讓我的電腦一直開着,。

總結

上面的兩個例子便是DQN最基本最基本的使用,我們還可以將上面的FlappyBird的問題稍微複雜化一點,比如說我們無法直接的知道環境的狀態,我們則可以使用CNN網絡去從遊戲圖片入手(關於這種做法,網絡上有很多人寫了相對應的博客)。

項目地址:Github

參考

  • openai-gym
  • MountainCar-v0 ——src code
  • DQN(Deep Q-learning)入門教程(四)之Q-learning Play Flappy Bird

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

UWP開發入門(25)——通過Radio控制Bluetooth, WiFi_網頁設計公司

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

回顧寫了許久的UWP開發入門,竟然沒有講過通過Windows.Devices.Radios.Radio來控制Bluetooth和WiFi等功能的開關。也許是因為相關的API設計的簡單好用,以至於被我給忽略了。最近工作中有涉及這塊的內容,不妨一起來回顧下,順便看看一些新的發現。

在Windows 10以前,想要控制Bluetooth,WiFi等功能,那麻煩大了。得操作ManagementBaseObject,ManagementEventWatcher 等一系列WMI提供的API,寫出來的代碼又臭又長。其間還夾着複雜的WMI query字符串,十分難用。

升級到Windows 10后,我們通過Windows.Devices.Radios.Radio可以方便的獲取控制Bluetooth和WiFi的對象。

var radios = await Radio.GetRadiosAsync();
Bluetooth = radios.FirstOrDefault(r => r.Kind == RadioKind.Bluetooth);
WiFi = radios.FirstOrDefault(r => r.Kind == RadioKind.Bluetooth);

在拿到上面的Bluetooth和WiFi的Radio實例后,就可以通過

Public event TypedEventHandler<Radio, object> StateChanged;

來監聽Radio實例的狀態改變,可以說通過寥寥幾行代碼,就可以替代以往大量繁瑣的操作。

而設置Bluetooth和WiFi設備On/Off的狀態,也非常簡單。

public IAsyncOperation<RadioAccessStatus> SetStateAsync(RadioState value);

RadioState枚舉如同字面的意思:

    public enum RadioState
    {
        //
        // Summary:
        //     The radio state is unknown, or the radio is in a bad or uncontrollable state.
        Unknown = 0,
        //
        // Summary:
        //     The radio is powered on.
        On = 1,
        //
        // Summary:
        //     The radio is powered off.
        Off = 2,
        //
        // Summary:
        //     The radio is powered off and disabled by the device firmware or a hardware switch
        //     on the device.
        Disabled = 3
    }

這裏需要提一下的是,在第一次更改狀態前,UWP APP需要向用戶申請權限。

慢着慢着,貌似忘記給UWP APP向Windows要權限了,我們要編輯Package.aaxmanifest文件,在Capabilities節點加上DeviceCapability這一行才行。

  <Capabilities>
    <Capability Name="internetClient" />
    <DeviceCapability Name="radios"></DeviceCapability>
  </Capabilities>

 

這回運行起來,才真的可以操作Bluetooth和WiFi了。

是不是覺得幾行代碼就能寫出一個控制Bluetooth和WiFi的APP了?事實也確實如此。

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

如果人生只如初見那般美好,UWP的處境就不會如此難堪了。對於某軟提供了完善UWP API的功能,開發起來那是一個爽快。但要是沒有或是沒來得及提供,UWP就顯得頗為尷尬了。

我們不妨看下RadioKind的枚舉,很顯然如果想操作FM radio就得另尋他法。而MobileBoardband即Cellular也是從1703版本才開始得到支持。

那是不是意味這MobileBroadband就可以像Bluetooth和WiFi一樣通過

public static IAsyncOperation<IReadOnlyList<Radio>> GetRadiosAsync();

來獲取實例對象了,還真不是,欲知如何操作,且聽下回《UWP開發入門(26)——通過Radio控制Cellular》。

實際是我Sample code還沒整理好。所以分成了兩篇來寫。

有感日前MS Store里的網易雲音樂UWP也被替換成Win32版本,可嘆國產的UWP APP越來越少。某軟畫了個好餅,可惜不能讓人在Windows生態上通過UWP掙到錢。好技術生不逢時出不了頭,真是可惜。

同時也能感覺到某軟的妥協和進步,現如今的UWP,結合desktop extension以及desktop bridge技術。只要公司的APP能通過某軟的審核,功能方面已經無限接近傳統desktop APP了。可惜一個Windows平台做Win32和UWP兩個產品,燒的錢可不是小數目。總不能用愛發電吧。

希望Win7早日被淘汰,WinUI 3.0能進一步融合UWP和Win32。距離上一次某軟說要重振desktop開發已經過去蠻久了。

本篇提到的相關Sample code在GitHub:

https://github.com/manupstairs/UWPSamples/tree/master/UWPSamples/RadioDevice

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

為什麼國產品牌SUV能贏合資品牌?你買車會選誰?_網頁設計

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

從是否引入全新車型,合資品牌內部便要經過長時間的項目討論。出於商業上的原因,合資品牌的外資方對於新車型的引入,是持極其謹慎的態度的。所以,儘管合資品牌的中方人員已經意識到這一問題,但往往也是處於干著急的情況,畢竟中方在大部分的情況下,是沒有獨立開發新車型的權力的。

根據乘聯會公布的數據显示,2016年上半年,國內SUV銷量共計391.5萬輛。而中國自主品牌以56.6%的絕對性優勢,成功稱霸了這個時下國內最為火熱的汽車細分市場。在上半年SUV銷量前10排名中,自主品牌共佔到了6個席位。這就不禁讓人有一個疑問:合資品牌SUV為什麼會一敗塗地?

SUV市場的火熱

是合資品牌做夢都沒有想到的

2003年,國內的SUV銷量僅為9.74萬輛,連如今哈弗H6半年的銷量都達不到。但十年過後的2013年,國內的SUV銷量迅速膨脹至266.13萬輛,堪稱是火箭式的增長。對於多年來一直注重發展轎車市場的合資品牌來說,SUV市場誇張式的增長以及市場規模的龐大程度一直是一件讓它們難以相信的事情。這種對市場形勢的誤判,為合資品牌在SUV市場上的糟糕市場表現埋下了深深的伏筆。

十個手指頭能數完的合資SUV

早在1994年的日內瓦車展上,豐田便發布了被譽為“城市SUV鼻祖”的SUV車型—RAV4。比越野車更時尚的外觀,比普通轎車能寬裕的空間,比轎車略為優秀的通過能力,從全世界的範圍來說,SUV都是一種具有深深吸引力的車型。但可惜的是,消費者對於SUV的追捧,並沒有讓合資品牌加深在SUV車型上的研發,特別是針對中國市場。

直至8年之後,也就是2002年,才有了第一款國產的合資SUV—東風本田CRV的出現。這8年間,竟然沒有一家合資品牌在中國市場推出一款SUV車型,這是一件如今讓人無法相信的事情。而即便有了第一款國產合資SUV的出現,但合資品牌對於中國的SVU市場依舊沒有太多的重視。即便是在中國汽車市場具有絕對品牌影響力的大眾汽車,把途觀引入國內生產的時間已經是2009年的事情。

自主品牌已經贏在了起跑線上

相反,不少的自主品牌已經在SUV真槍實彈地幹了起來。譬如,大家眾所周知的雙環汽車(沒錯,就是那家被本田告上法庭,還反坑本田1600萬元賠償的雙環汽車),在2004年便山寨本田的CRV,推出了大名鼎鼎的雙環SRV。而如今國內SUV的銷量冠軍—長城汽車,在2002年也依靠皮卡的平台基礎,生產出了自家的第一款SUV—長城賽弗。更不要說具有傳奇色彩,賣到南美市場的奇瑞瑞虎。所以說,從第一步棋開始,

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

合資品牌便已經輸在了起跑線上。

合資品牌龜速一樣的產品研發

意識到落後的合資品牌開始奮力直追,但要知道合資車企的內部運營遠沒有自主品牌靈活。從是否引入全新車型,合資品牌內部便要經過長時間的項目討論。出於商業上的原因,合資品牌的外資方對於新車型的引入,是持極其謹慎的態度的。

所以,儘管合資品牌的中方人員已經意識到這一問題,但往往也是處於干著急的情況,畢竟中方在大部分的情況下,是沒有獨立開發新車型的權力的。在確定要引入全新的SUV車型以後,還要進行一定的本土化適配、車輛測試等長時間的流程。比如國內著名的合資品牌–X安X特的一款SUV,整車試驗的流程,便有4000多項。所以,目前合資品牌的SUV研發以及生產的速度,完全可以用緩慢去形容。

自主品牌的地頭蛇優勢

但相比合資品牌,自主品牌完全就是地頭蛇一般的靈活。依靠國外成熟的SUV進行逆向研發,大大縮小研發所需的研發。再通過三菱、愛信等成熟的零部件供應商,完成最核心的動力組裝。當然,自主品牌不但在產品的設計、製造有着相當的速度優勢。在推出全新車型的決策上,同樣具有優勢。譬如國內的某知名的自主品牌,自今年3月推出緊湊型SUV以來,依次又在5月、8月,相繼發布了兩款全新的緊湊型SUV。新品推出的速度之快,不僅讓業內人士感到詫異,消費者同樣感到詫異。

神一般的速度

更著名的,如漢騰汽車,今年的5月份才宣布完成品牌的發布。僅僅過了4月的時候,旗下的首款緊湊型SUV—漢騰X7便騰空出世。從品牌的構建,到產品的設計研發,再到生產線的投產,漢騰僅用了4個月的時間。這幾乎是世界上任何一家合資品牌都無法完成的事情。

不過,有部分的自主品牌存在過於追求投產速度,希望更早地享受到SUV市場的紅利,而在生產、製造環節存在把控不嚴的情況。也正是這個原因,談到自主SUV,許多的消費者第一時間都會拋出諸如“耐用性怎麼樣?”“小問題多不多”的疑問。這也是這種運作的靈活性給自主品牌帶來的負面影響。

不過儘管合資品牌SUV在產品的投放速度不如自主品牌,但根據相關的數據显示,截止2015年底,17萬以上區間的SUV市場,98%的市場份額依然由合資品牌所垄斷。而自主品牌主要活躍在中端(13-17萬)、次中端(9-13萬)、低端(9萬以下)等SUV市場。也就是說,儘管自主品牌佔據了SUV的大部分市場份額,但更具有盈利能力的高端SUV市場依然是合資品牌的天下。如何在保障銷量漂亮的同時,改善產品的質量,以及提高自身品牌在高端SUV市場的影響力,是目前自主品牌亟待需要思考的問題。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

火樹銀花_台北網頁設計

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

南澗跳菜為節日添彩_網頁設計公司網頁設計公司,網頁設計,台北網頁設計

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

  跳菜之猴子嬉鬧。
  適志宏攝

  跳菜之空手疊塔。
  適志宏攝

  “南澗跳菜”又被叫做“抬菜舞”或者“捧盤舞”,將舞蹈、音樂與雜技、飲食融為一體,是雲南南澗彝族自治縣群眾流傳已久的習俗。彝族跳菜2008年被列入第二批國家級非物質文化遺產名錄。

  “跳菜有‘宴席跳菜’和‘表演跳菜’兩種形式。‘表演跳菜’主要是在舞台上,菜不是真菜,主要是通過舞蹈和音樂來表現。我們日常做的主要是‘宴席跳菜’,抬的是真菜,這是每一次宴席慶典的必備儀式,往往是我們接待賓客的最高禮節。”跳菜隊隊長張玉香說。

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

  音樂聲響,張玉香和另一位隊員從廚房跳出,一人在前面引舞護菜,另一人在後面舞着托盤,兩人踏着節拍登場。輕快敏捷的舞步,幽默逗人的扮相,混着激昂的嗩吶聲,引起現場陣陣歡笑。

  “口功送菜”和“空中送菜”是張玉香的拿手絕技。在村裡跳菜,還有許多套路,張玉香說:“不管採用什麼樣的形式,最關鍵的就是保證把這些寓意吉祥的菜平平穩穩地上到桌上。”

  在舞姿的變化中,跳菜隊員們利落地把菜擺放到桌上。不僅跳舞有講究,擺菜的形式也很講究。“梅花形是我們常用的擺法,等這些農家菜上齊了,大家就可以一起享受這些美味佳肴啦。”張玉香說。

  版式設計:蔡華偉 張丹峰

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

Sony Mobile 新旗艦 Xperia 1 III 爆料規格曝光:將搭載 S888 處理器、4K HDR 螢幕亮度提升_網頁設計

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

在首款搭載高通 Snapdragon 888 處理器的小米11 推出後,接下來在 2021 年包括華碩、黑鯊、聯想、LG、魅族、motorola、nubia、realme、OnePlus、OPPO、Sharp、vivo 與 ZTE 都確定將成為首批搭載的的手機品牌。而近期網傳 Sony Mobile  接下來的 Xperia 1 系列新旗艦 Xperia 1 III 也可能會搭載此處理器。

▲示意圖,圖為 Xperia 1 II

Sony Mobile 新旗艦 Xperia 1 III 爆料規格曝光:將搭載 S888 處理器、4K HDR 螢幕亮度提升

Sony Mobile 今年在台灣推出包括 Xperia 1 II 以及 Xperia 5 II 兩款旗艦級手機,而根據往年在 MWC(Mobile World Congress)也都會發表新機。然而受到疫情影響,接下來在 2021 年也還有許多變數。不過最近在 Twitter 也有人開始爆料 Xperia 1 系列下一代旗艦新機 Xperia 1 III(暫稱)的規格。

▲示意圖,圖為 Xperia 1 II

爆料指出 Xperia 1 III 將一樣採用 6.5 吋 4K OLED HDR 螢幕,不過螢幕亮度部分將比起 Xperia 1 II 提升 15% 。硬體規格方面,也機搭載高通 Snapdragon 888  5G 處理器、配備 8GB RAM 和 256GB ROM 。指紋辨識也延續採用側邊指紋辨識的方式,全機也支持 IP65/IP68 防塵防水等級。

▲示意圖,圖為 Xperia 1 II

而爆料尚未提到 Sony 一直以來強調的相機拍攝功能,若沒意外這次應繼續和蔡司認證的鏡頭,在相機鏡頭搭配、拍攝穩定性、夜拍方面進行升級。

▲示意圖,圖為 Xperia 1 II

不過爆料則有提到 Xperia 1 III 可能的售價,傳聞為 1,199 美元起(約合新台幣 33,770 元)。

▲圖片來源:Anthony (Twitter/@TheGalox_)

消息來源:Anthony (Twitter/@TheGalox_)

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

延伸閱讀:
realme 7 5G 開箱動手玩|天璣 800U 處理器、5G+5G 雙卡雙待、120Hz 更新率螢幕、5000mAh大電量與 30W Dart 閃充,萬元內 5G 超值選擇

小米11 Pro 傳聞規格首次曝光:預計 2021 上半年推出, Redmi K11 將為同期最便宜 S888 旗艦新機之一

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

Node.js躬行記(2)——文件系統和網絡_台北網頁設計台北網頁設計

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

一、文件系統

  fs模塊可與文件系統進行交互,封裝了常規的POSIX函數。POSIX(Portable Operating System Interface,可移植操作系統接口)是UNIX系統的一個設計標準。fs模塊提供了多個操作目錄和文件的方法,大多會有同步和異步兩種版本,同步的方法名稱會以Sync為後綴。

1)目錄處理

  fs模塊的readdir()方法可異步的讀取目錄內容,回調函數包含兩個參數,第一個是錯誤對象,第二個是一個包含文件名稱的數組,對應的同步方法是readdirSync()。在下面的示例中,當前目錄包含兩個文件,上一級目錄包含一個目錄名稱。

const fs = require('fs');
fs.readdir('./', (err, files) => {
  console.log(files);        //[ 'demo.txt', 'index.js' ]
});
fs.readdir('../', (err, files) => {
  console.log(files);        //[ '1' ]
});

  其它處理目錄的方法還有opendir()、mkdir()等。

2)讀寫文件

  在fs模塊中,可使用批量方法readFile()將文件內容一次性的加載到內存中,如下所示。

const fs = require('fs');
fs.readFile('./origin.txt', (err, buf) => {
  console.log(buf.toString());        //"hello Node.js"
});

  對應的寫入方法是writeFile(),如下所示,文件路徑、寫入內容和回調函數是必傳的參數。如果文件不存在,那麼會自動創建。

fs.writeFile('./target.txt', 'hello Node.js', (err) => {
  if (err) throw err;
  console.log('文件已被保存');
});

  當文件很大時,像上面這樣直接讀取會有問題,可以改用流式方法createReadStream(),分批次的讀取文件,如下所示,每次只讀7個字節的內容。

const readable = fs.createReadStream('./origin.txt', {highWaterMark: 7});
readable.on("data", (chunk) => {
  /*************/
  /* "hello N"
  /* "ode.js"
  /************/
  console.log(chunk.toString());
});

  通過管道方法pipe()將origin.txt中的內容寫入到target-stream.txt中,如下所示,對於不存在的文件,也會自動創建。

const writable = fs.createWriteStream('./target-stream.txt');
readable.pipe(writable);

3)文件描述

  fs模塊的stat()方法可讀取文件的描述信息,如下所示。

fs.stat('./demo.txt', (err, stats) => {
  console.log(stats);
});

  回調函數中的stats參數是一個fs.Stats對象,其屬性如下所示。它還包含一些判斷方法,例如isDirectory()、isFile()等。

Stats {
  dev: 195650,
  mode: 33206,
  nlink: 1,
  uid: 0,
  gid: 0,
  rdev: 0,
  blksize: undefined,
  ino: 36873221949682120,
  size: 13,
  blocks: undefined,
  atimeMs: 1586227933993.0217,
  mtimeMs: 1585882949091.0166,
  ctimeMs: 1586227933995.0222,
  birthtimeMs: 1586227933993.0217,
  atime: 2020-04-07T02:52:13.993Z,
  mtime: 2020-04-03T03:02:29.091Z,
  ctime: 2020-04-07T02:52:13.995Z,
  birthtime: 2020-04-07T02:52:13.993Z
}

  fs模塊還提供了fstat()方法,在功能上與stat()等價,只是fstat()方法的第一個參數是文件描述符。在POSIX系統中,文件描述符是一個正整數,它實際上是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當打開或創建一個文件時,就會被分配一個文件描述符。

  在下面的示例中,open()方法的回調函數中包含一個fd參數(即文件描述符),搭配fstat()方法就能讀取文件信息。

fs.open('./demo.txt', 'r', (err, fd) => {
  fs.fstat(fd, (err, stats) => {
    console.log(stats);
  });
});

4)監控文件

  fs模塊提供了兩種方法來監控文件:watch()和watchFile(),前者能監控文件或目錄的更改,後者只能監控文件的更改。

  watch()方法的監聽器回調包含兩個參數,第一個是事件類型(包括rename和change),第二個是觸發事件的文件名稱,如下所示。

fs.watch('./demo.txt', (eventType, filename) => {
  console.log(eventType, filename);
});

  執行node命令后,每次更改demo.txt文件,就會在控制台打印出下面這條語句。

$ node index.js
change demo.txt

  雖然watch()方法的性能優於watchFile()方法,但是watch()不是一個跨平台的方法,其表現在各個平台中並非百分百一致(例如filename參數不能保證提供),而watchFile()是跨平台的。

5)異步文件

  fs.promises是Node提供的一組備用的異步文件系統方法,它們會返回Promise對象而不是通過回調來處理結果。例如以Promise的方式使用stat()方法,如下所示。

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

const fsPromises = require('fs').promises;
fsPromises.stat('./demo.txt').then(stats => {
  console.log(stats);
});

二、網絡

  Node可以處理的網絡協議包括HTTP、TCP、HTTPS等,本節將會重點分析HTTP協議。Node中的http模塊基於net、stream、events等模塊,提供了一系列的網絡傳輸的底層API,可創建HTTP服務器和客戶端,包含重定向、代理、上傳數據等功能。

1)HTTP服務器

  通過http.createServer()方法就能創建HTTP服務器,其內部會調用net.Server()方法,返回一個http.Server實例。當服務被創建后,就能通過server.listen()方法監聽一個端口。

  在下面的示例中,監聽的端口是8000,createServer()的回調函數包含兩個參數:req和res,分別表示HTTP的請求和響應,前者是IncomingMessage類(一個可讀流)的實例,後者是ServerResponse類(一個可寫流)的實例。__dirname是一個全局變量,保存着當前文件所處的路徑。

const http = require('http'); 
const fs = require('fs'); 
const server = http.createServer(function (req, res) {
  fs.readFile(__dirname + "/index.html", function (err, data) {
    if (err) {
      res.statusCode = 500;
      res.end(String(err));
    } else {
      res.end(data);
    }
  });
}).listen(8000);

  在回調函數中,可修改或解析響應報文,例如像上面當發生錯誤時,將狀態碼改成500。在瀏覽器中訪問http://localhost:8000,就能通過end()方法在頁面中輸出index.html的文件內容。

  注意,必須調用響應對象的end()方法結束此次響應,如果省略,那麼頁面將會一直處於加載中,阻塞內容渲染。

  http.Server包含一組事件,例如connection和request,如下所示。前者會在建立新的TCP流時觸發,後者會在每次請求服務器時觸發。

server.on("connection", function (socket) {
  console.log(connection");
});
server.on("request", function (req, res) {
  console.log('request');
});

2)重定向

  3XX格式的狀態碼用於重定向,例如在Node中實現302跳轉,如下所示。

http.createServer(function (req, res) {
  if(req.url == '/strick') {
    res.writeHead(302, {'Location': 'http://www.pwstrick.com'});
  }
  res.end();
}).listen(8000);

  當瀏覽器訪問http://localhost:8000/strick時,才會執行重定向。writeHead()方法可一次性設置響應的狀態碼和所有的首部。如果只想設置單個響應首部,可以使用setHeader()方法。

3)上傳數據

  在實際的業務開發中,免不了上傳數據的需求,例如表單提交、圖像上傳等。下面是一張form表單,包含一個文本框和一個提交按鈕。

<form action="http://localhost:8000" method="post">
  <input type="text" name="name" />
  <button type="submit">提交</button>
</form>

  點擊提交按鈕,會將整張表單提交到HTTP服務器(代碼如下所示),假設文本框中輸入的內容為“咖啡機strick”。

const http = require('http');
const querystring = require('querystring');

const server = http.createServer(function (req, res) {
  //聲明Content-Type響應首部,以免中文亂碼
  res.setHeader("Content-Type", "application/json; charset=utf-8");
  const arr = [];
  req.on("data", chunk => {
    arr.push(chunk);
  });
  req.on("end", () => {
    const buf = Buffer.concat(arr);        //拼接Buffer數據
    const params = querystring.decode(buf.toString());
    console.log(params.name);             //"咖啡機strick"
    res.write(JSON.stringify(params));    //響應數據
    res.end();
  });
}).listen(8000);

  在data事件中接收數據並添加到arr數組中,然後在end事件中由Buffer.concat()方法拼接Buffer數據。querystring模塊的decode()方法可將查詢字符串解析成一個對象。write()方法會將響應數據發送給客戶端。

4)客戶端服務

  http模塊提供了request()方法,可讓客戶端向服務器發起請求。在下面的示例中,postData變量是要發送的數據,options變量是各種配置參數,包括請求首部的信息。

const http = require('http'); 
const querystring = require('querystring');

const postData = querystring.stringify({
  'name': '咖啡機strick'
});
const options = {
  hostname: 'localhost',
  port: 8000,
  path: '/',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'Content-Length': Buffer.byteLength(postData)
  }
};
const req = http.request(options, (res) => {
  const code = res.statusCode;                    //狀態碼
  const headers = JSON.stringify(res.headers);       //響應首部
  res.setEncoding('utf8');         //為可讀流設置字符編碼
  res.on('data', (chunk) => {
    console.log(chunk);            //響應數據
  });
  res.on('end', () => {
    console.log('響應中已無數據');
  });
});
req.write(postData);
req.end();

  注意,在請求最後需要調用end()方法,結束本次請求。Node還提供了一個基於request()的便捷方法:get(),它會自動調用req.end()方法,並且請求方法是GET。

5)代理

  代理是一種特殊的網絡服務,允許一個客戶端通過這個服務與目標服務器進行間接的連接,也就是在它們之間建立了一層中轉,可用來過濾廣告、控制內部資源的訪問權限、保障終端的隱私或安全等。

  http模塊可搭建出一個簡易的HTTP代理服務器,下面示例引用自《Node.js硬實戰》技巧52一節。

const http = require('http');
const url = require('url');
http.createServer(function (req, res) {
  const options = url.parse(req.url);  //一個URL對象
  options.headers = req.headers;
  
  req.on('data', chunk => {            //將請求的原始數據發送給代理
    proxyRequest.write(chunk);
  });
  req.on('end', chunk => {             //完成原始請求
    proxyRequest.end();
  });
  
  //複製原始請求
  const proxyRequest = http.request(options, proxyResponse => {
    proxyResponse.on('data', chunk => {    //將響應的原始數據發送給客戶端
      res.write(chunk);
    });
    proxyResponse.on('end', chunk => {     //完成代理請求
      res.end();
    });
    //將響應報文發送給客戶端
    res.writeHead(proxyResponse.statusCode, proxyResponse.headers);
  });
}).listen(8000);

  url模塊的parse()方法能將字符串轉換成一個URL對象。在綁定請求對象的data事件后,就能將客戶端的請求數據發送給代理。通過http.request()方法發起一次代理請求,並將服務器的響應數據發送給客戶端。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。