はじめに
この記事では、UDPトラッカープロトコルの作業に焦点を当てます。 この記事の例はすべて、Netty NIOフレームワークを使用したJavaで行われます。 MongoDBはデータベースとして使用されます。
通常、トレントトラッカーはHTTPプロトコルを介して動作し、GET要求を介してデータを送信します。 UDPプロトコルを使用するトラッカーの作業により、トラフィックを大幅に削減(2倍以上)できるだけでなく、TCPプロトコルが課す同時接続数の制限を取り除くことができます。
クライアントのUDPトラッカーへのリンクは、 udp://tracker.openbittorrent.com:80 / Announceのようになります。ここで、announceの代わりに何でも使用できます(または何もない)。 ただし、HTTPトラッカーとは異なり、ポートを指定する必要があります。
プロトコルの一般原則
次に、一般的な用語でUDPトラッカーがどのように機能するかについて説明します。
1.最初に、クライアントはトラッカーに接続要求を送信します(パケット0x00接続)。 この要求では、接続IDフィールドは0x41727101980です-これはプロトコル識別子です。 さらに、クライアントはランダムに選択したトランザクションIDを送信します。
2.次に、サーバーはクライアント用の一意の接続IDを作成し、応答パケットで送信します。 この場合、サーバーはクライアントから受信したトランザクションIDを送信する必要があります。
3.クライアントは一意のIDを取得しました(ただし、ユーザー登録とトラフィックアカウンティングのないオープントラッカーの場合、実際には必要ありません)。また、アナウンス付きのパッケージを送信できます。
4.アナウンスに応答して、サーバーはトレントピアのリスト、サーバーへのクライアントコールの間隔、およびシード/ピア統計を提供します。
5.別のクライアントは、統計を取得したいトレントのいくつかのハッシュが送信されるScrapeリクエストを送信できます。 UDPプロトコルの制限により、1要求あたりの要求トレントの数は74を超えることはできません。
サーバー開発
この段階で、トラッカーのソースコードをダウンロードすることをお勧めします。 この記事では、重要なポイントのみを説明します。 ここで使用されているソースとライブラリをダウンロードできます: github.com/lafayette/udp-torrent-tracker
Nettyの初期化。
Executor threadPool = Executors.newCachedThreadPool(); DatagramChannelFactory factory = new NioDatagramChannelFactory(threadPool); // UDP. bootstrap = new ConnectionlessBootstrap(factory); bootstrap.getPipeline().addLast("handler", new Handler()); // Handler . bootstrap.setOption("reuseAddress", true); // . , reuseAddress . , - . // ShutdowHook , Netty. , . Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { channel.close(); bootstrap.releaseExternalResources(); } })); String host = Config.getInstance().getString("listen.host", "127.0.0.1"); Integer port = Config.getInstance().getInt("listen.port", 8080); InetSocketAddress address = new InetSocketAddress(host, port); // , , . Netty . logger.info("Listening on " + host + ":" + port); // bind, . bootstrap.bind(address);
顧客からのメッセージを受信します。
public class Handler extends SimpleChannelUpstreamHandler { private static final Logger logger = Logger.getLogger(Handler.class); public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { ChannelBuffer channelBuffer = (ChannelBuffer)e.getMessage(); // ChannelBuffer udp-. // , , connection ID (long), action ID (int) transaction ID (int) 16 . if (channelBuffer.readableBytes() < 16) { logger.debug("Incorrect packet received from " + e.getRemoteAddress()); } long connectionId = channelBuffer.readLong(); // connectionId, . int actionId = channelBuffer.readInt(); // ID . : 0x00 Connect; 0x01 Announce; 0x02 Scrape; 0x03: Error. () . int transactionId = channelBuffer.readInt(); // ID . ID , . Action action = Action.byId(actionId); ClientRequest request; switch (action) { case CONNECT: request = new ConnectionRequest(); break; case ANNOUNCE: request = new AnnounceRequest(); break; case SCRAPE: request = new ScrapeRequest(); break; default: logger.debug("Incorrect action supplied"); ErrorResponse.send(e, transactionId, "Incorrect action"); return; } // , , . request.setContext(ctx); request.setMessageEvent(e); request.setChannelBuffer(channelBuffer); request.setConnectionId(connectionId); request.setAction(action); request.setTransactionId(transactionId); // . request.read(); } }
モンゴッド
MongoDBを使用するために、素晴らしいマッピングライブラリMorphiaを使用しました。
ここで、ごちそうを保存するためのクラスをどのように説明しましたか:
@Entity("peers") public class Peer { public @Id ObjectId id; public @Indexed byte[] infoHash; public byte[] peerId; public long downloaded; public long left; public long uploaded; public @Transient int event; public int ip; public short port; public @Transient int key; public @Transient int numWant; public @Transient short extensions; public long lastUpdate; @PrePersist private void prePersist() { this.lastUpdate = System.currentTimeMillis(); } }
一時的な注釈は、このフィールドをテーブルに保存しないことを意味します。 これらのフィールドは、リクエストの処理にのみ必要です。 infoHashフィールドには、 インデックス付きの注釈が付いています。 トレントのハッシュによって適切なピアを探します。
データベース接続も作成する必要があります。 これは非常に簡単に行われます:
morphia = new Morphia(); morphia.map(Peer.class); // , . mongo = new Mongo(host, port); datastore = morphia.createDatastore(mongo, "udptracker");
そして、info_hashによるピア検索の例
Query<Peer> peersQuery = Datastore.instance().find(Peer.class); peersQuery.field("infoHash").equal(peer.infoHash); peersQuery.field("peerId").notEqual(peer.peerId); // . peersQuery.limit(peer.numWant).offset(randomOffset); // numWant .
理解を深めるために、 Morphiaのドキュメントを参照することをお勧めします。
それ以外の場合、すべてが非常に簡単です。受信したananBufferからクライアントからデータを読み取り、e.getChannel()に回答を送信します。 ソース内のすべてのパッケージの実装を確認できます。
さらに、プロトコルをよりよく理解するために、 xbtt.sourceforge.net / udp_tracker_protocol.htmlを調べることをお勧めします。
上記サーバーのソース: github.com/lafayette/udp-torrent-tracker
PS NettyとMongoDBの両方での初めての経験であるとすぐに言いたいです。 実際、私はこのプロジェクトでこれらの素晴らしいことの両方を研究しました。 したがって、ジェダイスタイルでより良く/きれいに/作る方法についてのアドバイスは大歓迎です。