skynetネットワーク最下位層
8771 ワード
最近雲風のskynetフレームを見て、多くの利益を得たような気がします.再び雲風大神の無私な奉仕に感謝します.以下は私がskynetのフレームワークを見る時、下層のネット層を見る時のいくつかの心得の体得で、もし書き間違えたところがあれば指摘を歓迎して、本文は私のオリジナルで、もし転載するならば明記してください.
skynetのネットワークの下層にはepollモデル(linuxプラットフォームの下)が採用され、linux、apple、freebsd、openbsd、netbsdなどのプラットフォームをサポートしている.
socket_epoll.h--linuxプラットフォームにおける関連インタフェースの実現
socket_kqueue.h--他のいくつかのプラットフォームのインタフェース実装
socket_poll.h--pollインタフェース宣言
関連インタフェース:
bool sp_invalid(poll_fd fd)--ハンドルfdが有効かどうかを判断し、-1が無効なハンドルである
poll_fd sp_「create()」--epollを作成し、対応するハンドルを返します.
int sp_add(poll_fd fd,int sock,void*ud)--sockハンドルをepollに追加するepollに対応するハンドルをfd、ユーザーデータをud、デフォルトはinイベントに注目し、0を返すと成功、1は失敗
void sp_del(poll_fd fd, int sock); -epoll fdからsockを除去する.
void sp_write(poll_fd, int sock, void *ud, bool enable);epoll fdに既存のsockをoutイベントに注目するかどうかを設定します
int sp_wait(poll_fd, struct event *e, int max);--待機epoll、eは配列であり、最大長はmaxであり、waitからの関連イベントを返します.返されるのは、実際のイベントの数です.
void sp_nonblocking(int sock);--sockを非ブロックに設定
skynetネットワークの下位層の具体的な論理はsocket_server.h,socket_server.cこの2つのファイルに作成されます.ネットワークの最下位レベルの簡単な流れを簡単に説明します.制御フロー;ネットワーク層に関する操作はすべてコマンドによって通知され、ネットワークモジュールの作成時にパイプが作成され、外部はパイプを通じて要求するコマンドが書き込まれ、ネットワークモジュールのスレッドは輪訓のたびにパイプにコマンドがあるかどうかを判断し、要求コマンドを読み取ることで、対応するネットワーク操作を完了する. 読み出しフロー;tcpのみに対して、socketに読み取り可能なイベントがあるたびに、一定の長さのデータが読み出され、データ読み取り可能なネットワークイベントを介してlua層ロジックに渡され、lua層ロジックはこのイベントを受信するとbuffer_poolメモリプールでは、空きbufが取得され、最も近い長さのbufがこのデータを格納し、対応するsocketのsocket_に接続します.bufferリストの後ろにあります.実際にはluaで定義されている読み取り関数はすべてsocket_を読み込むことによってbufferリストのデータが取得されました. 書き込みプロセス;tcpのみに対してlua論理層はプロセスを制御する要求コマンドrequest_を通過するsendはネットワークの下位層にデータを送信し、ネットワークの下位層はidに基づいて送信するデータを対応するsocketの2つのキャッシュチェーンテーブル(高または低)に追加する.優先度の高いデータは、「高」のキャッシュチェーンテーブルに保存されます.対応するsocketに書き込み可能なイベントがある場合、高いキャッシュリストが優先的に送信され、低いキャッシュリストのデータが送信されます.
以下、各プロセスがどのように実現されているかを詳しく分析します.
データ構造の定義
次に、ネットワークリクエストメッセージの定義を参照してください.
次のセクションのほとんどは、読み取りキャッシュを操作する関連インタフェースです. socketdriver.buffer--新しいsocket_bufferは、buf に戻る socketdriver.push(socketbuf,buffer_pool,data,size)--buffer_poolで空きbufを探し、dataとsizeを格納し、socketbuf末尾 に添付する. socketdriver.pop(socketbuf,buffer_pool,sz)--socketbufのメモリリストから、長さszのメモリブロックをまとめて返します.socketbufチェーンテーブルのメモリブロックがポップアップされるとbuffer_に回収されます.poolの最初の要素リストの socketdriver.drop(data,size)--dataメモリブロック を解放 socketdriver.readall(socketbuf,buffer_pool)--socketbufのメモリリストをブロック化してpopのように返しますが、ここではすべてのデータです. socketdriver.readline(socketbuf,buffer_pool,sep)--socketbufからsepシンボルで分割されたメモリを読み出し、 . socketdriver.str 2 p(str)--文字列をポインタに変換し、 をポップアップします. socketdriver.unpack--ネットワークの下位層のパケット解除関数で、一般的にdispatch関数には が割り当てられています.
この次のグループは基本的にいくつかのネットワーク操作であり、実際にはパイプを通じてネットワーク要求を下位層に送信し、下位層は対応する要求に基づいて対応する操作を行う. socketdriver.接続(addr,port)--request_を送信Open要求ネットワーク最下位要求接続 socketdriver.close(id)--request_を送信closeはネットワークの最下層に、ネットワークの最下層は返信のidによって対応するsocketを閉じ、データが送信されていない場合はSOCKET_に設定されます.TYPE_HALFCLOSEは半閉鎖状態で、データの送信を待ってから本当に を閉鎖します. socketdriver.shutdown(id)--closeと似ていますが、データの送信を待つ必要はありません.彼は を強制的に閉じます. socketdriver.Listen(address,port)--指定したアドレスとipをバインドするsocketハンドルを新規作成し、request_を送信しながらリスニングを開始します.Listenは、ネットワークの最下位のsocketプールに登録されていますが、epollには登録されていません.socketプールの下にあるid を返します. socketdriver.start(id)--idに基づいて対応するsocketを見つけて対応する状態(SOCKET_TYPE_PACCEPTとSOCKET_TYPE_PLISTENがepoll注目inイベントに参加する)からepollに参加する必要があるかどうかを判断するこの関数は一般的にlistenと連用される.なぜ分離するのかは、主にユーザーがいつ を有効にするかを決定することができる. socketdriver.send(id,pBuf)--id対応socket対応wb_にデータpBufを追加List highキューでは、送信待ち、このキューが空の場合は を直接送信する socketdriver.lsend(id,pBuf)--sendに似ていますが、lowキューに入れると、データ送信はhighキューのデータ を優先的に送信します. socketdriver.bind(fd)--外部のハンドルを最下位層にバインドするepollでは、ストリームに注目するための入出力イベント のようなバインドシステムを使用するいくつかのハンドルのみが呼び出される. socketdriver.Nodelay(id)--idに対応するsocketハンドルオプションTCP_NODELAYは1 に設定
接続確立プロセス
サーバはsocketを呼び出す.Listernインタフェースリスニング対応ポートリスニングsocket対応idを返す
SOcket.を呼び出すことでstart(id,cb)はlistern socketの読み取りイベントを開き、クライアント接続の受け入れを開始する.また、新しい接続時の処理関数cbを登録し、パラメータは接続されたアドレスとハンドルである.start関数がconnectを呼び出すのを見ることができます.次にconnectの実装を見ることができます.
コールバック関数funcに基づいてsocket_を作成する必要があるかどうかを判断することがわかります.bufferとは、クライアントからのデータを読み出しキャッシュとして読み出す必要があるため、socketを傍受するにはbufferは必要ありません.それは接続のみを受け入れるためです.対応する接続socketは、クライアントからのデータを読み出す必要があるため、bufferが必要です.
また、suspend(s)が現在の協程を保留するのを見ることができますが、request_を待つ必要があります.startネットワークメッセージの返信が成功するとSKYNET_に戻りますSOCKET_TYPE_CONNECT失敗はSKYNETに戻るSOCKET_TYPE_ERRORメッセージは、この2つのメッセージの処理関数を見ることができます.
実際には、ネットワークの下部に新しい接続があると論理層にメッセージが送信され、socketが表示されます.luaにおける対応する処理
接続が確立すると、接続された接続idに対してsocketを呼び出す必要がある.start(id)はcbを送信しないで、socketを傍受するためにコールバック関数を送信する必要があります.普通のクライアントsocketは送信しないで、内部はcbが空いているかどうかによって、idがsocketを傍受するidか普通のクライアントのsocketかを決定します.普通のsocketのidであれば、このsocketのために新しいsocket_を作成します.bufferは、読み取りキャッシュとして使用されます.はい
skynetのネットワークの下層にはepollモデル(linuxプラットフォームの下)が採用され、linux、apple、freebsd、openbsd、netbsdなどのプラットフォームをサポートしている.
socket_epoll.h--linuxプラットフォームにおける関連インタフェースの実現
socket_kqueue.h--他のいくつかのプラットフォームのインタフェース実装
socket_poll.h--pollインタフェース宣言
関連インタフェース:
bool sp_invalid(poll_fd fd)--ハンドルfdが有効かどうかを判断し、-1が無効なハンドルである
poll_fd sp_「create()」--epollを作成し、対応するハンドルを返します.
int sp_add(poll_fd fd,int sock,void*ud)--sockハンドルをepollに追加するepollに対応するハンドルをfd、ユーザーデータをud、デフォルトはinイベントに注目し、0を返すと成功、1は失敗
void sp_del(poll_fd fd, int sock); -epoll fdからsockを除去する.
void sp_write(poll_fd, int sock, void *ud, bool enable);epoll fdに既存のsockをoutイベントに注目するかどうかを設定します
int sp_wait(poll_fd, struct event *e, int max);--待機epoll、eは配列であり、最大長はmaxであり、waitからの関連イベントを返します.返されるのは、実際のイベントの数です.
void sp_nonblocking(int sock);--sockを非ブロックに設定
skynetネットワークの下位層の具体的な論理はsocket_server.h,socket_server.cこの2つのファイルに作成されます.ネットワークの最下位レベルの簡単な流れを簡単に説明します.
以下、各プロセスがどのように実現されているかを詳しく分析します.
データ構造の定義
struct write_buffer { //
struct write_buffer * next; // ,
void *buffer;//
char *ptr;// ,
int sz;//
bool userobject;//
uint8_t udp_address[UDP_ADDRESS_SIZE]; //udp ,
};
//tcp udp_address ,
#define SIZEOF_TCPBUFFER (offsetof(struct write_buffer, udp_address[0]))
#define SIZEOF_UDPBUFFER (sizeof(struct write_buffer))
//
struct wb_list {
struct write_buffer * head;
struct write_buffer * tail;
};
struct socket {
uintptr_t opaque;// socket , socket
struct wb_list high;// , ,high
struct wb_list low;
int64_t wb_size;//
int fd; //socket
int id; // socket_server socket id, 。
uint16_t protocol;// udp tcp
uint16_t type;//socket
union {
int size;// fd , , MIN_READ_BUFFER = 64,
uint8_t udp_address[UDP_ADDRESS_SIZE];//udp
} p;
};
struct socket_server {
int recvctrl_fd;// piple ,
int sendctrl_fd;
int checkctrl;// recvctrl_fd
poll_fd event_fd;// epoll
int alloc_id;//socket id id reserve_id
int event_n;// epoll wait
int event_index;// epoll
struct socket_object_interface soi;
struct event ev[MAX_EVENT];// epoll wait
struct socket slot[MAX_SOCKET];//socket
char buffer[MAX_INFO];// ,
uint8_t udpbuffer[MAX_UDP_PACKAGE];
fd_set rfds;// recvctrl_fd , select
};
次に、ネットワークリクエストメッセージの定義を参照してください.
/* , , ,header 7,8 */
struct request_package {
uint8_t header[8]; // 6 bytes dummy
union {
char buffer[256];
struct request_open open;
struct request_send send;
struct request_send_udp send_udp;
struct request_close close;
struct request_listen listen;
struct request_bind bind;
struct request_start start;
struct request_setopt setopt;
struct request_udp udp;
struct request_setudp set_udp;
} u;
uint8_t dummy[256];
};
lua-socket.を見ることができますcファイルには、lua論理層に投げ出されて使用されるサービス側ネットワーク層に関連するいくつかのインタフェースが定義されています.対応するluaネットワークに関する論理パッケージはsocket.luaファイルで定義します.次に、投げ出された関連インタフェースを分析するとluaopen_を見ることができます.socketdriver関数は、インタフェースが投げ出されたことを知ることができます.次のセクションのほとんどは、読み取りキャッシュを操作する関連インタフェースです.
この次のグループは基本的にいくつかのネットワーク操作であり、実際にはパイプを通じてネットワーク要求を下位層に送信し、下位層は対応する要求に基づいて対応する操作を行う.
接続確立プロセス
サーバはsocketを呼び出す.Listernインタフェースリスニング対応ポートリスニングsocket対応idを返す
SOcket.を呼び出すことでstart(id,cb)はlistern socketの読み取りイベントを開き、クライアント接続の受け入れを開始する.また、新しい接続時の処理関数cbを登録し、パラメータは接続されたアドレスとハンドルである.start関数がconnectを呼び出すのを見ることができます.次にconnectの実装を見ることができます.
local function connect(id, func)
local newbuffer
if func == nil then
newbuffer = driver.buffer()
end
local s = {
id = id,
buffer = newbuffer,
connected = false,
connecting = true,
read_required = false,
co = false,
callback = func,
protocol = "TCP",
}
assert(not socket_pool[id], "socket is not closed")
socket_pool[id] = s
suspend(s)
local err = s.connecting
s.connecting = nil
if s.connected then
return id
else
socket_pool[id] = nil
return nil, err
end
end
コールバック関数funcに基づいてsocket_を作成する必要があるかどうかを判断することがわかります.bufferとは、クライアントからのデータを読み出しキャッシュとして読み出す必要があるため、socketを傍受するにはbufferは必要ありません.それは接続のみを受け入れるためです.対応する接続socketは、クライアントからのデータを読み出す必要があるため、bufferが必要です.
また、suspend(s)が現在の協程を保留するのを見ることができますが、request_を待つ必要があります.startネットワークメッセージの返信が成功するとSKYNET_に戻りますSOCKET_TYPE_CONNECT失敗はSKYNETに戻るSOCKET_TYPE_ERRORメッセージは、この2つのメッセージの処理関数を見ることができます.
-- SKYNET_SOCKET_TYPE_CONNECT = 2
socket_message[2] = function(id, _ , addr)
local s = socket_pool[id]
if s == nil then
return
end
-- log remote addr
s.connected = true
wakeup(s)
end
はconnectedをtrueに設定し、socket sに対応するコモンを再起動します.-- SKYNET_SOCKET_TYPE_ERROR = 5
socket_message[5] = function(id, _, err)
local s = socket_pool[id]
if s == nil then
skynet.error("socket: error on unknown", id, err)
return
end
if s.connected then
skynet.error("socket: error on", id, err)
elseif s.connecting then
s.connecting = err
end
s.connected = false
driver.shutdown(id)
wakeup(s)
end
が失敗するとconnectingはエラーメッセージとなり、対応するsocketがshutdownされます.実際には、ネットワークの下部に新しい接続があると論理層にメッセージが送信され、socketが表示されます.luaにおける対応する処理
-- SKYNET_SOCKET_TYPE_ACCEPT = 4
socket_message[4] = function(id, newid, addr)
local s = socket_pool[id]
if s == nil then
driver.close(newid)
return
end
s.callback(newid, addr)
end
は、登録したばかりのcb関数を呼び出すことを知っています.接続が確立すると、接続された接続idに対してsocketを呼び出す必要がある.start(id)はcbを送信しないで、socketを傍受するためにコールバック関数を送信する必要があります.普通のクライアントsocketは送信しないで、内部はcbが空いているかどうかによって、idがsocketを傍受するidか普通のクライアントのsocketかを決定します.普通のsocketのidであれば、このsocketのために新しいsocket_を作成します.bufferは、読み取りキャッシュとして使用されます.はい