blob: 40469ab40ce5d6cdc0d87d70ba3b0d72cb081a24 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* Contains some contributions under the Thrift Software License.
* Please see doc/old-thrift-license.txt in the Thrift distribution for
* details.
*/
/* only for silverlight */
#if SILVERLIGHT
using System;
using System.Net.Sockets;
using System.IO;
using System.Net;
using System.Threading;
namespace Thrift.Transport
{
public class TSilverlightSocket : TTransport
{
Socket socket = null;
static ManualResetEvent readAsyncComplete = new ManualResetEvent(false);
public event EventHandler<SocketAsyncEventArgs> connectHandler = null;
// memory stream for write cache.
private MemoryStream outputStream = new MemoryStream();
private string host = null;
private int port = 0;
private int timeout = 0;
// constructor
public TSilverlightSocket(string host, int port)
: this(host, port, 0)
{
}
// constructor
public TSilverlightSocket(string host, int port, int timeout)
{
this.host = host;
this.port = port;
this.timeout = timeout;
InitSocket();
}
private void InitSocket()
{
// Create a stream-based, TCP socket using the InterNetwork Address Family.
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;
}
public int Timeout
{
set
{
timeout = value;
}
}
public string Host
{
get
{
return host;
}
}
public int Port
{
get
{
return port;
}
}
public override bool IsOpen
{
get
{
if (socket == null)
{
return false;
}
return socket.Connected;
}
}
public override void Open()
{
if (IsOpen)
{
throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected");
}
if (string.IsNullOrEmpty(host))
{
throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host");
}
if (port <= 0)
{
throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port");
}
if (socket == null)
{
InitSocket();
}
if (timeout == 0) // no timeout -> infinite
{
timeout = 10000; // set a default timeout for WP.
}
{
// Create DnsEndPoint. The hostName and port are passed in to this method.
DnsEndPoint hostEntry = new DnsEndPoint(this.host, this.port);
// Create a SocketAsyncEventArgs object to be used in the connection request
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = hostEntry;
// Inline event handler for the Completed event.
// Note: This event handler was implemented inline in order to make this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
if (connectHandler != null)
{
connectHandler(this, e);
}
});
// Make an asynchronous Connect request over the socket
socket.ConnectAsync(socketEventArg);
}
}
public override int Read(byte[] buf, int off, int len)
{
bool _timeout = true;
string _error = null;
int _recvBytes = -1;
if (socket == null)
{
throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Socket is not open");
}
// Create SocketAsyncEventArgs context object
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = socket.RemoteEndPoint;
// Setup the buffer to receive the data
socketEventArg.SetBuffer(buf, off, len);
// Inline event handler for the Completed event.
// Note: This even handler was implemented inline in order to make
// this method self-contained.
socketEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(delegate(object s, SocketAsyncEventArgs e)
{
_timeout = false;
if (e.SocketError == SocketError.Success)
{
_recvBytes = e.BytesTransferred;
}
else
{
_error = e.SocketError.ToString();
}
readAsyncComplete.Set();
});
// Sets the state of the event to nonsignaled, causing threads to block
readAsyncComplete.Reset();
// Make an asynchronous Receive request over the socket
socket.ReceiveAsync(socketEventArg);
// Block the UI thread for a maximum of TIMEOUT_MILLISECONDS milliseconds.
// If no response comes back within this time then proceed
readAsyncComplete.WaitOne(this.timeout);
if (_timeout)
{
throw new TTransportException(TTransportException.ExceptionType.TimedOut, "Socket recv timeout");
}
if (_error != null)
{
throw new TTransportException(TTransportException.ExceptionType.Unknown, _error);
}
return _recvBytes;
}
public override void Write(byte[] buf, int off, int len)
{
outputStream.Write(buf, off, len);
}
private void beginFlush_Completed(object sender, SocketAsyncEventArgs e)
{
FlushAsyncResult flushAsyncResult = e.UserToken as FlushAsyncResult;
flushAsyncResult.UpdateStatusToComplete();
flushAsyncResult.NotifyCallbackWhenAvailable();
if (e.SocketError != SocketError.Success)
{
throw new TTransportException(TTransportException.ExceptionType.Unknown, e.SocketError.ToString());
}
}
public override IAsyncResult BeginFlush(AsyncCallback callback, object state)
{
// Extract request and reset buffer
byte[] data = outputStream.ToArray();
FlushAsyncResult flushAsyncResult = new FlushAsyncResult(callback, state);
SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
socketEventArg.RemoteEndPoint = socket.RemoteEndPoint;
socketEventArg.UserToken = flushAsyncResult;
socketEventArg.Completed += beginFlush_Completed;
socketEventArg.SetBuffer(data, 0, data.Length);
socket.SendAsync(socketEventArg);
return flushAsyncResult;
}
public override void EndFlush(IAsyncResult asyncResult)
{
try
{
var flushAsyncResult = (FlushAsyncResult)asyncResult;
if (!flushAsyncResult.IsCompleted)
{
var waitHandle = flushAsyncResult.AsyncWaitHandle;
waitHandle.WaitOne();
waitHandle.Close();
}
if (flushAsyncResult.AsyncException != null)
{
throw flushAsyncResult.AsyncException;
}
}
finally
{
outputStream = new MemoryStream();
}
}
// Copy from impl from THttpClient.cs
// Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx
class FlushAsyncResult : IAsyncResult
{
private volatile Boolean _isCompleted;
private ManualResetEvent _evt;
private readonly AsyncCallback _cbMethod;
private readonly object _state;
public FlushAsyncResult(AsyncCallback cbMethod, object state)
{
_cbMethod = cbMethod;
_state = state;
}
internal byte[] Data { get; set; }
internal Socket Connection { get; set; }
internal TTransportException AsyncException { get; set; }
public object AsyncState
{
get { return _state; }
}
public WaitHandle AsyncWaitHandle
{
get { return GetEvtHandle(); }
}
public bool CompletedSynchronously
{
get { return false; }
}
public bool IsCompleted
{
get { return _isCompleted; }
}
private readonly object _locker = new object();
private ManualResetEvent GetEvtHandle()
{
lock (_locker)
{
if (_evt == null)
{
_evt = new ManualResetEvent(false);
}
if (_isCompleted)
{
_evt.Set();
}
}
return _evt;
}
internal void UpdateStatusToComplete()
{
_isCompleted = true; //1. set _iscompleted to true
lock (_locker)
{
if (_evt != null)
{
_evt.Set(); //2. set the event, when it exists
}
}
}
internal void NotifyCallbackWhenAvailable()
{
if (_cbMethod != null)
{
_cbMethod(this);
}
}
}
public override void Close()
{
if (socket != null)
{
socket.Close();
socket = null;
}
}
#region " IDisposable Support "
private bool _IsDisposed;
// IDisposable
protected override void Dispose(bool disposing)
{
if (!_IsDisposed)
{
if (disposing)
{
if (outputStream != null)
{
outputStream.Dispose();
}
outputStream = null;
if (socket != null)
{
((IDisposable)socket).Dispose();
}
}
}
_IsDisposed = true;
}
#endregion
}
}
#endif