.NET에서 비 차단, 단일 스레드, 비동기 웹 서버 (예 : Node.js)가 가능합니까?
.NET에서 단일 스레드 , 이벤트 기반 비 차단 비동기 웹 서버 를 만드는 방법을 찾고있는 이 질문 을보고있었습니다 .
이 답변 은 코드 본문이 단일 스레드에서 실행된다고 주장함으로써 처음에는 유망 해 보였습니다.
그러나 나는 이것을 C #에서 테스트했습니다.
using System;
using System.IO;
using System.Threading;
class Program
{
static void Main()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
var sc = new SynchronizationContext();
SynchronizationContext.SetSynchronizationContext(sc);
{
var path = Environment.ExpandEnvironmentVariables(
@"%SystemRoot%\Notepad.exe");
var fs = new FileStream(path, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite, 1024 * 4, true);
var bytes = new byte[1024];
fs.BeginRead(bytes, 0, bytes.Length, ar =>
{
sc.Post(dummy =>
{
var res = fs.EndRead(ar);
// Are we in the same thread?
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}, null);
}, null);
}
Thread.Sleep(100);
}
}
결과는 다음과 같습니다.
1
5
따라서 대답과는 달리 읽기를 시작하는 스레드와 읽기를 종료하는 스레드 가 동일 하지 않은 것 같습니다.
이제 내 질문은 .NET에서 단일 스레드 , 이벤트 기반 비 차단 비동기 웹 서버를 어떻게 얻을 수 있습니까?
전체 SetSynchronizationContext
는 붉은 청어입니다. 이것은 마샬링을위한 메커니즘 일뿐입니다. 작업은 여전히 IO 스레드 풀에서 발생합니다.
당신이 요구하는 것은 메인 스레드에서 모든 IO 작업에 대한 비동기 프로 시저 호출 을 대기열에 넣고 수집하는 방법 입니다. 많은 상위 레벨 프레임 워크가 이러한 종류의 기능을 래핑하며 가장 유명한 것은 libevent 입니다.
여기에 다양한 옵션에 대한 큰 요약이 있습니다. epoll, poll, threadpool의 차이점은 무엇입니까? .
.NET은 BeginXYZ
메서드 를 호출 할 때 IO 액세스를 처리하는 특수 "IO 스레드 풀"을 통해 이미 확장을 처리 합니다. 이 IO 스레드 풀에는 상자의 프로세서 당 최소 1 개의 스레드가 있어야합니다. 참조 : ThreadPool.SetMaxThreads .
단일 스레드 앱이 중요한 요구 사항 인 경우 (미친 이유로) 물론 DllImport 를 사용하여이 모든 항목을 상호 운용 할 수 있습니다 ( 여기 에서 예제 참조 ).
그러나 이것은 매우 복잡하고 위험한 작업입니다 .
완료 메커니즘으로 APC를 지원하지 않는 이유는 무엇입니까? APC는 실제로 사용자 코드를위한 좋은 범용 완성 메커니즘이 아닙니다. APC에 의해 도입 된 재진입을 관리하는 것은 거의 불가능합니다. 예를 들어, 잠금을 차단할 때마다 임의의 I / O 완료가 스레드를 차지할 수 있습니다. 자체 잠금을 획득하려고 시도 할 수 있으며, 이로 인해 잠금 순서 문제가 발생하여 교착 상태가 발생할 수 있습니다. 이를 방지하려면 세심한 설계가 필요하며 경고 대기 중에 다른 사람의 코드가 실행되지 않도록하는 능력이 필요하며 그 반대의 경우도 마찬가지입니다. 이것은 APC의 유용성을 크게 제한합니다.
그래서 요약하자. APC 및 완료 포트를 사용하여 모든 작업을 수행 하는 단일 스레드 관리 프로세스 를 원한다면 직접 코딩해야합니다. 그것을 만드는 것은 위험하고 까다로울 것입니다.
단순히 높은 규모의 네트워킹을 원한다면 BeginXYZ
APC를 사용하기 때문에 계속 해서 가족과 함께 사용할 수 있으며 잘 수행 될 것입니다. 스레드와 .NET 특정 구현 사이에 물건을 마샬링하는 데 약간의 비용을 지불합니다.
출처 : http://msdn.microsoft.com/en-us/magazine/cc300760.aspx
서버 확장의 다음 단계는 비동기 I / O를 사용하는 것입니다. 비동기 I / O는 스레드를 생성하고 관리 할 필요성을 줄여줍니다. 이것은 훨씬 더 간단한 코드로 이어지고 또한 더 효율적인 I / O 모델입니다. 비동기 I / O는 콜백을 사용하여 수신 데이터 및 연결을 처리합니다. 즉, 설정 및 검색 할 목록이 없으며 보류중인 I / O를 처리하기 위해 새 작업자 스레드를 만들 필요가 없습니다.
흥미로운 점은 단일 스레드가 완료 포트를 사용하여 Windows에서 비동기 소켓을 수행하는 가장 빠른 방법이 아니라는 것입니다. http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/info/comport를 참조 하십시오. shtml
서버의 목표는 스레드가 불필요한 블로킹을 방지하는 동시에 여러 스레드를 사용하여 병렬 처리를 최대화하여 가능한 한 적은 컨텍스트 전환을 발생시키는 것입니다. 이상적인 것은 모든 프로세서에서 클라이언트 요청을 적극적으로 서비스하는 스레드가 있고 요청을 완료 할 때 대기중인 추가 요청이있는 경우 해당 스레드가 차단되지 않는 것입니다. 그러나 이것이 올바르게 작동하려면 클라이언트 요청 처리가 I / O에서 차단 될 때 (예 : 처리의 일부로 파일에서 읽을 때) 응용 프로그램이 다른 스레드를 활성화 할 수있는 방법이 있어야합니다.
당신이 필요로하는 것은 큐에있는 다음 태스크를 취하고 그것을 실행하는 "메시지 루프"입니다. 또한 모든 작업은 차단없이 최대한 많은 작업을 완료하도록 코딩 한 다음 나중에 시간이 필요한 작업을 선택하기 위해 추가 작업을 대기열에 추가해야합니다. 이것에 대해 마법 같은 것은 없습니다. 차단 호출을 사용하지 않고 추가 스레드를 생성하지 않습니다.
예를 들어, HTTP GET을 처리 할 때 서버는 소켓에서 현재 사용 가능한만큼의 데이터를 읽을 수 있습니다. 이 데이터가 요청을 처리하기에 충분하지 않으면 나중에 소켓에서 다시 읽을 수 있도록 새 작업을 대기열에 추가합니다. FileStream의 경우 인스턴스의 ReadTimeout을 낮은 값으로 설정하고 전체 파일보다 적은 바이트를 읽을 수 있도록 준비하려고합니다.
C # 5는 실제로이 패턴을 훨씬 더 사소하게 만듭니다. 많은 사람들이 비동기 기능이 멀티 스레딩을 의미 한다고 생각 하지만 그렇지 않습니다 . 비동기를 사용하면 기본적으로 앞서 언급 한 작업 대기열을 관리 할 필요없이 얻을 수 있습니다.
네, 이름은 Manos de mono
진지하게, manos의 전체 아이디어는 단일 스레드 비동기 이벤트 구동 웹 서버입니다.
고성능 및 확장 가능. 친구 피드를 지원하는 기술인 tornadoweb을 모델로 한 Manos는 수천 개의 동시 연결이 가능하며 서버와의 지속적인 연결을 생성하는 애플리케이션에 이상적입니다.
이 프로젝트는 유지 관리가 적고 생산 준비가되지 않았을 것 같지만 이것이 가능하다는 것을 보여주는 좋은 사례 연구가됩니다.
다음은 IO 완료 포트가 무엇이며 C #을 통해 액세스 할 수있는 방법을 설명하는 훌륭한 기사 시리즈입니다 (예 : Kernel32.dll에서 Win32 API 호출로 PInvoke해야 함).
참고 : node.js 뒤의 교차 플랫폼 IO 프레임 워크 인 libuv 는 Windows에서 IOCP를 사용하고 유닉스 운영 체제에서 libev를 사용합니다.
http://www.theukwebdesigncompany.com/articles/iocp-thread-pooling.php
아무도 카약 을 언급하지 않았 는지 궁금 합니다. 기본적으로 Pythons twisted , JavaScripts node.js 또는 Rubys eventmachine에 대한 C #의 답변입니다.
나는 그러한 아키텍처의 내 자신의 간단한 구현을 다루어 왔으며 github 에 올려 놓았습니다 . 나는 그것을 배우는 것으로 더하고있다. 그러나 그것은 많은 즐거움이었고 나는 그것을 더 플러시 할 것이라고 생각합니다.
매우 알파이므로 변경 될 수 있지만 코드는 다음과 같습니다.
//Start the event loop.
EventLoop.Start(() => {
//Create a Hello World server on port 1337.
Server.Create((req, res) => {
res.Write("<h1>Hello World</h1>");
}).Listen("http://*:1337");
});
이에 대한 자세한 정보는 여기 에서 찾을 수 있습니다 .
MVC, WebApi 및 라우팅을 지원하는 HttpListener 및 이벤트 루프를 기반으로 서버를 개발했습니다. 내가 본 성능은 표준 IIS + MVC보다 훨씬 낫습니다. 누군가가 그것을 시도한다면 나는 피드백을 위해 고군분투하고 있습니다! 실제로이 구조를 기반으로 웹 사이트를 만드는 템플릿이 있습니다.
절대적으로 필요할 때까지 ASYNC / AWAIT를 사용하지 않습니다. 내가 사용하는 유일한 작업은 소켓에 쓰기 또는 파일 읽기와 같은 I / O 바인딩 작업을위한 작업입니다.
PS 어떤 제안이나 수정도 환영합니다!
여기에서는 운영 체제의 지원이 필수적입니다. 예를 들어 Mono는 비동기 I / O가있는 Linux에서 epoll을 사용하므로 확장 성이 매우 우수해야합니다 (여전히 스레드 풀). 당신이 찾고 있고 성능과 확장 성을 원한다면 꼭 시도하십시오.
반면에 언급 한 아이디어를 기반으로하는 C # (네이티브 라이브러리 포함) 웹 서버의 예는 Manos de Mono 일 수 있습니다. 프로젝트가 최근에 활성화되지 않았습니다. 그러나 아이디어와 코드는 일반적으로 사용할 수 있습니다. 이것을 읽으십시오 (특히 "마 노스 자세히보기"부분).
편집하다:
주 스레드에서 콜백을 시작하려면 WPF 디스패처와 같은 기존 동기화 컨텍스트를 약간 남용 할 수 있습니다. 이 접근 방식으로 번역 된 코드 :
using System;
using System.IO;
using System.Threading;
using System.Windows;
namespace Node
{
class Program
{
public static void Main()
{
var app = new Application();
app.Startup += ServerStart;
app.Run();
}
private static void ServerStart(object sender, StartupEventArgs e)
{
var dispatcher = ((Application) sender).Dispatcher;
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
var path = Environment.ExpandEnvironmentVariables(
@"%SystemRoot%\Notepad.exe");
var fs = new FileStream(path, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite, 1024 * 4, true);
var bytes = new byte[1024];
fs.BeginRead(bytes, 0, bytes.Length, ar =>
{
dispatcher.BeginInvoke(new Action(() =>
{
var res = fs.EndRead(ar);
// Are we in the same thread?
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}));
}, null);
}
}
}
원하는 것을 인쇄합니다. 또한 디스패처로 우선 순위를 설정할 수 있습니다. 그러나 동의하십시오, 이것은 추악하고 해키이며 데모 요청에 응답하는 것보다 다른 이유로 왜 그렇게 할 것인지 모르겠습니다.)
먼저 SynchronizationContext에 대해 설명합니다. Sam이 쓴 것과 똑같습니다. 기본 클래스는 단일 스레드 기능을 제공하지 않습니다. UI 스레드에서 코드를 실행하는 기능을 제공하는 WindowsFormsSynchronizationContext에서 이러한 아이디어를 얻었을 것입니다.
ThreadPool 매개 변수와 함께 작동하는 코드를 작성했습니다. (Sam이 이미 지적한 것).
이 코드는 자유 스레드에서 실행할 3 개의 비동기 작업을 등록합니다. 둘 중 하나가 ThreadPool 매개 변수를 변경할 때까지 병렬로 실행됩니다. 그런 다음 각 작업이 동일한 스레드에서 실행됩니다.
.net 앱이 하나의 스레드를 사용하도록 강제 할 수 있다는 것을 증명할뿐입니다. 하나의 스레드에서만 호출을 수신하고 처리하는 웹 서버의 실제 구현은 완전히 다릅니다. :).
코드는 다음과 같습니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
namespace SingleThreadTest
{
class Program
{
class TestState
{
internal string ID { get; set; }
internal int Count { get; set; }
internal int ChangeCount { get; set; }
}
static ManualResetEvent s_event = new ManualResetEvent(false);
static void Main(string[] args)
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
int nWorkerThreads;
int nCompletionPortThreads;
ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);
Console.WriteLine(String.Format("Max Workers: {0} Ports: {1}",nWorkerThreads,nCompletionPortThreads));
ThreadPool.GetMinThreads(out nWorkerThreads, out nCompletionPortThreads);
Console.WriteLine(String.Format("Min Workers: {0} Ports: {1}",nWorkerThreads,nCompletionPortThreads));
ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), new TestState() { ID = "A ", Count = 10, ChangeCount = 0 });
ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), new TestState() { ID = " B ", Count = 10, ChangeCount = 5 });
ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), new TestState() { ID = " C", Count = 10, ChangeCount = 0 });
s_event.WaitOne();
Console.WriteLine("Press enter...");
Console.In.ReadLine();
}
static void LetsRunLikeCrazy(object o)
{
if (s_event.WaitOne(0))
{
return;
}
TestState oState = o as TestState;
if (oState != null)
{
// Are we in the same thread?
Console.WriteLine(String.Format("Hello. Start id: {0} in thread: {1}",oState.ID, Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(1000);
oState.Count -= 1;
if (oState.ChangeCount == oState.Count)
{
int nWorkerThreads = 1;
int nCompletionPortThreads = 1;
ThreadPool.SetMinThreads(nWorkerThreads, nCompletionPortThreads);
ThreadPool.SetMaxThreads(nWorkerThreads, nCompletionPortThreads);
ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);
Console.WriteLine(String.Format("New Max Workers: {0} Ports: {1}", nWorkerThreads, nCompletionPortThreads));
ThreadPool.GetMinThreads(out nWorkerThreads, out nCompletionPortThreads);
Console.WriteLine(String.Format("New Min Workers: {0} Ports: {1}", nWorkerThreads, nCompletionPortThreads));
}
if (oState.Count > 0)
{
Console.WriteLine(String.Format("Hello. End id: {0} in thread: {1}", oState.ID, Thread.CurrentThread.ManagedThreadId));
ThreadPool.QueueUserWorkItem(new WaitCallback(LetsRunLikeCrazy), oState);
}
else
{
Console.WriteLine(String.Format("Hello. End id: {0} in thread: {1}", oState.ID, Thread.CurrentThread.ManagedThreadId));
s_event.Set();
}
}
else
{
Console.WriteLine("Error !!!");
s_event.Set();
}
}
}
}
LibuvSharp is a wrapper for libuv, which is used in the node.js project for async IO. BUt it only contains only low level TCP/UDP/Pipe/Timer functionality. And it will stay like that, writing a webserver on top of it is an entire different story. It doesn't even support dns resolving, since this is just a protocol on top of udp.
I believe it's possible, here is an open-source example written in VB.NET and C#:
https://github.com/perrybutler/dotnetsockets/
It uses Event-based Asynchronous Pattern (EAP), IAsyncResult Pattern and thread pool (IOCP). It will serialize/marshal the messages (messages can be any native object such as a class instance) into binary packets, transfer the packets over TCP, and then deserialize/unmarshal the packets at the receiving end so you get your native object to work with. This part is somewhat like Protobuf or RPC.
It was originally developed as a "netcode" for real-time multiplayer gaming, but it can serve many purposes. Unfortunately I never got around to using it. Maybe someone else will.
The source code has a lot of comments so it should be easy to follow. Enjoy!
Here is one more implementation of the event-loop web server called SingleSand. It executes all custom logic inside single-threaded event loop but the web server is hosted in asp.net. Answering the question, it is generally not possible to run a pure single threaded app because of .NET multi-threaded nature. There are some activities that run in separate threads and developer cannot change their behavior.
ReferenceURL : https://stackoverflow.com/questions/8905860/is-a-non-blocking-single-threaded-asynchronous-web-server-like-node-js-possi
'Development Tip' 카테고리의 다른 글
java.util.AbstractList.add의 UnsupportedOperationException (0) | 2020.12.26 |
---|---|
Javascript로 목록 이해하기 (0) | 2020.12.25 |
Flask 앱의 공통 폴더 / 파일 구조 (0) | 2020.12.25 |
iOS 7의 기본 공유 (0) | 2020.12.25 |
Facebook Graph API 2.0에서 사용자 이름 필드 가져 오기 (0) | 2020.12.25 |