C# WCF性能、延迟和可扩展性
我正在尝试将一个简单的异步TCP服务器从F#移植到C#4。服务器接收连接,读取单个请求,并在关闭连接之前返回一系列响应 C#4中的异步看起来单调乏味而且容易出错,所以我想我应该尝试使用WCF。这台服务器不太可能在野外同时看到1000个请求,因此我认为吞吐量和延迟都很重要 我已经用C#编写了一个最小双工WCF web服务和控制台客户端。虽然我使用的是WCF而不是原始套接字,但这已经是175行代码,而原始套接字的代码是80行。但我更关心的是性能和可扩展性:C# WCF性能、延迟和可扩展性,c#,wcf,f#,tcp,C#,Wcf,F#,Tcp,我正在尝试将一个简单的异步TCP服务器从F#移植到C#4。服务器接收连接,读取单个请求,并在关闭连接之前返回一系列响应 C#4中的异步看起来单调乏味而且容易出错,所以我想我应该尝试使用WCF。这台服务器不太可能在野外同时看到1000个请求,因此我认为吞吐量和延迟都很重要 我已经用C#编写了一个最小双工WCF web服务和控制台客户端。虽然我使用的是WCF而不是原始套接字,但这已经是175行代码,而原始套接字的代码是80行。但我更关心的是性能和可扩展性: WCF的延迟为154倍 WCF的吞吐量是
- WCF的延迟为154倍
- WCF的吞吐量是54倍
- TCP可以轻松处理1000个同时连接,但WCF只能处理20个
IService1.cs
[DataContract]
public class Stock
{
[DataMember]
public DateTime FirstDealDate { get; set; }
[DataMember]
public DateTime LastDealDate { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public DateTime EndDate { get; set; }
[DataMember]
public decimal Open { get; set; }
[DataMember]
public decimal High { get; set; }
[DataMember]
public decimal Low { get; set; }
[DataMember]
public decimal Close { get; set; }
[DataMember]
public decimal VolumeWeightedPrice { get; set; }
[DataMember]
public decimal TotalQuantity { get; set; }
}
[ServiceContract(CallbackContract = typeof(IPutStock))]
public interface IStock
{
[OperationContract]
void GetStocks();
}
public interface IPutStock
{
[OperationContract]
void PutStock(Stock stock);
}
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Stocks : IStock
{
IPutStock callback;
#region IStock Members
public void GetStocks()
{
callback = OperationContext.Current.GetCallbackChannel<IPutStock>();
Stock st = null;
st = new Stock
{
FirstDealDate = System.DateTime.Now,
LastDealDate = System.DateTime.Now,
StartDate = System.DateTime.Now,
EndDate = System.DateTime.Now,
Open = 495,
High = 495,
Low = 495,
Close = 495,
VolumeWeightedPrice = 495,
TotalQuantity = 495
};
for (int i=0; i<1000; ++i)
callback.PutStock(st);
}
#endregion
}
[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
class Callback : DuplexWcfService2.IStockCallback
{
System.Diagnostics.Stopwatch timer;
int n;
public Callback(System.Diagnostics.Stopwatch t)
{
timer = t;
n = 0;
}
public void PutStock(DuplexWcfService2.Stock st)
{
++n;
if (n == 1)
Console.WriteLine("First result in " + this.timer.Elapsed.TotalSeconds + "s");
if (n == 1000)
Console.WriteLine("1,000 results in " + this.timer.Elapsed.TotalSeconds + "s");
}
}
class Program
{
static void Test(int i)
{
var timer = System.Diagnostics.Stopwatch.StartNew();
var ctx = new InstanceContext(new Callback(timer));
var proxy = new DuplexWcfService2.StockClient(ctx);
proxy.GetStocks();
Console.WriteLine(i + " connected");
}
static void Main(string[] args)
{
for (int i=0; i<10; ++i)
{
int j = i;
new System.Threading.Thread(() => Test(j)).Start();
}
}
}
Service1.svc
<%@ ServiceHost Language="C#" Debug="true" Service="DuplexWcfService2.Stocks" CodeBehind="Service1.svc.cs" %>
Web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<services>
<service name="DuplexWcfService2.Stocks">
<endpoint address="" binding="wsDualHttpBinding" contract="DuplexWcfService2.IStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
下面是我在F#中的异步TCP客户端和服务器代码:
首先回答第二个问题,与原始套接字相比,WCF总是有开销。但与原始套接字相比,它有大量的功能(如安全性、可靠性、互操作性、多种传输协议、跟踪等),权衡是否可接受取决于您的场景。看起来您正在做一些金融交易应用程序,WCF可能不适合您的情况(尽管我在金融行业没有经验证明这一点)
对于第一个问题,请尝试在客户机中托管一个单独的WCF服务,而不是双http绑定,这样客户机本身就可以成为一个服务,如果可能,请使用netTCP绑定。在服务行为中调整serviceThrottling元素中的属性。默认值低于.Net 4。WCF为几乎所有默认值选择非常安全的值。这遵循了“不要让新手开发人员开枪自杀”的理念。但是,如果您知道要更改的限制和要使用的绑定,则可以获得合理的性能和扩展 在我的核心i5-2400(四核,无超线程,3.10 GHz)上,下面的解决方案将运行1000个客户端,每个客户端有1000个回调,平均总运行时间为20秒。这意味着20秒内有1000000个WCF呼叫。 不幸的是,我无法让你的F#程序运行以进行直接比较。如果你在你的盒子上运行我的解决方案,你能发布一些F与C WCF的性能比较数字吗
免责声明:以下内容旨在证明概念。有些设置对生产没有意义 我所做的:
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Diagnostics;
using System.Threading;
using System.Runtime.Serialization;
namespace StockApp
{
[DataContract]
public class Stock
{
[DataMember]
public DateTime FirstDealDate { get; set; }
[DataMember]
public DateTime LastDealDate { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public DateTime EndDate { get; set; }
[DataMember]
public decimal Open { get; set; }
[DataMember]
public decimal High { get; set; }
[DataMember]
public decimal Low { get; set; }
[DataMember]
public decimal Close { get; set; }
[DataMember]
public decimal VolumeWeightedPrice { get; set; }
[DataMember]
public decimal TotalQuantity { get; set; }
}
[ServiceContract]
public interface IStock
{
[OperationContract(IsOneWay = true)]
void GetStocks(string address);
}
[ServiceContract]
public interface IPutStock
{
[OperationContract(IsOneWay = true)]
void PutStock(Stock stock);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class StocksService : IStock
{
public void SendStocks(object obj)
{
string address = (string)obj;
ChannelFactory<IPutStock> factory = new ChannelFactory<IPutStock>("CallbackClientEndpoint");
IPutStock callback = factory.CreateChannel(new EndpointAddress(address));
Stock st = null; st = new Stock
{
FirstDealDate = System.DateTime.Now,
LastDealDate = System.DateTime.Now,
StartDate = System.DateTime.Now,
EndDate = System.DateTime.Now,
Open = 495,
High = 495,
Low = 495,
Close = 495,
VolumeWeightedPrice = 495,
TotalQuantity = 495
};
for (int i = 0; i < 1000; ++i)
callback.PutStock(st);
//Console.WriteLine("Done calling {0}", address);
((ICommunicationObject)callback).Shutdown();
factory.Shutdown();
}
public void GetStocks(string address)
{
/// WCF service methods execute on IO threads.
/// Passing work off to worker thread improves service responsiveness... with a measurable cost in total runtime.
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(SendStocks), address);
// SendStocks(address);
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class Callback : IPutStock
{
public static int CallbacksCompleted = 0;
System.Diagnostics.Stopwatch timer = Stopwatch.StartNew();
int n = 0;
public void PutStock(Stock st)
{
++n;
if (n == 1000)
{
//Console.WriteLine("1,000 results in " + this.timer.Elapsed.TotalSeconds + "s");
int compelted = Interlocked.Increment(ref CallbacksCompleted);
if (compelted % 100 == 0)
{
Console.WriteLine("Client #{0} completed 1,000 results in {1} s", compelted, this.timer.Elapsed.TotalSeconds);
if (compelted == Program.CLIENT_COUNT)
{
Console.WriteLine("ALL DONE. Total number of clients: {0} Total runtime: {1} msec", Program.CLIENT_COUNT, Program.ProgramTimer.ElapsedMilliseconds);
}
}
}
}
}
class Program
{
public const int CLIENT_COUNT = 1000; // TEST WITH DIFFERENT VALUES
public static System.Diagnostics.Stopwatch ProgramTimer;
static void StartCallPool(object uriObj)
{
string callbackUri = (string)uriObj;
ChannelFactory<IStock> factory = new ChannelFactory<IStock>("StockClientEndpoint");
IStock proxy = factory.CreateChannel();
proxy.GetStocks(callbackUri);
((ICommunicationObject)proxy).Shutdown();
factory.Shutdown();
}
static void Test()
{
ThreadPool.SetMinThreads(CLIENT_COUNT, CLIENT_COUNT * 2);
// Create all the hosts that will recieve call backs.
List<ServiceHost> callBackHosts = new List<ServiceHost>();
for (int i = 0; i < CLIENT_COUNT; ++i)
{
string port = string.Format("{0}", i).PadLeft(3, '0');
string baseAddress = "net.tcp://localhost:7" + port + "/";
ServiceHost callbackHost = new ServiceHost(typeof(Callback), new Uri[] { new Uri( baseAddress)});
callbackHost.Open();
callBackHosts.Add(callbackHost);
}
Console.WriteLine("All client hosts open.");
ServiceHost stockHost = new ServiceHost(typeof(StocksService));
stockHost.Open();
Console.WriteLine("Service Host opened. Starting timer...");
ProgramTimer = Stopwatch.StartNew();
foreach (var callbackHost in callBackHosts)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(StartCallPool), callbackHost.BaseAddresses[0].AbsoluteUri);
}
Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
Console.ReadLine();
foreach (var h in callBackHosts)
h.Shutdown();
stockHost.Shutdown();
}
static void Main(string[] args)
{
Test();
}
}
public static class Extensions
{
static public void Shutdown(this ICommunicationObject obj)
{
try
{
obj.Close();
}
catch (Exception ex)
{
Console.WriteLine("Shutdown exception: {0}", ex.Message);
obj.Abort();
}
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="StockApp.StocksService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8123/StockApp/"/>
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpConfig" contract="StockApp.IStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
<service name="StockApp.Callback">
<host>
<baseAddresses>
<!-- Base address defined at runtime. -->
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpConfig" contract="StockApp.IPutStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
</services>
<client>
<endpoint name="StockClientEndpoint"
address="net.tcp://localhost:8123/StockApp/"
binding="netTcpBinding"
bindingConfiguration="tcpConfig"
contract="StockApp.IStock" >
</endpoint>
<!-- CallbackClientEndpoint address defined at runtime. -->
<endpoint name="CallbackClientEndpoint"
binding="netTcpBinding"
bindingConfiguration="tcpConfig"
contract="StockApp.IPutStock" >
</endpoint>
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<!--<serviceMetadata httpGetEnabled="true"/>-->
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceThrottling maxConcurrentCalls="1000" maxConcurrentSessions="1000" maxConcurrentInstances="1000" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="tcpConfig" listenBacklog="100" maxConnections="1000">
<security mode="None"/>
<reliableSession enabled="false" />
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
- 删除了双工绑定,并让客户端创建自己的绑定 服务主机接收回调。这基本上是一个 双面装订在发动机罩下进行。(这也是普拉蒂克的 (建议)
- 将绑定更改为netTcpBinding李>
- 更改的节流值:
- WCF:maxConcurrentCalls、maxConcurrentSessions、, 总共1000人
- :maxConnections=1000
- 线程池:最小工作线程数=1000,最小IO线程数=2000
- 添加到服务操作中
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Diagnostics;
using System.Threading;
using System.Runtime.Serialization;
namespace StockApp
{
[DataContract]
public class Stock
{
[DataMember]
public DateTime FirstDealDate { get; set; }
[DataMember]
public DateTime LastDealDate { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public DateTime EndDate { get; set; }
[DataMember]
public decimal Open { get; set; }
[DataMember]
public decimal High { get; set; }
[DataMember]
public decimal Low { get; set; }
[DataMember]
public decimal Close { get; set; }
[DataMember]
public decimal VolumeWeightedPrice { get; set; }
[DataMember]
public decimal TotalQuantity { get; set; }
}
[ServiceContract]
public interface IStock
{
[OperationContract(IsOneWay = true)]
void GetStocks(string address);
}
[ServiceContract]
public interface IPutStock
{
[OperationContract(IsOneWay = true)]
void PutStock(Stock stock);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class StocksService : IStock
{
public void SendStocks(object obj)
{
string address = (string)obj;
ChannelFactory<IPutStock> factory = new ChannelFactory<IPutStock>("CallbackClientEndpoint");
IPutStock callback = factory.CreateChannel(new EndpointAddress(address));
Stock st = null; st = new Stock
{
FirstDealDate = System.DateTime.Now,
LastDealDate = System.DateTime.Now,
StartDate = System.DateTime.Now,
EndDate = System.DateTime.Now,
Open = 495,
High = 495,
Low = 495,
Close = 495,
VolumeWeightedPrice = 495,
TotalQuantity = 495
};
for (int i = 0; i < 1000; ++i)
callback.PutStock(st);
//Console.WriteLine("Done calling {0}", address);
((ICommunicationObject)callback).Shutdown();
factory.Shutdown();
}
public void GetStocks(string address)
{
/// WCF service methods execute on IO threads.
/// Passing work off to worker thread improves service responsiveness... with a measurable cost in total runtime.
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(SendStocks), address);
// SendStocks(address);
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class Callback : IPutStock
{
public static int CallbacksCompleted = 0;
System.Diagnostics.Stopwatch timer = Stopwatch.StartNew();
int n = 0;
public void PutStock(Stock st)
{
++n;
if (n == 1000)
{
//Console.WriteLine("1,000 results in " + this.timer.Elapsed.TotalSeconds + "s");
int compelted = Interlocked.Increment(ref CallbacksCompleted);
if (compelted % 100 == 0)
{
Console.WriteLine("Client #{0} completed 1,000 results in {1} s", compelted, this.timer.Elapsed.TotalSeconds);
if (compelted == Program.CLIENT_COUNT)
{
Console.WriteLine("ALL DONE. Total number of clients: {0} Total runtime: {1} msec", Program.CLIENT_COUNT, Program.ProgramTimer.ElapsedMilliseconds);
}
}
}
}
}
class Program
{
public const int CLIENT_COUNT = 1000; // TEST WITH DIFFERENT VALUES
public static System.Diagnostics.Stopwatch ProgramTimer;
static void StartCallPool(object uriObj)
{
string callbackUri = (string)uriObj;
ChannelFactory<IStock> factory = new ChannelFactory<IStock>("StockClientEndpoint");
IStock proxy = factory.CreateChannel();
proxy.GetStocks(callbackUri);
((ICommunicationObject)proxy).Shutdown();
factory.Shutdown();
}
static void Test()
{
ThreadPool.SetMinThreads(CLIENT_COUNT, CLIENT_COUNT * 2);
// Create all the hosts that will recieve call backs.
List<ServiceHost> callBackHosts = new List<ServiceHost>();
for (int i = 0; i < CLIENT_COUNT; ++i)
{
string port = string.Format("{0}", i).PadLeft(3, '0');
string baseAddress = "net.tcp://localhost:7" + port + "/";
ServiceHost callbackHost = new ServiceHost(typeof(Callback), new Uri[] { new Uri( baseAddress)});
callbackHost.Open();
callBackHosts.Add(callbackHost);
}
Console.WriteLine("All client hosts open.");
ServiceHost stockHost = new ServiceHost(typeof(StocksService));
stockHost.Open();
Console.WriteLine("Service Host opened. Starting timer...");
ProgramTimer = Stopwatch.StartNew();
foreach (var callbackHost in callBackHosts)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(StartCallPool), callbackHost.BaseAddresses[0].AbsoluteUri);
}
Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
Console.ReadLine();
foreach (var h in callBackHosts)
h.Shutdown();
stockHost.Shutdown();
}
static void Main(string[] args)
{
Test();
}
}
public static class Extensions
{
static public void Shutdown(this ICommunicationObject obj)
{
try
{
obj.Close();
}
catch (Exception ex)
{
Console.WriteLine("Shutdown exception: {0}", ex.Message);
obj.Abort();
}
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="StockApp.StocksService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8123/StockApp/"/>
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpConfig" contract="StockApp.IStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
<service name="StockApp.Callback">
<host>
<baseAddresses>
<!-- Base address defined at runtime. -->
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpConfig" contract="StockApp.IPutStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
</services>
<client>
<endpoint name="StockClientEndpoint"
address="net.tcp://localhost:8123/StockApp/"
binding="netTcpBinding"
bindingConfiguration="tcpConfig"
contract="StockApp.IStock" >
</endpoint>
<!-- CallbackClientEndpoint address defined at runtime. -->
<endpoint name="CallbackClientEndpoint"
binding="netTcpBinding"
bindingConfiguration="tcpConfig"
contract="StockApp.IPutStock" >
</endpoint>
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<!--<serviceMetadata httpGetEnabled="true"/>-->
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceThrottling maxConcurrentCalls="1000" maxConcurrentSessions="1000" maxConcurrentInstances="1000" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="tcpConfig" listenBacklog="100" maxConnections="1000">
<security mode="None"/>
<reliableSession enabled="false" />
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
- 当客户端收到“由于目标计算机主动拒绝而无法建立连接”异常时
- 可能的原因:
- 已达到WCF限制
- 已达到TCP限制
- 没有可用于处理调用的I/O线程李>
- #3的解决方案是:
- 增加最小IO线程数-或-
- 让StockService在工作线程上执行回调(这会增加总运行时间)
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Diagnostics;
using System.Threading;
using System.Runtime.Serialization;
namespace StockApp
{
[DataContract]
public class Stock
{
[DataMember]
public DateTime FirstDealDate { get; set; }
[DataMember]
public DateTime LastDealDate { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public DateTime EndDate { get; set; }
[DataMember]
public decimal Open { get; set; }
[DataMember]
public decimal High { get; set; }
[DataMember]
public decimal Low { get; set; }
[DataMember]
public decimal Close { get; set; }
[DataMember]
public decimal VolumeWeightedPrice { get; set; }
[DataMember]
public decimal TotalQuantity { get; set; }
}
[ServiceContract]
public interface IStock
{
[OperationContract(IsOneWay = true)]
void GetStocks(string address);
}
[ServiceContract]
public interface IPutStock
{
[OperationContract(IsOneWay = true)]
void PutStock(Stock stock);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class StocksService : IStock
{
public void SendStocks(object obj)
{
string address = (string)obj;
ChannelFactory<IPutStock> factory = new ChannelFactory<IPutStock>("CallbackClientEndpoint");
IPutStock callback = factory.CreateChannel(new EndpointAddress(address));
Stock st = null; st = new Stock
{
FirstDealDate = System.DateTime.Now,
LastDealDate = System.DateTime.Now,
StartDate = System.DateTime.Now,
EndDate = System.DateTime.Now,
Open = 495,
High = 495,
Low = 495,
Close = 495,
VolumeWeightedPrice = 495,
TotalQuantity = 495
};
for (int i = 0; i < 1000; ++i)
callback.PutStock(st);
//Console.WriteLine("Done calling {0}", address);
((ICommunicationObject)callback).Shutdown();
factory.Shutdown();
}
public void GetStocks(string address)
{
/// WCF service methods execute on IO threads.
/// Passing work off to worker thread improves service responsiveness... with a measurable cost in total runtime.
System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(SendStocks), address);
// SendStocks(address);
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class Callback : IPutStock
{
public static int CallbacksCompleted = 0;
System.Diagnostics.Stopwatch timer = Stopwatch.StartNew();
int n = 0;
public void PutStock(Stock st)
{
++n;
if (n == 1000)
{
//Console.WriteLine("1,000 results in " + this.timer.Elapsed.TotalSeconds + "s");
int compelted = Interlocked.Increment(ref CallbacksCompleted);
if (compelted % 100 == 0)
{
Console.WriteLine("Client #{0} completed 1,000 results in {1} s", compelted, this.timer.Elapsed.TotalSeconds);
if (compelted == Program.CLIENT_COUNT)
{
Console.WriteLine("ALL DONE. Total number of clients: {0} Total runtime: {1} msec", Program.CLIENT_COUNT, Program.ProgramTimer.ElapsedMilliseconds);
}
}
}
}
}
class Program
{
public const int CLIENT_COUNT = 1000; // TEST WITH DIFFERENT VALUES
public static System.Diagnostics.Stopwatch ProgramTimer;
static void StartCallPool(object uriObj)
{
string callbackUri = (string)uriObj;
ChannelFactory<IStock> factory = new ChannelFactory<IStock>("StockClientEndpoint");
IStock proxy = factory.CreateChannel();
proxy.GetStocks(callbackUri);
((ICommunicationObject)proxy).Shutdown();
factory.Shutdown();
}
static void Test()
{
ThreadPool.SetMinThreads(CLIENT_COUNT, CLIENT_COUNT * 2);
// Create all the hosts that will recieve call backs.
List<ServiceHost> callBackHosts = new List<ServiceHost>();
for (int i = 0; i < CLIENT_COUNT; ++i)
{
string port = string.Format("{0}", i).PadLeft(3, '0');
string baseAddress = "net.tcp://localhost:7" + port + "/";
ServiceHost callbackHost = new ServiceHost(typeof(Callback), new Uri[] { new Uri( baseAddress)});
callbackHost.Open();
callBackHosts.Add(callbackHost);
}
Console.WriteLine("All client hosts open.");
ServiceHost stockHost = new ServiceHost(typeof(StocksService));
stockHost.Open();
Console.WriteLine("Service Host opened. Starting timer...");
ProgramTimer = Stopwatch.StartNew();
foreach (var callbackHost in callBackHosts)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(StartCallPool), callbackHost.BaseAddresses[0].AbsoluteUri);
}
Console.WriteLine("Press ENTER to close the host once you see 'ALL DONE'.");
Console.ReadLine();
foreach (var h in callBackHosts)
h.Shutdown();
stockHost.Shutdown();
}
static void Main(string[] args)
{
Test();
}
}
public static class Extensions
{
static public void Shutdown(this ICommunicationObject obj)
{
try
{
obj.Close();
}
catch (Exception ex)
{
Console.WriteLine("Shutdown exception: {0}", ex.Message);
obj.Abort();
}
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="StockApp.StocksService">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8123/StockApp/"/>
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpConfig" contract="StockApp.IStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
<service name="StockApp.Callback">
<host>
<baseAddresses>
<!-- Base address defined at runtime. -->
</baseAddresses>
</host>
<endpoint address="" binding="netTcpBinding" bindingConfiguration="tcpConfig" contract="StockApp.IPutStock">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
</services>
<client>
<endpoint name="StockClientEndpoint"
address="net.tcp://localhost:8123/StockApp/"
binding="netTcpBinding"
bindingConfiguration="tcpConfig"
contract="StockApp.IStock" >
</endpoint>
<!-- CallbackClientEndpoint address defined at runtime. -->
<endpoint name="CallbackClientEndpoint"
binding="netTcpBinding"
bindingConfiguration="tcpConfig"
contract="StockApp.IPutStock" >
</endpoint>
</client>
<behaviors>
<serviceBehaviors>
<behavior>
<!--<serviceMetadata httpGetEnabled="true"/>-->
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceThrottling maxConcurrentCalls="1000" maxConcurrentSessions="1000" maxConcurrentInstances="1000" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding name="tcpConfig" listenBacklog="100" maxConnections="1000">
<security mode="None"/>
<reliableSession enabled="false" />
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
使用系统;
使用System.Collections.Generic;
使用System.ServiceModel;
使用系统诊断;
使用系统线程;
使用System.Runtime.Serialization;
名称空间StockApp
{
[数据合同]
公开股
{
[数据成员]
public DateTime FirstDealDate{get;set;}
[数据成员]
公共日期时间LastDealDate{get;set;}
[数据成员]
公共日期时间起始日期{get;set;}
[数据成员]
公共日期时间结束日期{get;set;}
[数据成员]
公共十进制开放{get;set;}
[数据成员]
公共十进制高位{get;set;}
[数据成员]
公共十进制低位{get;set;}
[数据成员]
公共十进制关闭{get;set;}
[数据成员]
公共十进制卷加权价格{get;set;}
[数据成员]
公共十进制总数{get;set;}
}
[服务合同]
公共接口IStock
{
[运营合同(IsOneWay=true)]
void GetStocks(字符串地址);
}
[服务合同]
公共接口IPutStock
{
[运营合同(IsOneWay=true)]
作废库存(库存);
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
公共类股票服务:IStock
{
公共库存(对象obj)
{
字符串地址=(字符串)obj;
渠道工厂