以下场景对HTTP有效吗?
- 客户端向服务器发送HTTP标头
- 服务器接收标头并发送HTTP响应标头
- 客户端流HTTP正文(分块传输)
- 服务器接收请求正文块并发送HTTP响应正文块(再次分块传输)
我尝试使用HttpWebRequest和Asp.NetWebApi来实现这一点,但在客户端上出现了这个错误
发生类型为"System.NotSupportedException"的未经处理的异常在System.dll 中
附加信息:流不支持并发IO读取或写操作
客户端
static void Main(string[] args)
{
HttpWebRequest request = WebRequest.Create("http://localhost.fiddler:16462/") as HttpWebRequest;
request.SendChunked = true;
request.ContentType = "application/octet-stream";
request.Method = "POST";
request.AllowWriteStreamBuffering = false;
request.AllowReadStreamBuffering = false;
request.Timeout = 3600000;
var requestStream = request.GetRequestStream();
var responseStream = request.GetResponse().GetResponseStream();
int bufLength = 10 * 1024;
byte[] requestBuffer = new byte[bufLength];
byte[] responseBuffer = new byte[bufLength];
for (int i = 0; i < 1024; ++i)
{
requestStream.Write(requestBuffer, 0, bufLength);
responseStream.Read(responseBuffer, 0, bufLength);
}
requestStream.Close();
responseStream.Close();
}
我验证了TcpClient确实支持同时请求-响应流。不过,很高兴看到HttpWebRequest
也支持这一点。
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace TcpClientTest
{
class Program
{
static void Main(string[] args)
{
TcpClient client = new TcpClient("localhost", 16462);
var stream = client.GetStream();
byte[] buffer = Encoding.UTF8.GetBytes("POST http://localhost:16462/ HTTP/1.1rn");
stream.Write(buffer, 0, buffer.Length);
buffer = Encoding.UTF8.GetBytes("Content-Type: application/octet-streamrn");
stream.Write(buffer, 0, buffer.Length);
buffer = Encoding.UTF8.GetBytes("Host: localhost:16462rn");
stream.Write(buffer, 0, buffer.Length);
buffer = Encoding.UTF8.GetBytes("Transfer-Encoding: chunkedrnrn");
stream.Write(buffer, 0, buffer.Length);
int chunkLen = 128 * 1024;
string chunkSizeStr = chunkLen.ToString("X");
byte[] chunkSizeBytes = Encoding.UTF8.GetBytes(chunkSizeStr + "rn");
buffer = new byte[chunkLen];
for (int i = 0; i < chunkLen; ++i)
{
buffer[i] = (byte)'a';
}
// Start reader thread
var reader = new Thread(() =>
{
byte[] response = new byte[128 * 1024];
int bytesRead = 0;
while ((bytesRead = stream.Read(response, 0, response.Length)) > 0)
{
Console.WriteLine("Read {0} bytes", bytesRead);
}
});
reader.Start();
// Streaming chunks
for (int i = 0; i < 1024 * 1024; ++i)
{
stream.Write(chunkSizeBytes, 0, chunkSizeBytes.Length);
stream.Write(buffer, 0, buffer.Length);
stream.Write(Encoding.UTF8.GetBytes("rn"), 0, 2);
}
buffer = Encoding.UTF8.GetBytes("0rnrn");
stream.Write(buffer, 0, buffer.Length);
reader.Join();
}
}
}
这似乎不是有效的HTTP行为。你可以自定义";类似HTTP的";客户端和服务器,在客户端完成请求之前开始发送响应,但根据HTTP规范,这似乎是无效的:
6 Response
After receiving and interpreting a request message, a server responds
with an HTTP response message.
不过,网络套接字可能是你的爱好!它们允许在HTTP之上进行双向流。https://en.wikipedia.org/wiki/WebSocket
WinHttp不支持在发送整个请求之前接收HTTP响应。我做了一个测试,发现如果所有请求块都没有发送,那么对WinHttpReceiveResponse的调用就会阻塞。这是代码。
// WinHttpTest.cpp : Defines the entry point for the console application.
//
#include <Windows.h>
#include <winhttp.h>
#include <stdio.h>
#include <memory>
#include <string>
#include <iostream>
#pragma comment(lib, "winhttp.lib")
#define SEND_BUFFER_SIZE 128 * 1024
#define RECV_BUFFER_SIZE 128 * 1024
int main()
{
HINTERNET session = 0, connection = 0, request = 0;
bool result = false;
std::unique_ptr<char[]> sendBuffer = std::make_unique<char[]>(SEND_BUFFER_SIZE);
std::unique_ptr<char[]> recvBuffer = std::make_unique<char[]>(RECV_BUFFER_SIZE);
DWORD bytesTransferred = 0;
std::string chunkHeader = "20000rn";
std::string chunkFooter = "0rnrn";
std::string newLine = "rn";
int i = 0;
session = WinHttpOpen(L"Test", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
if (!session)
{
printf("WinHttpOpen failed %dn", GetLastError());
goto cleanup;
}
connection = WinHttpConnect(session, L"localhost", 16462, 0);
if (!connection)
{
printf("WinHttpConnect failed %dn", GetLastError());
goto cleanup;
}
request = WinHttpOpenRequest(connection, L"POST", NULL, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
if (!request)
{
printf("WinHttpOpenRequest failed %dn", GetLastError());
goto cleanup;
}
result = WinHttpSendRequest(request, L"Host: localhost:16462rnTransfer-Encoding: chunkedrnContent-Type: application/octet-streamrn", -1L, WINHTTP_NO_REQUEST_DATA, 0, WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0);
if (!result)
{
printf("WinHttpSendRequest failed %dn", GetLastError());
goto cleanup;
}
// Streaming chunks
for (i = 0; i < 1024; ++i)
{
result = WinHttpWriteData(request, chunkHeader.data(), chunkHeader.length(), &bytesTransferred);
if (!result)
{
printf("WinHttpWriteData ChunkHeader failed %dn", GetLastError());
goto cleanup;
}
result = WinHttpWriteData(request, sendBuffer.get(), SEND_BUFFER_SIZE, &bytesTransferred);
if (!result)
{
printf("WinHttpWriteData failed %dn", GetLastError());
goto cleanup;
}
result = WinHttpWriteData(request, newLine.data(), newLine.length(), &bytesTransferred);
if (!result)
{
printf("WinHttpWriteData NewLine failed %dn", GetLastError());
goto cleanup;
}
}
result = WinHttpWriteData(request, chunkFooter.data(), chunkFooter.length(), &bytesTransferred);
if (!result)
{
printf("WinHttpWriteData ChunkFooter failed %dn", GetLastError());
goto cleanup;
}
result = WinHttpReceiveResponse(request, NULL);
if (!result)
{
printf("WinHttpReceiveResponse failed %dn", GetLastError());
goto cleanup;
}
do
{
result = WinHttpReadData(request, recvBuffer.get(), RECV_BUFFER_SIZE, &bytesTransferred);
if (!result)
{
printf("WinHttpReadData failed %dn", GetLastError());
goto cleanup;
}
else
{
printf("Received %d bytesn", bytesTransferred);
if (bytesTransferred < 1024)
{
std::string message(recvBuffer.get(), recvBuffer.get() + bytesTransferred);
std::cout << message << std::endl;
}
}
} while (bytesTransferred > 0);
printf("All Donen");
cleanup:
if (request)
WinHttpCloseHandle(request);
if (connection)
WinHttpCloseHandle(connection);
if (session)
WinHttpCloseHandle(session);
}
有趣的是,有一篇博客文章提到IIS8支持全双工读写,但WinHttp仍然是半双工的。
IHttpContext3->EnableFullDuplex()--此API告诉IIS管道以进入全双工模式。这使处理程序能够发出一个读取以及一个并行的写入操作。
WinHttp仍然是半双工的。然而,他们引入了新的API支持websocket流量,实际上是全双工的。