简体   繁体   中英

Handling sockets using internal classes in C#

I have created a class that acts as a plugin for another application. It should hold functions to use in the main application. It works in general - that means i can handle usual functions like calculations and even reading files. But i have problems implementing a socket class. I know how to work with sockets in general but in this case i have a problem.

As you may see in the code, there is an internal class SockAttrib that should manage the socket creation, the listening and also the messages. Received messages are stored in a dictionary.

public class Net : Module {

    private static ReadOnlyCollection<CustomMethod> customMethods;

    internal class SockAttrib {

        public Socket listener;
        public Socket handler;

        /* create the listener */
        public SockAttrib(int port) {
            IPHostEntry host = Dns.GetHostEntry("localhost");
            IPAddress ipAddress = host.AddressList[1];
            IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);          
            try {
                listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                listener.Bind(localEndPoint);
                listener.Listen(10);
                handler = listener.Accept();
            } catch (Exception e) { Console.WriteLine("socket() " + e); }
        }

        /* listen */
        public SockAttrib() {
                try {
                // Incoming data from the client.    
                string data = "";
                byte[] bytes = null;
                    while (true) {
                        bytes = new byte[1024];
                        int bytesRec = handler.Receive(bytes);
                        data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
                        if (data.IndexOf("<EOF>") > -1)
                        {
                            messages[++idm] = data;
                            //return;
                        }
                    }
                }
                catch (Exception e) { Console.WriteLine("listen() "+e); }
        }

        public void Close() {
            handler.Close();
        }
    }

    /* message index */
    private static int idm = 0;
    /* list of messages */
    private static Dictionary<int, String> messages = new Dictionary<int, String>();

    public Net() {               
        if (customMethods != null) return;
        List<CustomMethod> moduleMethods = new List<CustomMethod>();
        moduleMethods.Add(new CustomMethod(typeof(int), "socket", typeof(int)));
        moduleMethods.Add(new CustomMethod(typeof(int), "listen" ));
        moduleMethods.Add(new CustomMethod(typeof(string), "sockread"));
        customMethods = moduleMethods.AsReadOnly();
    }

    public ReadOnlyCollection<CustomMethod> Prototypes {
        get { return customMethods; }
    }

    public object OnMethodInvoke(String functionName, List<object> parameters) {

        if( functionName == "socket") {
            int port = (int)parameters[0];
            SockAttrib sa = new SockAttrib( port );
            return 1;
        }

        if (functionName == "listen") {
            SockAttrib sa = new SockAttrib();
            return 1; 
        }

        if (functionName == "sockread") {
            if (idm > 0) {
                String message = messages[--idm];
                return message;
            } else {
                return "nope";
            }
        }

        return false;
    }

}

My problem is the handler. The creation of the socket works but as soon as i connect to the socket using netcat the socket stop listening and i dont get any responses. I hope its not too much code and it should also be easy readable.

Finally the module gets exported as a library (dll) so i cant really give a minimal working example without also posting the module handler.

Though your requirements are still a bit fuzzy, I will give it a try.

First I would recommend to create a class containing the core functionality of your TCP server. This makes it easier to unit-test your code and to adapt it to changing requirements.

/// <summary>
/// This class encapsulates the TCP server
/// </summary>
public class Server : IDisposable
{
    private static TcpListener _listener;
    private static TcpClient _client;
    private static NetworkStream _stream;
    private static byte[] _buffer;
    private static readonly StringBuilder _receivedText = new StringBuilder();
    private const string EOF = "<EOF>";

    /// <summary>
    /// Starts listening on the specified port
    /// </summary>
    /// <param name="port">The port number</param>
    public Server(int port)
    {
        _listener = new TcpListener(IPAddress.Any, port);
        _listener.Start();
        _listener.BeginAcceptTcpClient(Accepted, null);
    }

    public void Dispose()
    {
        if (_client != null)
        {
            _client.Dispose();
        }
        if (_listener != null)
        {
            _listener.Stop();
        }
    }

    /// <summary>
    /// Returns any text that has been sent via TCP to the port specified in the constructor.
    /// </summary>
    /// <returns>The received text, or null if no (complete) text has been received yet.</returns>
    /// <remarks>
    /// The method returns the next text delimited by "&lt;EOF&gt;".
    /// </remarks>
    public string Read()
    {
        lock (_receivedText)
        {
            var receivedText = _receivedText.ToString();
            var eofIndex = receivedText.IndexOf(EOF);
            if (eofIndex < 0)
                return null; // no (complete) text has been received yet
            var result = receivedText.Substring(0, eofIndex);
            _receivedText.Remove(0, eofIndex + EOF.Length);
            return result;
        }
    }

    // called whenever a client has connected to our server.
    private static void Accepted(IAsyncResult ar)
    {
        _client = _listener.EndAcceptTcpClient(ar);
        _stream = _client.GetStream();
        _buffer = new byte[4096];
        _stream.BeginRead(_buffer, 0, _buffer.Length, Read, null);
    }

    // called whenever data has arrived or if the client closed the TCP connection
    private static void Read(IAsyncResult ar)
    {
        var bytesReceived = _stream.EndRead(ar);
        if (bytesReceived == 0)
        {
            // TCP connection closed
            _client.Close();
            _client = null;
            _stream.Dispose();
            _stream = null;
            // prepare for accepting the next TCP connection
            _listener.BeginAcceptTcpClient(Accepted, null);
            return;
        }

        lock (_receivedText)
        {
            _receivedText.Append(Encoding.ASCII.GetString(_buffer, 0, bytesReceived));
        }

        // prepare for reading more
        _stream.BeginRead(_buffer, 0, _buffer.Length, Read, null);
    }
}

Integrating this into your Net class should then be fairly simple:

// static or not? Depends on your "Module plugin" architecture
private static Server _server;

public object OnMethodInvoke(String functionName, List<object> parameters)
{
    if (functionName == "socket")
    {
        if (_server != null)
        {
            // oops, already open
            return 0;
        }
        int port = (int)parameters[0];
        _server = new Server(port);
        return 1;
    }

    if (functionName == "sockread")
    {
        if (_server != null)
        {
            return _server.Read() ?? "nope";
        }
        else
        {
            return "nope";
        }
    }

    return false;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM