ACE之(二)ACE Socket封裝器
2 ACE Socket封裝器
這部分內容不是ACE的網路框架,而是ACE對Socket API的物件導向的封裝,在ACE中稱為Socket Wrapper Facade。它是ACE網路框架的基礎。
ACE將Socket應用介面封裝到3個不同的類中,它們是ACE_SOCK_Stream、ACE_SOCK_Acceptor和ACE_SOCK_Connector,結構如圖2-1所示。
圖2-1 Socket封裝器結構圖
其中:
- ACE_SOCK_Stream 表示一條由通訊雙方建立的通訊流,它之進行I/O操作,需要一個Socket描述符,儲存在ACE_IPC_SAP類的資料成員handle_中。
- ACE_SOCK_Connector只進行主動連線操作,連線的Socket描述符儲存在ACE_SOCK_Stream中。每一個連線都有一個ACE_SOCK_Stream物件,用於後續的I/O操作。
- ACE_SOCK_Acceptor只用於被動連線。被動連線需要一個偵聽的Scoket描述符,同樣也儲存在ACE_IPC_SAP類的handle_中。
2.1 Socket IPC分析
程式間通訊(IPC)是作業系統提供的一個非常重要的功能。程式間通訊的方式非常多,如用於遠端通訊的Socket介面、TLI介面,用於本機通訊的SVR4 STREAM管道、UNIX FIFO、Windows命名管道等。為了將這些不同的程式間通訊方式封裝成物件導向的介面,ACE設計了一個IPC Wrapper Facader元件,用於簡化通訊軟體的開發。而ACE_IPC_SAP類是ACE IPC Wrapper Facader繼承結構的根基類。其實ACE_IPC_SAP類除了圖2-1中的ACE_SOCK類、ACE_FIFO類外,還有ACE_SPIPE、ACE_TLI等其他類,它們都用於不同形式的程式間通訊。
ACE_IPC_SAP類主要有兩個功能:一個是包含一個資料成員handle_,用於儲存檔案描述符;另一個是提供成員函式用於基本的I/O屬性操作。ACE_IPC_SAP類程式碼清單如下:
// 程式碼在ace/IPC_SAP.h檔案中
#ifndef ACE_IPC_SAP_H
#define ACE_IPC_SAP_H
#include /**/ "ace/pre.h"
#include "ace/Flag_Manip.h"
#include "ace/os_include/sys/os_types.h"
#if !defined (ACE_LACKS_PRAGMA_ONCE)
# pragma once
#endif /* ACE_LACKS_PRAGMA_ONCE */
ACE_BEGIN_VERSIONED_NAMESPACE_DECL
class ACE_Export ACE_IPC_SAP
{
public:
/// Interface for <ioctl>.
int control (int cmd, void *) const;
// = Common I/O handle options related to sockets.
/**
* Enable asynchronous I/O (ACE_SIGIO), urgent data (ACE_SIGURG),
* non-blocking I/O (ACE_NONBLOCK), or close-on-exec (ACE_CLOEXEC),
* which is passed as the @a value.
*/
int enable (int value) const;
/**
* Disable asynchronous I/O (ACE_SIGIO), urgent data (ACE_SIGURG),
* non-blocking I/O (ACE_NONBLOCK), or close-on-exec (ACE_CLOEXEC),
* which is passed as the @a value.
*/
int disable (int value) const;
/// Get the underlying handle.
ACE_HANDLE get_handle (void) const;
/// Set the underlying handle.
void set_handle (ACE_HANDLE handle);
/// Dump the state of an object.
void dump (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
protected:
// = Ensure that ACE_IPC_SAP is an abstract base class.
/// Default constructor.
ACE_IPC_SAP (void);
/// Protected destructor.
/**
* Not a virtual destructor. Protected destructor to prevent
* operator delete() from being called through a base class
* ACE_IPC_SAP pointer/reference.
*/
~ACE_IPC_SAP (void);
private:
/// Underlying I/O handle. handle_是I/O操作的檔案描述符
ACE_HANDLE handle_;
/// Cache the process ID. pid_用於儲存程式號
static pid_t pid_;
};
ACE_END_VERSIONED_NAMESPACE_DECL
#if defined (__ACE_INLINE__)
#include "ace/IPC_SAP.inl"
#endif /* __ACE_INLINE__ */
#include /**/ "ace/post.h"
#endif /* ACE_IPC_SAP_H */
(1)ACE_IPC_SAP類的建構函式和解構函式都是protected型別的。這樣可以防止ACE_IPC_SAP被直接例項化,使它可以成為一個抽象類。
(2)ACE_IPC_SAP類用兩個重要的函式:enable函式和disable函式,用於設定和取消描述符的屬性。由於它們完全屬於檔案屬性操作。
(3)ACE_SOCK是ACE_IPC_SAP類的子類,用於實現基於Socket介面的通訊方式。它的主要功能是建立Socket和設定Socket屬性。ACE_SOCK類的open函式用於建立Socket,並且將描述符儲存在父類的handle_資料成員中。open函式有兩種重置的形式,以適應不同的平臺,程式碼清單如下:
// 程式碼在ace/SOCK.h中
/// Wrapper around the BSD-style @c socket system call (no QoS).
int open (int type,
int protocol_family,
int protocol,
int reuse_addr);
/// Wrapper around the QoS-enabled @c WSASocket function.
int open (int type,
int protocol_family,
int protocol,
ACE_Protocol_Info *protocolinfo,
ACE_SOCK_GROUP g,
u_long flags,
int reuse_addr);
2.2 ACE_SOCK_Acceptor類的分析
ACE_SOCK_Acceptor類用於被動連線端,它的作用是建立偵聽Socket,等待客戶端的連線。應用程式通過open函式建立偵聽的Socket,通過accept函式等待客戶端的連線。ACE_SOCK_Acceptor類的程式碼清單如下:
// 程式碼在ace/SOCK_Acceptor.h中
#ifndef ACE_SOCK_ACCEPTOR_H
#define ACE_SOCK_ACCEPTOR_H
#include /**/ "ace/pre.h"
#include "ace/SOCK_Stream.h"
#if !defined (ACE_LACKS_PRAGMA_ONCE)
# pragma once
#endif /* ACE_LACKS_PRAGMA_ONCE */
ACE_BEGIN_VERSIONED_NAMESPACE_DECL
class ACE_Time_Value;
class ACE_Accept_QoS_Params;
class ACE_Export ACE_SOCK_Acceptor : public ACE_SOCK
{
public:
// = Initialization and termination methods.
/// Default constructor.
ACE_SOCK_Acceptor (void);
/**
* Initialize a passive-mode BSD-style acceptor socket (no QoS).
* @a local_sap is the address that we're going to listen for
* connections on. If @a reuse_addr is 1 then we'll use the
* @c SO_REUSEADDR to reuse this address.
*/
ACE_SOCK_Acceptor (const ACE_Addr &local_sap,
int reuse_addr = 0,
int protocol_family = PF_UNSPEC,
int backlog = ACE_DEFAULT_BACKLOG,
int protocol = 0);
/// Initialize a passive-mode QoS-enabled acceptor socket. Returns 0
/// on success and -1 on failure.
ACE_SOCK_Acceptor (const ACE_Addr &local_sap,
ACE_Protocol_Info *protocolinfo,
ACE_SOCK_GROUP g,
u_long flags,
int reuse_addr,
int protocol_family = PF_UNSPEC,
int backlog = ACE_DEFAULT_BACKLOG,
int protocol = 0);
/**
* Initialize a passive-mode BSD-style acceptor socket (no QoS).
* @a local_sap is the address that we're going to listen for
* connections on. If @a reuse_addr is 1 then we'll use the
* @c SO_REUSEADDR to reuse this address. Returns 0 on success and
* -1 on failure.
*/
int open (const ACE_Addr &local_sap,
int reuse_addr = 0,
int protocol_family = PF_UNSPEC,
int backlog = ACE_DEFAULT_BACKLOG,
int protocol = 0);
/// Initialize a passive-mode QoS-enabled acceptor socket. Returns 0
/// on success and -1 on failure.
int open (const ACE_Addr &local_sap,
ACE_Protocol_Info *protocolinfo,
ACE_SOCK_GROUP g,
u_long flags,
int reuse_addr,
int protocol_family = PF_UNSPEC,
int backlog = ACE_DEFAULT_BACKLOG,
int protocol = 0);
/// Close the socket. Returns 0 on success and -1 on failure.
int close (void);
/// Default dtor.
~ACE_SOCK_Acceptor (void);
// = Passive connection <accept> methods.
/**
* Accept a new ACE_SOCK_Stream connection. A @a timeout of 0
* means block forever, a @a timeout of {0, 0} means poll. @a restart
* == true means "restart if interrupted," i.e., if errno == EINTR.
* Note that @a new_stream inherits the "blocking mode" of @c this
* ACE_SOCK_Acceptor, i.e., if @c this acceptor factory is in
* non-blocking mode, the @a new_stream will be in non-blocking mode
* and vice versa.
*/
int accept (ACE_SOCK_Stream &new_stream,
ACE_Addr *remote_addr = 0,
ACE_Time_Value *timeout = 0,
bool restart = true,
bool reset_new_handle = false) const;
#if !defined (ACE_HAS_WINCE)
/**
* Accept a new ACE_SOCK_Stream connection using the QoS
* information in @a qos_params. A @a timeout of 0 means block
* forever, a @a timeout of {0, 0} means poll. @a restart == true means
* "restart if interrupted," i.e., if errno == EINTR. Note that
* @a new_stream inherits the "blocking mode" of @c this
* ACE_SOCK_Acceptor, i.e., if @c this acceptor factory is in
* non-blocking mode, the @a new_stream will be in non-blocking mode
* and vice versa.
*/
int accept (ACE_SOCK_Stream &new_stream,
ACE_Accept_QoS_Params qos_params,
ACE_Addr *remote_addr = 0,
ACE_Time_Value *timeout = 0,
bool restart = true,
bool reset_new_handle = false) const;
#endif // ACE_HAS_WINCE
// = Meta-type info
typedef ACE_INET_Addr PEER_ADDR;
typedef ACE_SOCK_Stream PEER_STREAM;
/// Dump the state of an object.
void dump (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
protected:
/// Perform operations that must occur before <ACE_OS::accept> is
/// called.
int shared_accept_start (ACE_Time_Value *timeout,
bool restart,
int &in_blocking_mode) const;
/// Perform operations that must occur after <ACE_OS::accept> is
/// called.
int shared_accept_finish (ACE_SOCK_Stream new_stream,
int in_blocking_mode,
bool reset_new_handle) const;
/**
* This method factors out the common <open> code and is called by
* both the QoS-enabled <open> method and the BSD-style <open>
* method.
*/
int shared_open (const ACE_Addr &local_sap,
int protocol_family,
int backlog);
private:
/// Do not allow this function to percolate up to this interface...
int get_remote_addr (ACE_Addr &) const;
};
ACE_END_VERSIONED_NAMESPACE_DECL
#if defined (__ACE_INLINE__)
#include "ace/SOCK_Acceptor.inl"
#endif /* __ACE_INLINE__ */
#include /**/ "ace/post.h"
#endif /* ACE_SOCK_ACCEPTOR_H */
(1)open函式
open函式用於建立一個偵聽的Socket。它將Socket程式設計中的多個介面繫結在一個函式中,為應用程式隱藏了Socket程式設計的細節。
(2)accept函式
accept函式被應用程式呼叫,用來等待和接收客戶端的連線。
(3)close函式
如果應用程式明確要關閉偵聽的Socket,可以呼叫close()函式。ACE_SOCK_Stream、ACE_SOCK_Acceptor等類中,它們的解構函式都是空函式,而檔案的關閉則必須放在close函式中。這是ACE的一種設計方式,只有應用程式明確呼叫close函式才關閉檔案,這樣可以避免在解構函式中出現自動關閉的情況。
2.3 ACE_SOCK_Connector類的分析
ACE_SOCK_Connector類用於客戶端發起一個新連線,它的結構和ACE_SOCK_Acceptor非常相似。ACE_SOCK_Connector沒有關閉函式,它的解構函式也為空,因為新連線的Socket控制程式碼儲存在ACE_SOCK_Stream物件中,由它負責關閉。
ACE_SOCK_Connector類只負責connect操作。
// 程式碼在ace/SOCK_Connector.h
#ifndef ACE_SOCK_CONNECTOR_H
#define ACE_SOCK_CONNECTOR_H
#include /**/ "ace/pre.h"
#include "ace/SOCK_Stream.h"
#if !defined (ACE_LACKS_PRAGMA_ONCE)
# pragma once
#endif /* ACE_LACKS_PRAGMA_ONCE */
ACE_BEGIN_VERSIONED_NAMESPACE_DECL
class ACE_QoS_Params;
class ACE_Time_Value;
/**
* @class ACE_SOCK_Connector
*
* @brief Defines a factory that actively connects to a remote IP
* address and TCP port, creating a new @c ACE_SOCK_Stream object.
*
* The @c ACE_SOCK_Connector doesn't have a socket of its own,
* i.e., it simply "borrows" the one from the @c ACE_SOCK_Stream
* that's being connected. The reason for this is that the
* underlying socket API doesn't use a factory socket to connect
* data mode sockets. Therefore, there's no need to inherit
* @c ACE_SOCK_Connector from @c ACE_SOCK. A nice side-effect of
* this is that @c ACE_SOCK_Connector objects do not store state so
* they can be used reentrantly in multithreaded programs.
*/
class ACE_Export ACE_SOCK_Connector
{
public:
/// Default constructor.
ACE_SOCK_Connector (void);
/**
* Actively connect to a peer, producing a connected @c ACE_SOCK_Stream
* object if the connection succeeds.
*
* @param new_stream The @c ACE_SOCK_Stream object that will be connected
* to the peer.
* @param remote_sap The address that we are trying to connect to.
* The protocol family of @c remote_sap is used for
* the connected socket. That is, if @c remote_sap
* contains an IPv6 address, a socket with family
* PF_INET6 will be used, else it will be PF_INET.
* @param timeout Pointer to an @c ACE_Time_Value object with amount
* of time to wait to connect. If the pointer is 0
* then the call blocks until the connection attempt
* is complete, whether it succeeds or fails. If
* *timeout == {0, 0} then the connection is done
* using nonblocking mode. In this case, if the
* connection can't be made immediately, this method
* returns -1 and errno == EWOULDBLOCK.
* If *timeout > {0, 0} then this is the maximum amount
* of time to wait before timing out; if the specified
* amount of time passes before the connection is made,
* this method returns -1 and errno == ETIME. Note
* the difference between this case and when a blocking
* connect is attempted that TCP times out - in the latter
* case, errno will be ETIMEDOUT.
* @param local_sap (optional) The local address to bind to. If it's
* the default value of @c ACE_Addr::sap_any then the
* OS will choose an unused port.
* @param reuse_addr (optional) If the value is 1, the local address
* (@c local_sap) is reused, even if it hasn't been
* cleaned up yet.
* @param flags Ignored.
* @param perms Ignored.
* @param protocol (optional) If value is 0, default SOCK_STREAM
* protocol is selected by kernel (typically TCP).
*/
ACE_SOCK_Connector (ACE_SOCK_Stream &new_stream,
const ACE_Addr &remote_sap,
const ACE_Time_Value *timeout = 0,
const ACE_Addr &local_sap = ACE_Addr::sap_any,
int reuse_addr = 0,
int flags = 0,
int perms = 0,
int protocol = 0);
#if !defined (ACE_HAS_WINCE)
/**
* Actively connect to a peer, producing a connected @c ACE_SOCK_Stream
* object if the connection succeeds.
*
* @param new_stream The @c ACE_SOCK_Stream object that will be connected
* to the peer.
* @param remote_sap The address that we are trying to connect to.
* The protocol family of @c remote_sap is used for
* the connected socket. That is, if @c remote_sap
* contains an IPv6 address, a socket with family
* PF_INET6 will be used, else it will be PF_INET.
* @param qos_params Contains QoS parameters that are passed to the
* IntServ (RSVP) and DiffServ protocols.
* @see ACE_QoS_Params.
* @param timeout Pointer to an @c ACE_Time_Value object with amount
* of time to wait to connect. If the pointer is 0
* then the call blocks until the connection attempt
* is complete, whether it succeeds or fails. If
* *timeout == {0, 0} then the connection is done
* using nonblocking mode. In this case, if the
* connection can't be made immediately, this method
* returns -1 and errno == EWOULDBLOCK.
* If *timeout > {0, 0} then this is the maximum amount
* of time to wait before timing out; if the specified
* amount of time passes before the connection is made,
* this method returns -1 and errno == ETIME. Note
* the difference between this case and when a blocking
* connect is attempted that TCP times out - in the latter
* case, errno will be ETIMEDOUT.
* @param local_sap (optional) The local address to bind to. If it's
* the default value of @c ACE_Addr::sap_any then the
* OS will choose an unused port.
* @param reuse_addr (optional) If the value is 1, the local address
* (@c local_sap) is reused, even if it hasn't been
* cleaned up yet.
* @param flags Ignored.
* @param perms Ignored.
*/
ACE_SOCK_Connector (ACE_SOCK_Stream &new_stream,
const ACE_Addr &remote_sap,
ACE_QoS_Params qos_params,
const ACE_Time_Value *timeout = 0,
const ACE_Addr &local_sap = ACE_Addr::sap_any,
ACE_Protocol_Info *protocolinfo = 0,
ACE_SOCK_GROUP g = 0,
u_long flags = 0,
int reuse_addr = 0,
int perms = 0);
#endif // ACE_HAS_WINCE
/**
* Actively connect to a peer, producing a connected @c ACE_SOCK_Stream
* object if the connection succeeds.
*
* @param new_stream The @c ACE_SOCK_Stream object that will be connected
* to the peer.
* @param remote_sap The address that we are trying to connect to.
* The protocol family of @c remote_sap is used for
* the connected socket. That is, if @c remote_sap
* contains an IPv6 address, a socket with family
* PF_INET6 will be used, else it will be PF_INET.
* @param timeout Pointer to an @c ACE_Time_Value object with amount
* of time to wait to connect. If the pointer is 0
* then the call blocks until the connection attempt
* is complete, whether it succeeds or fails. If
* *timeout == {0, 0} then the connection is done
* using nonblocking mode. In this case, if the
* connection can't be made immediately, this method
* returns -1 and errno == EWOULDBLOCK.
* If *timeout > {0, 0} then this is the maximum amount
* of time to wait before timing out; if the specified
* amount of time passes before the connection is made,
* this method returns -1 and errno == ETIME. Note
* the difference between this case and when a blocking
* connect is attempted that TCP times out - in the latter
* case, errno will be ETIMEDOUT.
* @param local_sap (optional) The local address to bind to. If it's
* the default value of @c ACE_Addr::sap_any then the
* OS will choose an unused port.
* @param reuse_addr (optional) If the value is 1, the local address
* (@c local_sap) is reused, even if it hasn't been
* cleaned up yet.
* @param flags Ignored.
* @param perms Ignored.
* @param protocol (optional) If value is 0, default SOCK_STREAM
* protocol is selected by kernel (typically TCP).
*
* @return Returns 0 if the connection succeeds. If it fails,
* -1 is returned and errno contains a specific error
* code.
*/
int connect (ACE_SOCK_Stream &new_stream,
const ACE_Addr &remote_sap,
const ACE_Time_Value *timeout = 0,
const ACE_Addr &local_sap = ACE_Addr::sap_any,
int reuse_addr = 0,
int flags = 0,
int perms = 0,
int protocol = 0);
#if !defined (ACE_HAS_WINCE)
/**
* Actively connect to a peer, producing a connected @c ACE_SOCK_Stream
* object if the connection succeeds.
*
* @param new_stream The @c ACE_SOCK_Stream object that will be connected
* to the peer.
* @param remote_sap The address that we are trying to connect to.
* The protocol family of @c remote_sap is used for
* the connected socket. That is, if @c remote_sap
* contains an IPv6 address, a socket with family
* PF_INET6 will be used, else it will be PF_INET.
* @param qos_params Contains QoS parameters that are passed to the
* IntServ (RSVP) and DiffServ protocols.
* @see ACE_QoS_Params.
* @param timeout Pointer to an @c ACE_Time_Value object with amount
* of time to wait to connect. If the pointer is 0
* then the call blocks until the connection attempt
* is complete, whether it succeeds or fails. If
* *timeout == {0, 0} then the connection is done
* using nonblocking mode. In this case, if the
* connection can't be made immediately, this method
* returns -1 and errno == EWOULDBLOCK.
* If *timeout > {0, 0} then this is the maximum amount
* of time to wait before timing out; if the specified
* amount of time passes before the connection is made,
* this method returns -1 and errno == ETIME. Note
* the difference between this case and when a blocking
* connect is attempted that TCP times out - in the latter
* case, errno will be ETIMEDOUT.
* @param local_sap (optional) The local address to bind to. If it's
* the default value of @c ACE_Addr::sap_any then the
* OS will choose an unused port.
* @param reuse_addr (optional) If the value is 1, the local address
* (@c local_sap) is reused, even if it hasn't been
* cleaned up yet.
* @param flags Ignored.
* @param perms Ignored.
*
* @return Returns 0 if the connection succeeds. If it fails,
* -1 is returned and errno contains a specific error
* code.
*/
int connect (ACE_SOCK_Stream &new_stream,
const ACE_Addr &remote_sap,
ACE_QoS_Params qos_params,
const ACE_Time_Value *timeout = 0,
const ACE_Addr &local_sap = ACE_Addr::sap_any,
ACE_Protocol_Info *protocolinfo = 0,
ACE_SOCK_GROUP g = 0,
u_long flags = 0,
int reuse_addr = 0,
int perms = 0);
#endif // ACE_HAS_WINCE
/// Default destructor.
~ACE_SOCK_Connector (void);
// = Completion routine.
/**
* Try to complete a nonblocking connection that was begun by a
* previous call to connect with a {0, 0} ACE_Time_Value timeout.
* @see connect().
*
* @param new_stream The @c ACE_SOCK_Stream object that will be connected
* to the peer.
* @param remote_sap If non-0, it points to the @c ACE_INET_Addr object
* that will contain the address of the connected peer.
* @param timeout Same values and return value possibilites as for
* connect(). @see connect().
*/
int complete (ACE_SOCK_Stream &new_stream,
ACE_Addr *remote_sap = 0,
const ACE_Time_Value *timeout = 0);
/// Resets any event associations on this handle
bool reset_new_handle (ACE_HANDLE handle);
// = Meta-type info
typedef ACE_INET_Addr PEER_ADDR;
typedef ACE_SOCK_Stream PEER_STREAM;
/// Dump the state of an object.
void dump (void) const;
/// Declare the dynamic allocation hooks.
ACE_ALLOC_HOOK_DECLARE;
protected:
/// Perform operations that ensure the socket is opened using
/// BSD-style semantics (no QoS).
int shared_open (ACE_SOCK_Stream &new_stream,
int protocol_family,
int protocol,
int reuse_addr);
/// Perform operations that ensure the socket is opened using
/// QoS-enabled semantics.
int shared_open (ACE_SOCK_Stream &new_stream,
int protocol_family,
int protocol,
ACE_Protocol_Info *protocolinfo,
ACE_SOCK_GROUP g,
u_long flags,
int reuse_addr);
/// Perform operations that must be called before <ACE_OS::connect>.
int shared_connect_start (ACE_SOCK_Stream &new_stream,
const ACE_Time_Value *timeout,
const ACE_Addr &local_sap);
/// Perform operations that must be called after <ACE_OS::connect>.
int shared_connect_finish (ACE_SOCK_Stream &new_stream,
const ACE_Time_Value *timeout,
int result);
};
ACE_END_VERSIONED_NAMESPACE_DECL
#if defined (__ACE_INLINE__)
#include "ace/SOCK_Connector.inl"
#endif /* __ACE_INLINE__ */
#include /**/ "ace/post.h"
#endif /* ACE_SOCK_CONNECTOR_H */
【原始碼並沒有仔細看看,就是粗粗的瀏覽了一下,網路程式設計的基礎不牢靠,還是有些看不懂。先記錄下來,做一個學習的標記。】
參考文獻:
[1] ACE技術內幕:深入解析ACE架構設計與實現原理
相關文章
- ACE之(一)ACE概述
- ACE(01):Oracle ACE 申請Oracle
- ACE(02):Oracle ACE常見問題Oracle
- lit ace markdown編輯器
- Linux下簡單的ACE socket客戶端和伺服器端Linux客戶端伺服器
- Boost.Asio和ACE之間關於Socket程式設計的比較程式設計
- Oracle ACE AssociateOracle
- web指令碼編輯器ACE EditorWeb指令碼
- omi ace markdown editor
- stencil ace markdown editor
- react-ace使用示例React
- Ace editor中文文件
- astro react ace markdown editor and previewerASTReactView
- Laravel-admin 中引入 Ace 程式碼編輯器Laravel
- 一加ace3 禁用更新
- 不止一面的百變 ACE
- JS物件之封裝(二)JS物件封裝
- 一個關於ace-editor編輯器的問題
- 一文總結ACE程式碼框架框架
- Ace Editor 擴充套件編寫初步(轉)套件
- Ace editor 線上網頁版的vscode!程式碼編輯器網頁VSCode
- 微星MPG Z590 ACE主機板怎麼樣?微星MPG Z590 ACE主機板評測
- thinkphp + ace 後臺模板淘寶客網站PHP網站
- 阿里雲ACE認證學習知識點梳理阿里
- 雲端計算進階之路——與ACE相約
- Flutter 125: 圖解自傳 ACE_ICON.ttf 圖示庫Flutter圖解
- 使用動畫曲線編輯器打造炫酷的3D視覺化ACE動畫3D視覺化
- (3)Tcp Socket程式設計的封裝類 TcpListener/TcpClientTCP程式設計封裝client
- 二次封裝WebDriverWait封裝WebAI
- axios二次封裝iOS封裝
- 對話ACE第五期:到底什麼才是真正的HTAP?
- 王牌登場!一加Ace系列正式官宣,4月21日見
- 【Flutter 專題】125 圖解自傳 ACE_ICON.ttf 圖示庫Flutter圖解
- Dapper的封裝、二次封裝、官方擴充套件包封裝,以及ADO.NET原生封裝APP封裝套件
- iOS之WKWebView封裝iOSWebView封裝
- Oracle ACE尹海文:查詢是資料庫工具的核心功能Oracle資料庫
- 一加 Ace 3 售價2599元起,打造效能手機新標杆
- 火線再燃全民剛槍 ACE S2賽季熱血來襲