void Initialize(ushort port) { // initialize native layer int initialize = NativeBindings.network_initialize(); if (initialize == 0) { Debug.Log("network_initialized"); // create the socket and listen on IPv6 and IPv4 via DualMode int error = 0; byte[] bytes = IPAddress.IPv6Any.GetAddressBytes(); NetworkEndPoint address = NetworkEndPoint.CreateIPv6(bytes, port); if (NativeBindings.network_create_socket(ref listener, ref address, ref error) == 0) { // try to enable dual mode to support both IPv6 and IPv4 if (NativeBindings.network_set_dualmode(listener, 1, ref error) != 0) { Debug.LogError("network_set_dualmode failed: " + (NativeError)error); } // bind the socket if (NativeBindings.network_bind(listener, ref address, ref error) == 0) { // configure the socket ConfigureSocket(listener); } else { Debug.LogError("network_bind failed: " + (NativeError)error); } } else { Debug.LogError("network_create_socket failed: " + (NativeError)error); } } else { Debug.LogError("network_initialize failed: " + initialize); } }
// try to accept the next connection bool AcceptNext(out long socket) { socket = -1; int error = 0; NetworkEndPoint clientAddress = new NetworkEndPoint(); if (NativeBindings.network_accept(listener, ref socket, ref clientAddress, ref error) == 0) { //Debug.Log("network_accept: " + socket + " address=" + clientAddress); return(true); } // log error if unusual // (ignore EWOULDBLOCK, which is expected for nonblocking sockets) // (http://www.workers.com.br/manuais/53/html/tcp53/mu/mu-7.htm) else if ((NativeError)error != NativeError.EWOULDBLOCK) { Debug.LogError("network_accept failed: " + (NativeError)error); } return(false); }
// the connect thread function void ConnectThreadFunction(NetworkEndPoint address) { // absolutely must wrap with try/catch, otherwise thread // exceptions are silent try { // connect (blocking) int error = 0; if (NativeBindings.network_connect(socket, ref address, ref error) == 0) { // configure the socket ConfigureSocket(socket); // connect successful // AFTER configuring the socket. so that we don't call recv // while still blocking! Debug.Log("[Client] connected!"); lock (this) { _Connected = true; } } // log errors if failed. // (ECONNABORTED is expected if we call Disconnect while connecting) else if ((NativeError)error != NativeError.ECONNABORTED) { Debug.LogError("network_connect failed: " + (NativeError)error); } } catch (Exception exception) { // something went wrong. probably important. Debug.LogError("[Client] Connect Exception: " + exception); } finally { // we definitely aren't connecting anymore. either it worked or // it failed. lock (this) { _Connecting = false; } } }
// client's ip is sometimes needed by the server, e.g. for bans public unsafe string GetClientAddress(int connectionId) { // find the connection if (clients.TryGetValue(connectionId, out ClientToken token) && token.socket != -1) { int error = 0; // NetworkEndPoint NEEDS to be created with length, otherwise // data array is empty and get_peer_address won't write into it NetworkEndPoint address = new NetworkEndPoint { length = sizeof(NetworkEndPoint) }; if (NativeBindings.network_get_peer_address(token.socket, ref address, ref error) == 0) { return(address.Ip); } else { Debug.LogError("network_get_socket_address failed: " + (NativeError)error); } } return(""); }
public void Connect(string hostname, ushort port) { // not if already started if (Connecting || Connected) { return; } // create 'N' content buffers contentBuffers = new byte[MaxReceivesPerTickPerConnection][]; for (int i = 0; i < contentBuffers.Length; ++i) { // create content buffer depending on configured MaxMessageSize contentBuffers[i] = new byte[MaxMessageSize]; } // reset state lastConnected = false; contentSize = 0; // resolve host name (if hostname. otherwise it returns the IP) // and connect to the first available address (IPv4 or IPv6) // => GetHostAddresses is BLOCKING (for a very short time). we could // move it to the ConnectThread, but it's hardly worth the extra // code since we would have to create the socket in ConnectThread // too, which would require us to use locks around socket every- // where. it's better to live with a <1s block (if any). try { IPAddress[] addresses = Dns.GetHostAddresses(hostname); if (addresses.Length > 0) { // try to parse the IP string ip = addresses[0].ToString(); if (NetworkEndPoint.TryParse(ip, port, out NetworkEndPoint address)) { // create the socket int error = 0; if (NativeBindings.network_create_socket(ref socket, ref address, ref error) == 0) { // note: no need to enable DualMode because we // connect to IPv4 or IPv6 depending on 'hostname' // and then we don't reuse the socket. // we are connecting _Connecting = true; // thread isn't running, no lock needed! Debug.Log("[Client] connecting to: " + ip); // connect is blocking. let's call it in the thread and // return immediately. connectThread = new Thread(() => { ConnectThreadFunction(address); }); connectThread.IsBackground = true; connectThread.Start(); } else { Debug.LogError("network_create_socket failed: " + (NativeError)error); } } else { Debug.LogError("[Client] Connect: failed to parse ip: " + ip + " port: " + port); } } // it's not an error. just an invalid host so log a warning. else { Debug.LogWarning("[Client] Connect: failed to resolve host: " + hostname + " (no address found)"); } } catch (SocketException exception) { // it's not an error. just an invalid host so log a warning. Debug.LogWarning("[Client] Connect: failed to resolve host: " + hostname + " reason: " + exception); } }