Advertisement

C# Sockets

Started by January 02, 2004 09:43 PM
3 comments, last by glassJAw 20 years, 10 months ago
I''ve been messing around with asynchronous sockets in C# and I''ve been getting some very screwed up exceptions when I try to Shutdown() or Close() a socket. The socket is a static member of a class (and so are the functions that make it listen, close, and it''s Accept callback)

        //Listener socket
        private static Socket listener; 
Here is the function that starts it listening:

        //Initialize server and begin listening

        public static bool Init(int listenPort)
        {
            //Create a socket object

            listener = new Socket (AddressFamily.InterNetwork, SocketType.Stream,
                ProtocolType.Tcp);

            //Start listening on given port

            listener.Bind (new IPEndPoint(IPAddress.Any, listenPort));
            listener.Listen(MAX_PENDING_CONNECTIONS);

            //Setup a callback for notification of connections

            listener.BeginAccept (new AsyncCallback(OnConnectRequest), listener);

            //Set server started flag

            m_serverStarted = true;

            //Success

            return true;
        }
Here is the accept callback

        //Connection request

        private static void OnConnectRequest(IAsyncResult result)
        {
            try
            {
                User newUser = new User();
                
                //Accept connection and spawn new socket

                newUser.userSocket = listener.EndAccept(result);
                AddNewUser(newUser);
            }
            catch (ObjectDisposedException)
            {
                //Socket has been closed

                frmMain.instance.Print ("Error", "Connection request on closed socket", Color.Red);
                Close();
            }
            catch (SocketException se)
            {
                //Socket exception

                frmMain.instance.Print ("Socket Error " + se.ErrorCode.ToString(),
                    se.Message, Color.Red);
            }
        }
And here is the function that consistently screws up. The one to close the socket:

        //Close server

        public static bool Close()
        {
            //Clear server started flag

            m_serverStarted = false;

            //Release socket

            listener.Shutdown(SocketShutdown.Both);
            listener.Close();
            
            //Log status

            frmMain.instance.Print ("Server", "Listening ended", Color.Blue);

            //Success

            return true;
        }
When it hits the Shutdown() function, I get the following error message:
An unhandled exception of type ''System.Net.Sockets.SocketException'' occurred in system.dll

Additional information: A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied 
That kind of puzzled me as I''m clearly not using datagram sockets. So, since the Shutdown() function is not strictly necessary, I commented it and tried again. I got a new type of exception
An unhandled exception of type ''System.InvalidOperationException'' occurred in system.dll

Additional information: AcceptCallback 
There was no source code to show where the debugger broke (so not in my code). When I take out the call to BeginAccept() the socket closes down properly (unless I uncomment the call to Shutdown()). I updated the .NET Framework to the latest version but the problems persist. Anyone know what the hell is going on? Is it because the callback is in a static function or what?
I have since moved the offending code out of static functions. Same problems.
Advertisement
I've recently been helping a friend with his client/server app - he's got a static-style class and we're using a Monitor in the static ctor to wait for the connection to finish connecting. We have a major deadlock problem happening (like nothing I've ever seen), and I think it has to do with the static nature of his stuff.

I implemented the same exact types of things in a non-static-style class and it worked fine.

From what I see of your code, you're doing it the exact same way that I wrote my networking stuff (unrelated to the above friend's code). However, my netcode is not using any static stuff. What I usually have problems with is succefully getting notified when the other end closes its connection.

I will post both of my ENTIRE classes for review to see if anything is odd. Expect a ton of typos in the commenting - I did all that at about 4 AM with only two Mt. Dews to ward off the sandman.

Note that these are likely broken some way or another, and that I'm abandoning them in favor of DirectPlay (which rules, btw).

using System;using System.IO;using System.Net;using System.Windows.Forms;using System.Threading;namespace System.Net.Sockets{	/// <summary>	/// Delegate type for Events dealing with Socket objects.	/// </summary>	public delegate void SocketHandler(object sender, Socket socket);		/// <summary>	/// Delegate type for Events dealing with packet data (MemoryStreams)	/// </summary>	public delegate bool PacketHandler(object sender, MemoryStream stream);			/// <summary>	/// Delegate type for Events dealing with NetClient objects.	/// </summary>	public delegate void NetClientHandler(object sender, NetClient client);		/// <summary>	/// Allows reading/writing to a remote instance of NetClient using friendly, asynchronous callbacks.	/// </summary>	public class NetClient	{		bool active;		bool waitingForCallReturn;		MemoryStream returningStream;		Socket socket;				AsyncCallback connect;		AsyncCallback header;		AsyncCallback packet;				Thread disconnect;				byte[] hbuffer;		byte[] pbuffer;				ManualResetEvent wait;				/// <summary>		/// Gets the Socket associated with this NetClient.		/// </summary>		public Socket Socket { get { return socket; } }				/// <summary>		/// Event raised when we successfully connect to a remote listener.		/// </summary>		public event EventHandler  Connected;				/// <summary>		/// Event raised when we are disconnected from a remove listener.		/// </summary>		public event EventHandler  Disconnected;				/// <summary>		/// Event raised when the remote host sends us a packet.		/// </summary>		public event PacketHandler Read;				/// <summary>		/// Creates a NetClient and binds it to the local computer on a system-assigned port.		/// </summary>		public NetClient()		{			Console.WriteLine("NC");			wait = new ManualResetEvent(false);			returningStream=null;			waitingForCallReturn=false;			active=true;			Application.ApplicationExit += new EventHandler(KillMe);						socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);			socket.Bind(new IPEndPoint(IPAddress.Any, 0));		}				/// <summary>		/// Creates a NetClient, binds it and connects to a remote host.		/// </summary>		/// <param name="hostname">Hostname or IP address to connect to.</param>		/// <param name="port">The remote port to connect to.</param>		public NetClient(string hostname, int port) : this()		{			Console.WriteLine("NC2");			/*			waitingForCallReturn=false;			active=true;			Application.ApplicationExit += new EventHandler(KillMe);					socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);			socket.Bind(new IPEndPoint(IPAddress.Any, 0));			disconnect = new Thread(new ThreadStart(DisconnectProc));			disconnect.Start();			*/			Connect(hostname, port);		}				/// <summary>		/// Creates a NetClient using a Socket.  Not intended for public access.		/// </summary>		/// <param name="s">The socket to use.</param>		internal NetClient(Socket s)		{			Console.WriteLine("NC Socket");			wait = new ManualResetEvent(false);			returningStream=null;			waitingForCallReturn=false;			active=true;			Application.ApplicationExit += new EventHandler(KillMe);						socket = s;			disconnect = new Thread(new ThreadStart(DisconnectProc));			disconnect.Start();			Start();		}				/// <summary>		/// Shorts down and disconnects the NetClient		/// </summary>		~NetClient()		{			Console.WriteLine("~NC");			if (socket != null)			{				socket.Shutdown(SocketShutdown.Both);				socket.Close();				socket = null;			}		}				/// <summary>		/// Deactivates the Disconnect polling thread.  Not intended for public access.		/// </summary>		/// <param name="o"></param>		/// <param name="ea"></param>		void KillMe(object o, EventArgs ea)		{			active=false;		}				/// <summary>		/// Connects to a remote host.		/// </summary>		/// <param name="hostname">The hostname or IP address to connect to.</param>		/// <param name="port">The remote port to connect to.</param>		public void Connect(string hostname, int port)		{			IPHostEntry iphe = Dns.Resolve(hostname);						Connect(new IPEndPoint(iphe.AddressList[0], port));		}				/// <summary>		/// Connects to a remote host.		/// </summary>		/// <param name="remoteEP">The IPEndPoint to connect to.</param>		public void Connect(IPEndPoint remoteEP)		{			connect = new AsyncCallback(ConnectHandler);			socket.BeginConnect(remoteEP, connect, this);		}				/// <summary>		/// Closes any active connection to a remote host.		/// </summary>		public void Close()		{			Console.WriteLine("NC Close");			if (socket != null)			{				socket.Shutdown(SocketShutdown.Both);				socket.Close();				socket = null;			}		}						/// <summary>		/// Activates packet receiving functionality on this NetClient.		/// </summary>		public void Start()		{			Console.WriteLine("NC Start");			hbuffer = new byte[4];			pbuffer = new byte[64];			header = new AsyncCallback(HeaderHandler);			packet = new AsyncCallback(PacketHandler);						socket.BeginReceive(hbuffer, 0, 4, SocketFlags.None, header, this);		}						/// <summary>		/// Sends a packet to the remote host.		/// </summary>		/// <param name="stream"></param>		public void SendPacket(MemoryStream stream)		{			MemoryStream ms = new MemoryStream();			BinaryWriter bw = new BinaryWriter(ms);			bw.Write((int)stream.Length);			bw.Flush();			socket.Send(ms.ToArray());			socket.Send(stream.ToArray());			bw.Close();		}						/// <summary>		/// Sends a packet to the remote host and waits for a response.		/// </summary>		/// <param name="stream">The stream to pass to the remote host.</param>		/// <returns>The response stream from the remote host.</returns>		public MemoryStream SendCall(MemoryStream stream)		{			returningStream=new MemoryStream();			waitingForCallReturn = true;						SendPacket(stream);			wait.WaitOne();			wait.Reset();						MemoryStream toret = returningStream;						returningStream=null;			waitingForCallReturn = false;						return toret;		}				/// <summary>		/// Callback for Asynchronous Connection Acceptance.		/// </summary>		/// <param name="iar"></param>		void ConnectHandler(IAsyncResult iar)		{			if (Connected != null)				Connected(this, new EventArgs());			socket.EndConnect(iar);						disconnect = new Thread(new ThreadStart(DisconnectProc));			disconnect.Start();						Start();		}				/// <summary>		/// Handles receiving a packet header and starts the packet body async request.		/// </summary>		/// <param name="iar"></param>		void HeaderHandler(IAsyncResult iar)		{						int bytes = socket.EndReceive(iar);			if (bytes == 4)			{				BinaryReader br = new BinaryReader(new MemoryStream(hbuffer, 0, 4));				int size = br.ReadInt32();				br.Close();				if (size > pbuffer.Length)					pbuffer = new byte[size];				socket.BeginReceive(pbuffer, 0, size, SocketFlags.None, packet, this);			}		}						/// <summary>		/// Handles receiving a packet body and starts the next packet header async request.		/// </summary>		/// <param name="iar"></param>		void PacketHandler(IAsyncResult iar)		{			int bytes = socket.EndReceive(iar);			BinaryReader br = new BinaryReader(new MemoryStream(hbuffer, 0, 4));			int size = br.ReadInt32();			br.Close();			if (bytes != size)				MessageBox.Show("Packet size is wrong (bytes = " + bytes + ", size = " + size +  ")");			else			{				if (!waitingForCallReturn && Read != null)					Read(this, new MemoryStream(pbuffer, 0, size, false));				else if (waitingForCallReturn)					sendcall_Read(this, new MemoryStream(pbuffer, 0, size, false));									socket.BeginReceive(hbuffer, 0, 4, SocketFlags.None, header, this);			}		}						/// <summary>		/// Waits for the socket to disconnect and raises the Disconnected event.		/// </summary>		void DisconnectProc()		{			while (socket.Connected && active) Thread.Sleep(10);			if (active && (Disconnected != null))				Disconnected(this, new EventArgs());			Console.WriteLine("Disconnect Proc Shutting down");		}				private bool sendcall_Read(object sender, MemoryStream stream)		{			returningStream = stream;			wait.Set();			return false;		}	}			/// <summary>	/// Listens for incoming connections, automatically accepts and fires events when a remote connection is made.	/// </summary>	public sealed class NetListener	{		bool active;		Socket socket;		AsyncCallback callback;				public event NetClientHandler Accept;				/// <summary>		/// Creates a new listener on the specified port.		/// </summary>		/// <param name="port">The port to listen on.</param>		/// <remarks>Does not start listening.  Call Start() to begin listening.</remarks>		public NetListener(int port)		{			callback = new AsyncCallback(AcceptHandler);			socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);			socket.Bind(new IPEndPoint(IPAddress.Any, port));			socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1);		}				/// <summary>		/// Starts listening for connections.  New connections will raise the Accept event.		/// </summary>		public void Start()		{			active = true;			socket.Listen(-1);			socket.BeginAccept(callback, this);		}				/// <summary>		/// Stops listening for connections.		/// </summary>		public void Stop()		{			active = false;		}				/// <summary>		/// The Callback for BeginAccept.  Not intended for use outside of this class.		/// </summary>		/// <param name="iar">Callback parameter info required by BeginAccept.</param>		void AcceptHandler(IAsyncResult iar)		{			if (active)			{				NetClient nc = new NetClient(socket.EndAccept(iar));				if (Accept != null)					Accept(this, nc);				socket.BeginAccept(callback, this);			}		}	}}


[edited by - Nypyren on January 3, 2004 5:55:02 AM]
Aside from the above code, I have one more bit of advice.

At least when I was messing aroung in C++ doing sockets, you had to make sure to do a receive to clear out all the crap that might have accumulated in the network buffer before closing the socket. I think in .NET you can pass something to the shutdown (?) function to tell it that you (humbly) don''t give a rat''s about any leftovers.
Thanks for your help.

Re. the Shutdown() function, that''s basically what I''m doing. It only takes one parameter that can have one of 3 values: Stop sends, stop receives, or stop both.

I tell it to stop both and it dies.

I guess I''ll rewrite it using your code as a guide or use the Winsock ActiveX control or something.

Thanks again.

This topic is closed to new replies.

Advertisement