The Tracker
1:
|
module Tracker |
State
Tom the Tracker keeps a dictionary of Peers as his active workers. Every worker is identified by a unique id that he assigns himself. A peer is never reused. Even if he is reconnecting to the same remote node, a new peer will be used.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: |
type PeerSlot = { Id: int Peer: Peer mutable State: TrackerPeerState } let maxSlots = settings.MaxPeers let mutable connectionCount = 0 // how many peers are in the connected state let mutable seqId = 0 // current sequence id |
The queues
- incoming requests
- messages to send to every peer
- requests that were received but couldn't be assigned to a peer because they were all busy at the time
1: 2: 3: |
let incomingMessage = new Subject<BitcoinMessage>() let broadcastToPeers = new Subject<BitcoinMessage>() let pendingMessages = new Queue<TrackerCommand>() |
A crude unique sequence generator
1: 2: 3: |
let nextId() = seqId <- seqId + 1 seqId |
The employee directory. The Map is persistent and thread-safe and the reassignment to peerSlots
only happens
in a handler.
1: 2: 3: |
let mutable peerSlots = Map.empty<int, PeerSlot> exception InvalidTransition of TrackerPeerState * TrackerPeerState |
Check that the transition is valid. This should never raise an exception. It is an assertion.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: |
let checkTransition oldState newState = match (oldState, newState) with | (Allocated, Ready) | (_, Free) | (Ready, Busy) | (Busy, Ready) -> ignore() | other -> logger.ErrorF "Invalid transition from %A to %A" oldState newState raise (InvalidTransition other) let changeState id newState = let oldState = peerSlots.[id].State let peer: IPeer = peerSlots.[id].Peer :> IPeer logger.DebugF "State transition of peer(%d %A): %A->%A" id peer.Target oldState newState checkTransition oldState newState peerSlots.[id].State <- newState oldState |
Once a peer becomes available, call this to grab a message that was put on the back burner
1: 2: 3: 4: 5: 6: |
let dequeuePendingMessage() = logger.DebugF "Dequeue Msgs" if pendingMessages.Count <> 0 then trackerIncoming.OnNext(pendingMessages.Dequeue()) else logger.DebugF "Empty Queue" |
Route a message
Every command besides GetHeader
is treated the same way. Tom looks for an available peer and assigns
the work to him. GetHeader
is the exception because it should stick to a given peer because Bob who initiated
the call, wants to catchup from a given node.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
let assign message ps = let peer = ps.Peer :> IPeer logger.DebugF "Assigning %A to %A" message peer.Target changeState ps.Id Busy |> ignore peer.Receive(message) let forward (command: TrackerCommand) (message: PeerCommand) = match message with | PeerCommand.GetHeaders(_, ts, peer) -> peerSlots |> Map.tryFind peer.Id |> Option.bind (fun ps -> Option.conditional (ps.State = Ready) ps) |> Option.map (fun ps -> assign message ps) |> getOrElseF (fun () -> ts.SetResult(Observable.Throw(ArgumentException("Peer busy - cannot handle command")))) | _ -> peerSlots |> Map.tryPick(fun _ ps -> if ps.State = Ready then Some(ps) else None) |> Option.map (fun ps -> assign message ps) |> getOrElseF (fun () -> let freePeers = peerSlots |> Seq.filter(fun ps -> ps.Value.State = Free) |> Seq.length logger.DebugF "No peer available - %d dead peers" freePeers pendingMessages.Enqueue command // park the command for now ) |
Employment cycle of a Peer
When Tom hires a new peer, he gives a unique id and a position in his directory. When Tom fires a peer, Tom removes the peer from his directory and looks for a replacement.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: |
let newPeer() = let openSlotId = nextId() peerSlots <- peerSlots.Add(openSlotId, { Id = openSlotId; Peer = new Peer(openSlotId); State = Allocated }) let peer = peerSlots.[openSlotId].Peer connectionCount <- connectionCount + 1 peer let freePeer (id: int) = peerSlots |> Map.tryFind id |> Option.iter (fun ps -> logger.DebugF "Freeing %A" (peerSlots.[id].Peer :> IPeer).Target changeState id Free |> ignore let peer = ps.Peer connectionCount <- connectionCount - 1 if connectionCount < maxSlots then Db.updateState((peer :> IPeer).Target, -1) // blackball this peer let newPeer = Db.getPeer() // find a new peer newPeer |> Option.iter (fun p -> Db.updateState(p, 1) trackerIncoming.OnNext(Connect p)) peerSlots <- peerSlots.Remove id (peer :> IPeer).Receive Closed // tell the old peer to take a hike ) |
Handling a command
processCommand
handles 3 types of commands:
- connection requests. Either outgoing from this application or incoming
- related to the state of the peer like SetReady, Close
- commands. The last category is forwarded to a peer based on the 'amazing' routing algorithm - pick the first one who can do it
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: |
let mutable tip = Db.readHeader (Db.readTip()) let processCommand(command: TrackerCommand) = logger.DebugF "TrackerCommand> %A" command match command with | SetTip t -> tip <- t | GetPeers -> let peers = Db.getPeers() let cFreeSlots = maxSlots - peerSlots.Count for peer in peers |> Seq.truncate cFreeSlots do Db.updateState(peer, 1) trackerIncoming.OnNext(Connect peer) | Connect target -> let peer = newPeer() :> IPeer peer.Receive(Open(target, tip)) | IncomingConnection (stream, target) -> let peer = newPeer() :> IPeer peer.Receive(OpenStream(stream, target, tip)) | SetReady id -> peerSlots.TryFind id |> Option.iter(fun ps -> let peer = ps.Peer let oldState = changeState id Ready if oldState = Allocated then logger.InfoF "Connected to %A" peer blockchainIncoming.OnNext(Catchup(peer, null)) // Disable if you want no catchup on connect dequeuePendingMessage() ) | Close id -> freePeer id logger.DebugF "Connection count = %d" connectionCount | BitcoinMessage message -> forward command (Execute message) | Command peerCommand -> forward command peerCommand let processAddr (addr: Addr) = for a in addr.Addrs do Db.updateAddr(a) |
Helper functions that are called by other components. They are thread-safe and will enqueue messages for Tom
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: |
let getHeaders(gh: GetHeaders, peer: IPeer): Task<IObservable<Headers>> = let ts = new TaskCompletionSource<IObservable<Headers>>() trackerIncoming.OnNext(Command (PeerCommand.GetHeaders (gh, ts, peer))) ts.Task let getBlocks(blockHashes: seq<byte[]>): Task<IPeer * IObservable<Block * byte[]>> = let invHashes = seq { for h in blockHashes do yield new InvEntry(blockInvType, h) } |> Seq.toList let gd = new GetData(invHashes) let ts = new TaskCompletionSource<IPeer * IObservable<Block * byte[]>>() trackerIncoming.OnNext(Command (PeerCommand.DownloadBlocks (gd, ts))) ts.Task |
Send a message to every peer that is not busy
1: 2: 3: |
let processBroadcast (m: BitcoinMessage) = for peerSlot in peerSlots do (peerSlot.Value.Peer :> IPeer).Receive(Execute m) |
Server
The server binds to the port configured in the app.config file and listens to incoming connection. On a connection, it sends a message to Tom. At this time, there is no limit to the number of incoming connections
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: |
let port = defaultPort let ipAddress = IPAddress.Any let endpoint = IPEndPoint(ipAddress, port) let cts = new CancellationTokenSource() let listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) listener.Bind(endpoint) listener.Listen(int SocketOptionName.MaxConnections) let rec connectLoop() = async { let! socket = Async.FromBeginEnd(listener.BeginAccept, listener.EndAccept) let stream = new NetworkStream(socket) trackerIncoming.OnNext(TrackerCommand.IncomingConnection(stream, socket.RemoteEndPoint :?> IPEndPoint)) return! connectLoop() } let startServer() = logger.InfoF "Started listening on port %d" port Async.Start(connectLoop(), cts.Token) |
namespace System.Linq
--------------------
namespace Microsoft.FSharp.Linq
from Microsoft.FSharp.Core
{Id: int;
Peer: obj;
mutable State: obj;}
Full name: Tracker.PeerSlot
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
Full name: Tracker.maxSlots
Full name: Tracker.connectionCount
Full name: Tracker.seqId
Full name: Tracker.disposable
Full name: Tracker.scheduler
Full name: Tracker.incomingMessage
Full name: Tracker.broadcastToPeers
Full name: Tracker.pendingMessages
type Queue<'T> =
new : unit -> Queue<'T> + 2 overloads
member Clear : unit -> unit
member Contains : item:'T -> bool
member CopyTo : array:'T[] * arrayIndex:int -> unit
member Count : int
member Dequeue : unit -> 'T
member Enqueue : item:'T -> unit
member GetEnumerator : unit -> Enumerator<'T>
member Peek : unit -> 'T
member ToArray : unit -> 'T[]
...
nested type Enumerator
Full name: System.Collections.Generic.Queue<_>
--------------------
Queue() : unit
Queue(capacity: int) : unit
Queue(collection: IEnumerable<'T>) : unit
Full name: Tracker.nextId
Full name: Tracker.peerSlots
module Map
from Microsoft.FSharp.Collections
--------------------
type Map<'Key,'Value (requires comparison)> =
interface IEnumerable
interface IComparable
interface IEnumerable<KeyValuePair<'Key,'Value>>
interface ICollection<KeyValuePair<'Key,'Value>>
interface IDictionary<'Key,'Value>
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
member Add : key:'Key * value:'Value -> Map<'Key,'Value>
member ContainsKey : key:'Key -> bool
override Equals : obj -> bool
member Remove : key:'Key -> Map<'Key,'Value>
...
Full name: Microsoft.FSharp.Collections.Map<_,_>
--------------------
new : elements:seq<'Key * 'Value> -> Map<'Key,'Value>
Full name: Microsoft.FSharp.Collections.Map.empty
Full name: Tracker.InvalidTransition
Full name: Tracker.checkTransition
Full name: Microsoft.FSharp.Core.Operators.ignore
Full name: Microsoft.FSharp.Core.Operators.raise
Full name: Tracker.changeState
Full name: Tracker.dequeuePendingMessage
Full name: Tracker.assign
Full name: Tracker.forward
Full name: Microsoft.FSharp.Collections.Map.tryFind
Full name: Microsoft.FSharp.Core.Option.bind
Full name: Microsoft.FSharp.Core.Option.map
from Microsoft.FSharp.Control
type ArgumentException =
inherit SystemException
new : unit -> ArgumentException + 4 overloads
member GetObjectData : info:SerializationInfo * context:StreamingContext -> unit
member Message : string
member ParamName : string
Full name: System.ArgumentException
--------------------
ArgumentException() : unit
ArgumentException(message: string) : unit
ArgumentException(message: string, innerException: exn) : unit
ArgumentException(message: string, paramName: string) : unit
ArgumentException(message: string, paramName: string, innerException: exn) : unit
Full name: Microsoft.FSharp.Collections.Map.tryPick
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.filter
Full name: Microsoft.FSharp.Collections.Seq.length
Full name: Tracker.newPeer
Full name: Tracker.freePeer
Full name: Microsoft.FSharp.Core.Option.iter
Full name: Tracker.tip
Full name: Tracker.processCommand
Full name: Microsoft.FSharp.Collections.Seq.truncate
Full name: Microsoft.FSharp.Core.Operators.id
Full name: Tracker.processAddr
Full name: Tracker.getHeaders
type Task =
new : action:Action -> Task + 7 overloads
member AsyncState : obj
member ContinueWith : continuationAction:Action<Task> -> Task + 9 overloads
member CreationOptions : TaskCreationOptions
member Dispose : unit -> unit
member Exception : AggregateException
member Id : int
member IsCanceled : bool
member IsCompleted : bool
member IsFaulted : bool
...
Full name: System.Threading.Tasks.Task
--------------------
type Task<'TResult> =
inherit Task
new : function:Func<'TResult> -> Task<'TResult> + 7 overloads
member ContinueWith : continuationAction:Action<Task<'TResult>> -> Task + 9 overloads
member Result : 'TResult with get, set
static member Factory : TaskFactory<'TResult>
Full name: System.Threading.Tasks.Task<_>
--------------------
Task(action: Action) : unit
Task(action: Action, cancellationToken: CancellationToken) : unit
Task(action: Action, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj) : unit
Task(action: Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken) : unit
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
--------------------
Task(function: Func<'TResult>) : unit
Task(function: Func<'TResult>, cancellationToken: CancellationToken) : unit
Task(function: Func<'TResult>, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj) : unit
Task(function: Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : unit
Task(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
member Subscribe : observer:IObserver<'T> -> IDisposable
Full name: System.IObservable<_>
type TaskCompletionSource<'TResult> =
new : unit -> TaskCompletionSource<'TResult> + 3 overloads
member SetCanceled : unit -> unit
member SetException : exception:Exception -> unit + 1 overload
member SetResult : result:'TResult -> unit
member Task : Task<'TResult>
member TrySetCanceled : unit -> bool
member TrySetException : exception:Exception -> bool + 1 overload
member TrySetResult : result:'TResult -> bool
Full name: System.Threading.Tasks.TaskCompletionSource<_>
--------------------
TaskCompletionSource() : unit
TaskCompletionSource(creationOptions: TaskCreationOptions) : unit
TaskCompletionSource(state: obj) : unit
TaskCompletionSource(state: obj, creationOptions: TaskCreationOptions) : unit
Full name: Tracker.getBlocks
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>
val byte : value:'T -> byte (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.byte
--------------------
type byte = Byte
Full name: Microsoft.FSharp.Core.byte
Full name: Microsoft.FSharp.Collections.Seq.toList
Full name: Tracker.processBroadcast
Full name: Tracker.startTracker
Full name: Tracker.port
Full name: Tracker.ipAddress
type IPAddress =
new : newAddress:int64 -> IPAddress + 2 overloads
member Address : int64 with get, set
member AddressFamily : AddressFamily
member Equals : comparand:obj -> bool
member GetAddressBytes : unit -> byte[]
member GetHashCode : unit -> int
member IsIPv6LinkLocal : bool
member IsIPv6Multicast : bool
member IsIPv6SiteLocal : bool
member IsIPv6Teredo : bool
...
Full name: System.Net.IPAddress
--------------------
IPAddress(newAddress: int64) : unit
IPAddress(address: byte []) : unit
IPAddress(address: byte [], scopeid: int64) : unit
Full name: Tracker.endpoint
type IPEndPoint =
inherit EndPoint
new : address:int64 * port:int -> IPEndPoint + 1 overload
member Address : IPAddress with get, set
member AddressFamily : AddressFamily
member Create : socketAddress:SocketAddress -> EndPoint
member Equals : comparand:obj -> bool
member GetHashCode : unit -> int
member Port : int with get, set
member Serialize : unit -> SocketAddress
member ToString : unit -> string
static val MinPort : int
...
Full name: System.Net.IPEndPoint
--------------------
IPEndPoint(address: int64, port: int) : unit
IPEndPoint(address: IPAddress, port: int) : unit
Full name: Tracker.cts
type CancellationTokenSource =
new : unit -> CancellationTokenSource
member Cancel : unit -> unit + 1 overload
member Dispose : unit -> unit
member IsCancellationRequested : bool
member Token : CancellationToken
static member CreateLinkedTokenSource : params tokens:CancellationToken[] -> CancellationTokenSource + 1 overload
Full name: System.Threading.CancellationTokenSource
--------------------
CancellationTokenSource() : unit
Full name: Tracker.listener
type Socket =
new : socketInformation:SocketInformation -> Socket + 1 overload
member Accept : unit -> Socket
member AcceptAsync : e:SocketAsyncEventArgs -> bool
member AddressFamily : AddressFamily
member Available : int
member BeginAccept : callback:AsyncCallback * state:obj -> IAsyncResult + 2 overloads
member BeginConnect : remoteEP:EndPoint * callback:AsyncCallback * state:obj -> IAsyncResult + 3 overloads
member BeginDisconnect : reuseSocket:bool * callback:AsyncCallback * state:obj -> IAsyncResult
member BeginReceive : buffers:IList<ArraySegment<byte>> * socketFlags:SocketFlags * callback:AsyncCallback * state:obj -> IAsyncResult + 3 overloads
member BeginReceiveFrom : buffer:byte[] * offset:int * size:int * socketFlags:SocketFlags * remoteEP:EndPoint * callback:AsyncCallback * state:obj -> IAsyncResult
...
Full name: System.Net.Sockets.Socket
--------------------
Socket(socketInformation: SocketInformation) : unit
Socket(addressFamily: AddressFamily, socketType: SocketType, protocolType: ProtocolType) : unit
| Unknown = -1
| Unspecified = 0
| Unix = 1
| InterNetwork = 2
| ImpLink = 3
| Pup = 4
| Chaos = 5
| NS = 6
| Ipx = 6
| Iso = 7
...
Full name: System.Net.Sockets.AddressFamily
| Stream = 1
| Dgram = 2
| Raw = 3
| Rdm = 4
| Seqpacket = 5
| Unknown = -1
Full name: System.Net.Sockets.SocketType
| IP = 0
| IPv6HopByHopOptions = 0
| Icmp = 1
| Igmp = 2
| Ggp = 3
| IPv4 = 4
| Tcp = 6
| Pup = 12
| Udp = 17
| Idp = 22
...
Full name: System.Net.Sockets.ProtocolType
| Debug = 1
| AcceptConnection = 2
| ReuseAddress = 4
| KeepAlive = 8
| DontRoute = 16
| Broadcast = 32
| UseLoopback = 64
| Linger = 128
| OutOfBandInline = 256
| DontLinger = -129
...
Full name: System.Net.Sockets.SocketOptionName
Full name: Tracker.connectLoop
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken
Full name: Microsoft.FSharp.Control.Async
--------------------
type Async<'T>
Full name: Microsoft.FSharp.Control.Async<_>
static member Async.FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member Async.FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
Socket.BeginAccept(receiveSize: int, callback: AsyncCallback, state: obj) : IAsyncResult
Socket.BeginAccept(acceptSocket: Socket, receiveSize: int, callback: AsyncCallback, state: obj) : IAsyncResult
Socket.EndAccept(buffer: byref<byte []>, asyncResult: IAsyncResult) : Socket
Socket.EndAccept(buffer: byref<byte []>, bytesTransferred: byref<int>, asyncResult: IAsyncResult) : Socket
type NetworkStream =
inherit Stream
new : socket:Socket -> NetworkStream + 3 overloads
member BeginRead : buffer:byte[] * offset:int * size:int * callback:AsyncCallback * state:obj -> IAsyncResult
member BeginWrite : buffer:byte[] * offset:int * size:int * callback:AsyncCallback * state:obj -> IAsyncResult
member CanRead : bool
member CanSeek : bool
member CanTimeout : bool
member CanWrite : bool
member Close : timeout:int -> unit
member DataAvailable : bool
member EndRead : asyncResult:IAsyncResult -> int
...
Full name: System.Net.Sockets.NetworkStream
--------------------
NetworkStream(socket: Socket) : unit
NetworkStream(socket: Socket, ownsSocket: bool) : unit
NetworkStream(socket: Socket, access: FileAccess) : unit
NetworkStream(socket: Socket, access: FileAccess, ownsSocket: bool) : unit
Full name: Tracker.startServer