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に書き込み可能なイベントがある場合、高いキャッシュリストが優先的に送信され、低いキャッシュリストのデータが送信されます.

  • 以下、各プロセスがどのように実現されているかを詳しく分析します.
    データ構造の定義
    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関数は、インタフェースが投げ出されたことを知ることができます.
    次のセクションのほとんどは、読み取りキャッシュを操作する関連インタフェースです.
  • 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の実装を見ることができます.
    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は、読み取りキャッシュとして使用されます.はい