我们正在 Windows Embedded CE 6 平台上开发 .NET CF 3.5 应用程序。我们正在尝试在 .NET 中实现一个小型 (HTTP 1.0) Web 服务器,它应该提供一个 WebApp 并响应简单的 REST 请求。
我们的实现遵循这篇 MSDN 文章中显示的模式:http: //msdn.microsoft.com/en-us/library/aa446537.aspx。我们使用 TCP 监听套接字、异步回调和 BeginAccept、EndAccept、BeginRecieve 和 EndReceive。
侦听端口上的传入连接由异步接受回调处理。(参见http://msdn.microsoft.com/en-us/library/5bb431f9.aspx)。通过调用 EndAccept 方法,在这个异步接受回调中,我们告诉监听端口将连接交给一个新的套接字并释放该端口,以便监听端口可以接受新的传入连接请求。已经接受的请求在一个自己的线程中处理(因为它是在异步回调中处理的)。
我们已经尝试过最小化 BeginAccept 和 EndAccept 之间的时间。因为,在调用 BeginAccept 和 EndAccept 之间的这段时间里,传入的连接请求被放置在侦听套接字的积压队列中。这个队列的长度可以通过所谓的 backlog 参数来配置——这个参数有一个依赖于平台的最大值。如果积压队列已用尽,新的 tcp 连接请求将在三次握手期间被拒绝(客户端/浏览器获取 RST 作为对其 syn 的响应)。
现在我们遇到了一个问题,大多数现代浏览器,如 Firefox、Chrome、Safari,例如使用多达 15 个(或更多)并发连接从服务器加载数据(每个主机的最大并发连接数可以在 Firefox 中配置about:config -> network.http.max-connections-per-server)。当一个页面被加载时,浏览器在需要时建立 15 个连接,这取决于需要加载的资源的数量(例如图像、javascript 或 css 文件)。
.NET CF socket.listen 方法(参见http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.listen.aspx)允许定义积压编号。
根据我们的理解,我们的 backlog 应该大于 15,例如 20 左右,因为所有连接请求都是由浏览器同时触发的,所以我们的小型网络服务器同时受到 15 个连接请求的打击。太小的积压队列大小会导致 TCP 连接中止,因为在侦听套接字可以接受所有传入连接之前,并非所有传入连接都可以排队。在 Firebug 或 Chrome 中,这些请求显示为“已中止”。因此,我们通过 socket.listen(20) 将我们的积压工作增加到 20,并希望一切都会好起来,甚至可以承受最贪婪的浏览器。
问题是,socket.listen() 调用中的 backlog 参数被静默设置为 SOMAXXCON(在我们的例子中最多 5 个连接)。将数字设置得更高则没有效果。当浏览器建立例如 16 个并发套接字连接时,有些会丢失,因为某些套接字根本不适合 5 的积压队列,并且 TCP 连接从网络服务器获得 TCP-RST - 并且缺少一些资源在网页上。
有什么方法可以更改 Windows Embedded CE 6.0 中的 SOMAXXCON?(我们能够更改平台图像 - 使用平台构建器)。还是我们对这件事的理解有误?
我们附上了我们目前正在使用的源代码:
public class StateObject
{
// Client socket.
public Socket workSocket = null;
// Size of receive buffer.
public const int BufferSize = 1024;
// Receive buffer.
public byte[] buffer = new byte[BufferSize];
// Received data string.
public StringBuilder sb = new StringBuilder();
}
public void StartListening()
{
logger.Debug("Started Listening at : " + this.listeninghostIp + ":" + this.listeningport);
IPEndPoint localEP = new IPEndPoint(IPAddress.Parse(this.listeninghostIp), Convert.ToInt32(this.listeningport));
listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(localEP);
listener.Listen(10);
ThreadPool.QueueUserWorkItem(new WaitCallback(CheckForConnections) );
}
public void CheckForConnections()
{
try
{
logger.Debug("listening successfully started! Waiting for incoming connections...");
listener.BeginAccept(new AsyncCallback(acceptCallback), listener);
}
catch (Exception ex)
{
logger.Error("Exception Occured while starting Listening : " + ex.Message.ToString());
}
}
private void acceptCallback(IAsyncResult ar)
{
try
{
Socket listener = (Socket)ar.AsyncState;
listener.BeginAccept(new AsyncCallback(acceptCallback), listener);
Socket handler = listener.EndAccept(ar);
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
logger.Debug("listening socket accepted connection...");
}
catch (Exception ex)
{
logger.Error("Error on acceptCallback. Error: " + ex.Message);
}
public void ReadCallback(IAsyncResult ar)
{
String content = String.Empty;
StateObject state = (StateObject)ar.AsyncState;
Socket handler = state.workSocket;
int bytesRead = handler.EndReceive(ar);
if (bytesRead > 0)
{
state.sb.Append(Encoding.ASCII.GetString(
state.buffer, 0, bytesRead));
}
ClientConnectionFactory.createConnection(ar.AsyncState);
}