[Mono-list] TcpClient & TcpListener - request for code review

Phillip Pearson pp@myelin.co.nz
Thu, 22 Nov 2001 00:25:49 +1300


This is a multi-part message in MIME format.

------=_NextPart_000_0075_01C172EC.3C8F09E0
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 7bit

> You might want to have an `internal' method (this is a method that can
> only be called from within the assembly) to "configure" the TcpClient:
>
> internal SetTcpClient (Socket s)
> {
> MySocket = s;
> }

Aha, so *that's* how you do 'friend' in C# :)

I've attached the now complete (but probably buggy) code for
System.Net.Sockets.TcpClient and System.Net.Sockets.TcpListener to this
message.  Can the person in charge of System.Net[.Sockets] (Miguel?) review
the files and commit them if they look OK, or tell me what needs to be done
if they aren't OK?

Thanks,
Phillip.


------=_NextPart_000_0075_01C172EC.3C8F09E0
Content-Type: text/plain;
	name="TcpClient.cs"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="TcpClient.cs"

// System.Net.Sockets.TcpClient.cs=0A=
//=0A=
// Author:=0A=
//    Phillip Pearson (pp@myelin.co.nz)=0A=
//=0A=
// Copyright (C) 2001, Phillip Pearson=0A=
//    http://www.myelin.co.nz=0A=
//=0A=
=0A=
// NB: This is untested (probably buggy) code - take care if using it=0A=
=0A=
using System;=0A=
using System.Net;=0A=
=0A=
namespace System.Net.Sockets=0A=
{=0A=
	/// <remarks>=0A=
	/// A slightly more abstracted way to create an=0A=
	/// outgoing network connections than a Socket.=0A=
	/// </remarks>=0A=
	public class TcpClient : IDisposable=0A=
	{=0A=
		// private data=0A=
		=0A=
		private NetworkStream stream;=0A=
		private bool active;=0A=
		private Socket client;=0A=
		private bool disposed =3D false;=0A=
		=0A=
		// constructor=0A=
=0A=
		/* TODO: Code common to all the constructors goes here.=0A=
		Can anyone tell me why I can't call a constructor from=0A=
		another constructor? */=0A=
		=0A=
		/// <summary>
		/// Some code that is shared between the constructors.
		/// </summary>=0A=
		private void common_constructor ()=0A=
		{=0A=
			active =3D false;=0A=
			client =3D new Socket(AddressFamily.InterNetwork,=0A=
				SocketType.Stream, ProtocolType.Tcp);=0A=
		}=0A=
=0A=
		/// <summary>
		/// Constructs a new TcpClient with no connection set up
		/// </summary>=0A=
		public TcpClient ()=0A=
		{=0A=
			common_constructor();=0A=
			client.Bind(new IPEndPoint(IPAddress.Any, 0));=0A=
		}=0A=
	=0A=
		/// <summary>
		/// Constructs a new TcpClient with a specified local endpoint.
		/// Use this if you want to have your connections originating
		/// from a certain port, or a certain IP (on a multi homed
		/// system).
		/// </summary>
		/// <param name=3D"local_end_point">The aforementioned local =
endpoint</param>=0A=
		public TcpClient (IPEndPoint local_end_point)=0A=
		{=0A=
			common_constructor();=0A=
			client.Bind(local_end_point);=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Constructs a new TcpClient and connects to a specified
		/// host on a specified port.  A quick way to set up a network
		/// connection.
		/// </summary>
		/// <param name=3D"hostname">The host to connect to, e.g.
		/// 192.168.0.201 or www.myelin.co.nz</param>
		/// <param name=3D"port">The port to connect to, e.g. 80 for =
HTTP</param>=0A=
		public TcpClient (string hostname, int port)=0A=
		{=0A=
			common_constructor();=0A=
			client.Bind(new IPEndPoint(IPAddress.Any, 0));=0A=
			Connect(hostname, port);=0A=
		}=0A=
				=0A=
		/// <summary>
		/// A flag that is 'true' if the TcpClient has an active connection
		/// </summary>=0A=
		protected bool Active=0A=
		{=0A=
			get { return active; }=0A=
			set { active =3D value; }=0A=
		}=0A=
		=0A=
		/// <summary>
		/// The socket that all network comms passes through
		/// </summary>=0A=
		protected Socket Client=0A=
		{=0A=
			get { return client; }=0A=
			set { client =3D value; } //TODO: should we be able to set the socket =
like this?=0A=
		}=0A=
=0A=
		/// <summary>
		/// Internal function to allow TcpListener.AcceptTcpClient
		/// to work (it needs to be able to set protected property
		/// 'Client')
		/// </summary>
		/// <param name=3D"s"></param>=0A=
		internal void SetTcpClient (Socket s)=20
		{=0A=
			Client =3D s; // client or Client?  They are the same at the moment=0A=
		}=0A=
		=0A=
		/// <summary>
		/// If set, the socket will remain open after it has been
		/// instructed to close, in order to send data that remains
		/// in the buffer.
		/// </summary>=0A=
		public LingerOption LingerState=0A=
		{=0A=
			get {=0A=
				return (LingerOption)client.GetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.Linger);=0A=
			}=0A=
			set {=0A=
				client.SetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.Linger, value);=0A=
			}=0A=
		}=0A=
				=0A=
		/// <summary>
		/// <p>If set, outbound data will be sent at once rather than =
collected
		/// until enough is available to fill a packet.</p>
		///=20
		/// <p>This is the TCP_NODELAY sockopt from BSD sockets and WinSock.
		/// For more information, look up the Nagle algorithm.</p>
		/// </summary>=0A=
		public bool NoDelay=0A=
		{=0A=
			get {=0A=
				return (bool)client.GetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.NoDelay);=0A=
			}=0A=
			set {=0A=
				client.SetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.NoDelay, value);=0A=
			}=0A=
		}=0A=
				=0A=
		/// <summary>
		/// How big the receive buffer is (from the connection socket)
		/// </summary>=0A=
		public int ReceiveBufferSize=0A=
		{=0A=
			get {=0A=
				return (int)client.GetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.ReceiveBuffer);=0A=
			}=0A=
			set {=0A=
				client.SetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.ReceiveBuffer, value);=0A=
			}=0A=
		}=0A=
			=0A=
		/// <summary>
		/// How long before the socket will time out on a=20
		/// Receive() call
		/// </summary>=0A=
		public int ReceiveTimeout=0A=
		{=0A=
			get {=0A=
				return (int)client.GetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.ReceiveTimeout);=0A=
			}=0A=
			set {=0A=
				client.SetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.ReceiveTimeout, value);=0A=
			}=0A=
		}=0A=
		=0A=
		/// <summary>
		/// How big the send buffer is (from the connection socket)
		/// </summary>=0A=
		public int SendBufferSize=0A=
		{=0A=
			get {=0A=
				return (int)client.GetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.SendBuffer);=0A=
			}=0A=
			set {=0A=
				client.SetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.SendBuffer, value);=0A=
			}=0A=
		}=0A=
		=0A=
		/// <summary>
		/// How long before the socket will time out on a
		/// Send() call
		/// </summary>=0A=
		public int SendTimeout=0A=
		{=0A=
			get {=0A=
				return (int)client.GetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.SendTimeout);=0A=
			}=0A=
			set {=0A=
				client.SetSocketOption(=0A=
					SocketOptionLevel.Socket,=0A=
					SocketOptionName.SendTimeout, value);=0A=
			}=0A=
		}=0A=
		=0A=
		=0A=
		// methods=0A=
		=0A=
		/// <summary>
		/// Closes the socket and disposes of all managed resources
		/// </summary>=0A=
		public void Close ()=0A=
		{=0A=
			Dispose(true);=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Connects to a specified remote endpoint
		/// </summary>
		/// <param name=3D"remote_end_point">The aforementioned =
endpoint</param>=0A=
		public void Connect (IPEndPoint remote_end_point)=0A=
		{=0A=
			client.Connect(remote_end_point);=0A=
			stream =3D new NetworkStream(client, true);=0A=
			active =3D true;=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Connects to an IP address on a port
		/// </summary>
		/// <param name=3D"address">The IP address (get it from =
Dns.GetHostByName)</param>
		/// <param name=3D"port">The port to connect to, e.g. 80 for =
HTTP</param>=0A=
		public void Connect (IPAddress address, int port)=0A=
		{=0A=
			Connect(new IPEndPoint(address, port));=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Resolves a fully qualified domain name to an IP address
		/// and connects to it on a specified port
		/// </summary>
		/// <param name=3D"hostname">The hostname, e.g. =
www.myelin.co.nz</param>
		/// <param name=3D"port">The port, e.g. 80 for HTTP</param>=0A=
		public void Connect (string hostname, int port)=0A=
		{=0A=
			IPHostEntry host =3D Dns.GetHostByName(hostname);=0A=
			/* TODO: This will connect to the first IP address returned=0A=
			from GetHostByName.  Is that right? */=0A=
			Connect(new IPEndPoint(host.AddressList[0], port));=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Gets rid of all managed resources
		/// </summary>=0A=
		public void Dispose()=0A=
		{=0A=
			Dispose(true);=0A=
			GC.SuppressFinalize(this);=0A=
		}=0A=
=0A=
		/// <summary>
		/// Gets rid of all unmanaged resources
		/// </summary>
		/// <param name=3D"disposing">If this is true, it gets rid of all=0A=
		/// managed resources as well</param>=0A=
		protected virtual void Dispose (bool disposing)=0A=
		{=0A=
			if (disposed =3D=3D false) {=0A=
				if (active) {=0A=
					stream.Close();=0A=
					active =3D false;=0A=
				}=0A=
				disposed =3D true;=0A=
			}=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Destructor - just calls Dispose()
		/// </summary>=0A=
		~TcpClient ()=0A=
		{=0A=
			Dispose(false);=0A=
		}=0A=
		=0A=
		/// <returns>A NetworkStream object connected to the=0A=
		/// connection socket</returns>=0A=
		public NetworkStream GetStream()=0A=
		{=0A=
			return stream;=0A=
		}=0A=
=0A=
	}=0A=
}=0A=

------=_NextPart_000_0075_01C172EC.3C8F09E0
Content-Type: text/plain;
	name="TcpListenerTest.cs"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="TcpListenerTest.cs"

// System.Net.Sockets.TcpListenerTest.cs
//
// Author:=0A=
//    Phillip Pearson (pp@myelin.co.nz)=0A=
//=0A=
// Copyright (C) 2001, Phillip Pearson=0A=
//    http://www.myelin.co.nz=0A=
//=0A=

using System;
using System.Net;
using System.Net.Sockets;
using NUnit.Framework;

namespace MonoTests.System.Net.Sockets {

	/// <summary>
	/// Tests System.Net.Sockets.TcpListener
	/// </summary>
	public class TcpListenerTest : TestCase {
	=09
		public TcpListenerTest(string name) : base(name) {}

		public static ITest Suite {
			get {
				return new TestSuite(typeof (TcpListenerTest));
			}
		}

		/// <summary>
		/// Tests the TcpListener object
		/// (from System.Net.Sockets)
		/// </summary>
		public void test_TcpListener()
		{
			// listen with a new listener
			TcpListener inListener =3D new TcpListener(1234);
			inListener.Start();
		=09

			// connect to it from a new socket
			Socket outSock =3D new Socket(AddressFamily.InterNetwork, =
SocketType.Stream,
				ProtocolType.IP);
			IPHostEntry hostent =3D Dns.GetHostByAddress("127.0.0.1");
			IPEndPoint remote =3D new IPEndPoint(hostent.AddressList[0], 1234);
			outSock.Connect(remote);

		=09
			// make sure the connection arrives
			Assert(inListener.Pending());
			Socket inSock =3D inListener.AcceptSocket();


			// now send some data and see if it comes out the other end
			const int len =3D 1024;
			byte[] outBuf =3D new Byte[len];
			for (int i=3D0; i<len; i++)=20
			{
				outBuf[i] =3D (byte)(i % 256);
			}

			outSock.Send(outBuf, 0, len, 0);

			byte[] inBuf =3D new Byte[len];
			int ret =3D inSock.Receive(inBuf, 0, len, 0);


			// let's see if it arrived OK
			Assert(ret !=3D 0);
			for (int i=3D0; i<len; i++)=20
			{
				Assert(inBuf[i] =3D=3D outBuf[i]);
			}


			// tidy up after ourselves
			inSock.Close();

			inListener.Stop();=09
		}
=09
	=09
	}

}

------=_NextPart_000_0075_01C172EC.3C8F09E0
Content-Type: text/plain;
	name="TcpListener.cs"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="TcpListener.cs"

// System.Net.Sockets.TcpListener.cs=0A=
//=0A=
// Author:=0A=
//    Phillip Pearson (pp@myelin.co.nz)=0A=
//=0A=
// Copyright (C) 2001, Phillip Pearson=0A=
//    http://www.myelin.co.nz=0A=
//=0A=
=0A=
// NB: This is untested (probably buggy) code - take care using it=0A=
=0A=
using System;=0A=
using System.Net;=0A=
=0A=
namespace System.Net.Sockets=0A=
{=0A=
	/// <remarks>=0A=
	/// A slightly more abstracted way to listen for incoming=0A=
	/// network connections than a Socket.=0A=
	/// </remarks>=0A=
	public class TcpListener=0A=
	{=0A=
		// private data=0A=
		=0A=
		private bool active;=0A=
		private Socket server;=0A=
		=0A=
		// constructor=0A=
=0A=
		/* TODO: Code common to all the constructors goes here.  I can't=0A=
		call a constructor from another constructor, for some=0A=
		reason.  Why? */=0A=
		=0A=
		/// <summary>
		/// Some code that is shared between the constructors.
		/// </summary>=0A=
		private void common_constructor ()=0A=
		{=0A=
			active =3D false;=0A=
			server =3D new Socket(AddressFamily.InterNetwork,=0A=
				SocketType.Stream, ProtocolType.Tcp);=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Constructs a new TcpListener to listen on a specified port
		/// </summary>
		/// <param name=3D"port">The port to listen on, e.g. 80 if you=20
		/// are a web server</param>
		public TcpListener (int port)=0A=
		{=0A=
			common_constructor();=0A=
			server.Bind(new IPEndPoint(IPAddress.Any, port));=0A=
		}=0A=
=0A=
		/// <summary>
		/// Constructs a new TcpListener with a specified local endpoint
		/// </summary>
		/// <param name=3D"local_end_point">The endpoint</param>
		public TcpListener (IPEndPoint local_end_point)=0A=
		{=0A=
			common_constructor();=0A=
			server.Bind(local_end_point);=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Constructs a new TcpListener, listening on a specified port
		/// and IP (for use on a multi-homed machine)
		/// </summary>
		/// <param name=3D"listen_ip">The IP to listen on</param>
		/// <param name=3D"port">The port to listen on</param>=0A=
		public TcpListener (IPAddress listen_ip, int port)=0A=
		{=0A=
			common_constructor();=0A=
			server.Bind(new IPEndPoint(listen_ip, port));=0A=
		}=0A=
=0A=
=0A=
		// properties=0A=
=0A=
		/// <summary>
		/// A flag that is 'true' if the TcpListener is listening,
		/// or 'false' if it is not listening
		/// </summary>=0A=
		protected bool Active=0A=
		{=0A=
			get { return active; }=0A=
		}=0A=
=0A=
		/// <summary>
		/// The local end point
		/// </summary>=0A=
		public EndPoint LocalEndPoint=0A=
		{=0A=
			get { return server.LocalEndPoint; }=0A=
		}=0A=
		=0A=
		/// <summary>
		/// The listening socket
		/// </summary>=0A=
		protected Socket Server=0A=
		{=0A=
			get { return server; }=0A=
		}=0A=
		=0A=
		=0A=
		// methods=0A=
=0A=
		/// <summary>
		/// Accepts a pending connection=0A=
		/// <returns>A Socket object for the new connection</returns>=0A=
		public Socket AcceptSocket ()=0A=
		{=0A=
			return server.Accept();=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Accepts a pending connection
		/// </summary>
		/// <returns>A TcpClient
		/// object made from the new socket.</returns>=0A=
		public TcpClient AcceptTcpClient ()=0A=
		{=0A=
			/*	TODO: How do we set the socket in the new client,=0A=
				without having tcpclient as our base class?=0A=
				Does C# have something like 'friend'?=0A=
				=0A=
				The commented code below doesn't work because=0A=
				TcpClient.Client is protected.  If we derive=0A=
				=0A=
			*/=0A=
=0A=
			TcpClient client =3D new TcpClient();=0A=
			client.SetTcpClient(AcceptSocket());=0A=
			return client;=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Destructor - stops the listener listening
		/// </summary>=0A=
		~TcpListener ()=0A=
		{=0A=
			if (active =3D=3D true) {=0A=
				Stop();=0A=
			}=0A=
		}=0A=
	=0A=
		/// <returns>
		/// Returns 'true' if there is a connection waiting to be accepted
		/// with AcceptSocket() or AcceptTcpClient().
		/// </returns>=0A=
		public bool Pending ()=0A=
		{=0A=
			return server.Poll(1000, SelectMode.SelectRead);=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Tells the TcpListener to start listening.
		/// </summary>=0A=
		public void Start ()=0A=
		{=0A=
			server.Listen(-1);	//TODO: How big a backlog should we specify?  -1 =
=3D=3D MAX?=0A=
			active =3D true;=0A=
		}=0A=
		=0A=
		/// <summary>
		/// Tells the TcpListener to stop listening and dispose
		/// of all managed resources.
		/// </summary>=0A=
		public void Stop ()=0A=
		{=0A=
			server.Close();=0A=
		}=0A=
=0A=
	}=0A=
}=0A=

------=_NextPart_000_0075_01C172EC.3C8F09E0
Content-Type: text/plain;
	name="TcpClientTest.cs"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
	filename="TcpClientTest.cs"

// System.Net.Sockets.TcpClientTest.cs
//
// Author:=0A=
//    Phillip Pearson (pp@myelin.co.nz)=0A=
//=0A=
// Copyright (C) 2001, Phillip Pearson=0A=
//    http://www.myelin.co.nz=0A=
//=0A=

using System;
using System.Net;
using System.Net.Sockets;
using NUnit.Framework;

namespace MonoTests.System.Net.Sockets {

	/// <summary>
	/// Tests System.Net.Sockets.TcpClient
	/// </summary>
	public class TcpClientTest : TestCase {
	=09
		public TcpClientTest(string name) : base(name) {}
	=09
		public static ITest Suite {
			get {
				return new TestSuite(typeof (TcpClientTest));
			}
		}

		/// <summary>
		/// Tests the TcpClient object
		/// (from System.Net.Sockets)
		/// </summary>
		public void test_TcpClient()
		{
			// set up a listening Socket
			Socket lSock =3D new Socket(AddressFamily.InterNetwork,
				SocketType.Stream, ProtocolType.Tcp);
		=09
			lSock.Bind(new IPEndPoint(IPAddress.Any, 1234));
			lSock.Listen(-1);


			// connect to it with a TcpClient
			TcpClient outClient =3D new TcpClient("localhost", 1234);
			Socket inSock =3D lSock.Accept();

		=09
			// now try exchanging data
			NetworkStream stream =3D outClient.GetStream();

			const int len =3D 1024;
			byte[] outBuf =3D new Byte[len];
			for (int i=3D0; i<len; i++)=20
			{
				outBuf[i] =3D (byte)(i % 256);
			}

			// send it
			stream.Write(outBuf,0,len);

			// and see if it comes back
			byte[] inBuf =3D new Byte[len];
			int ret =3D inSock.Receive(inBuf, 0, len, 0);
			Assert(ret !=3D 0);

			for (int i=3D0; i<len; i++)=20
			{
				Assert(inBuf[i] =3D=3D outBuf[i]);
			}
		=09

			// tidy up
			inSock.Close();
			outClient.Close();
			lSock.Close();
		=09
		}=09
	=09
	}

}

------=_NextPart_000_0075_01C172EC.3C8F09E0--