Jestem na etapie projektowania, pisząc nową aplikację usługi Windows, która akceptuje połączenia TCP / IP dla długotrwałych połączeń (tj. Nie jest to jak HTTP, gdzie jest wiele krótkich połączeń, ale raczej klient łączy się i pozostaje połączony przez wiele godzin lub dni lub nawet tygodnie).
Szukam pomysłów na najlepszy sposób zaprojektowania architektury sieci. Muszę uruchomić co najmniej jeden wątek dla usługi. Rozważam użycie Asynch API (BeginRecieve itp.), Ponieważ nie wiem, ilu klientów będę podłączać w danym momencie (prawdopodobnie setki). Zdecydowanie nie chcę rozpoczynać wątku dla każdego połączenia.
Dane będą płynąć do klientów głównie z mojego serwera, ale czasami będą wysyłane polecenia od klientów. Jest to przede wszystkim aplikacja monitorująca, w której mój serwer okresowo wysyła dane o stanie do klientów.
Jakieś sugestie dotyczące najlepszego sposobu, aby uczynić to tak skalowalnym, jak to tylko możliwe? Podstawowy przepływ pracy? Dzięki.
EDYCJA: Żeby było jasne, szukam rozwiązań opartych na .net (C # jeśli to możliwe, ale każdy język .net będzie działał)
UWAGA: Aby otrzymać nagrodę, oczekuję czegoś więcej niż prostej odpowiedzi. Potrzebowałbym działającego przykładu rozwiązania, albo jako wskaźnika do czegoś, co mógłbym pobrać, albo krótkiego przykładu w linii. I musi być oparty na .net i Windows (dopuszczalny jest dowolny język .net)
EDYCJA: Chcę podziękować wszystkim, którzy udzielili dobrych odpowiedzi. Niestety mogłem zaakceptować tylko jedną i zdecydowałem się zaakceptować bardziej znaną metodę Begin / End. Rozwiązanie Esaca może być lepsze, ale wciąż jest na tyle nowe, że nie wiem na pewno, jak się sprawdzi.
Głosowałem za wszystkimi odpowiedziami, które uważałem za dobre, chciałbym zrobić dla was więcej. Dzięki jeszcze raz.
źródło
Odpowiedzi:
W przeszłości napisałem coś podobnego. Z moich badań lata temu wynika, że najlepszym rozwiązaniem było napisanie własnej implementacji gniazd, przy użyciu gniazd asynchronicznych. Oznaczało to, że klienci, którzy tak naprawdę nic nie robili, potrzebowali stosunkowo niewielkich zasobów. Wszystko, co ma miejsce, jest obsługiwane przez pulę wątków .net.
Napisałem to jako klasę zarządzającą wszystkimi połączeniami dla serwerów.
Po prostu użyłem listy do przechowywania wszystkich połączeń klientów, ale jeśli potrzebujesz szybszego wyszukiwania większych list, możesz ją napisać w dowolny sposób.
Potrzebujesz również gniazda faktycznie nasłuchującego połączeń przychodzących.
Metoda start w rzeczywistości uruchamia gniazdo serwera i rozpoczyna nasłuchiwanie wszelkich połączeń przychodzących.
Chciałbym tylko zauważyć, że kod obsługi wyjątków wygląda źle, ale powodem tego jest to, że miałem tam kod
false
blokujący wyjątki, aby wszelkie wyjątki były pomijane i zwracane, jeśli ustawiono opcję konfiguracji, ale chciałem go usunąć ze względu na zwięzłość._ServerSocket.BeginAccept (new AsyncCallback (acceptCallback)), _serverSocket) powyżej zasadniczo ustawia nasze gniazdo serwera na wywołanie metody acceptCallback za każdym razem, gdy użytkownik się połączy. Ta metoda jest uruchamiana z puli wątków .Net, która automatycznie obsługuje tworzenie dodatkowych wątków roboczych, jeśli masz wiele operacji blokujących. Powinno to optymalnie obsłużyć każde obciążenie serwera.
Powyższy kod w zasadzie właśnie zakończył akceptację nadchodzącego połączenia, kolejek,
BeginReceive
które są wywołaniami zwrotnymi, które zostaną uruchomione, gdy klient wyśle dane, a następnie kolejkuje następne,acceptCallback
które zaakceptuje następne połączenie klienta.BeginReceive
Wywołanie metody jest to, co mówi, gniazdo, co zrobić, gdy odbiera dane od klienta. DlaBeginReceive
, trzeba dać mu tablicę bajtów, czyli tam, gdzie będzie to skopiować dane, gdy klient wysyła dane.ReceiveCallback
Metoda będzie się nazywa, co jest jak załatwiamy odbiór danych.EDYCJA: W tym wzorze zapomniałem wspomnieć, że w tym obszarze kodu:
Generalnie robiłbym w kodzie, jaki chcesz, to ponowne składanie pakietów w wiadomości, a następnie tworzenie ich jako zadań w puli wątków. W ten sposób BeginReceive następnego bloku od klienta nie jest opóźnione podczas działania dowolnego kodu przetwarzania komunikatów.
Akceptacja wywołania zwrotnego kończy odczytywanie gniazda danych przez wywołanie zakończenia odbioru. Wypełnia to bufor dostarczony w funkcji rozpoczęcia odbioru. Gdy zrobisz cokolwiek chcesz w miejscu, w którym zostawiłem komentarz, wywołujemy następną
BeginReceive
metodę, która uruchomi wywołanie zwrotne ponownie, jeśli klient wyśle więcej danych. Teraz jest naprawdę trudna część, kiedy klient wysyła dane, twoje wywołanie zwrotne może zostać wywołane tylko z częścią wiadomości. Ponowny montaż może stać się bardzo skomplikowany. Użyłem swojej własnej metody i stworzyłem w tym celu rodzaj zastrzeżonego protokołu. Pominąłem to, ale jeśli poprosisz, mogę to dodać. Ten program obsługi był właściwie najbardziej skomplikowanym fragmentem kodu, jaki kiedykolwiek napisałem.Powyższa metoda wysyłania w rzeczywistości wykorzystuje
Send
wywołanie synchroniczne , co dla mnie było w porządku ze względu na rozmiary wiadomości i wielowątkowość mojej aplikacji. Jeśli chcesz wysłać wiadomość do każdego klienta, wystarczy przejrzeć listę _sockets.Klasa xConnection, o której mowa powyżej, jest w zasadzie prostym opakowaniem dla gniazda, które zawiera bufor bajtowy, aw mojej implementacji kilka dodatków.
Również w celach informacyjnych są tutaj
using
załączone przeze mnie elementy, ponieważ zawsze denerwuję się, gdy ich nie ma.Mam nadzieję, że to pomocne, może nie jest to najczystszy kod, ale działa. Istnieje również kilka niuansów w kodzie, których zmianą powinieneś być zmęczony. Po pierwsze, możesz mieć tylko jedno połączenie
BeginAccept
w dowolnym momencie. Wokół tego występował bardzo irytujący błąd .net, który miał miejsce lata temu, więc nie pamiętam szczegółów.Ponadto w
ReceiveCallback
kodzie przetwarzamy wszystko otrzymane z gniazda przed umieszczeniem w kolejce następnego odbioru. Oznacza to, że w przypadku pojedynczego gniazda jesteśmy w rzeczywistości tylkoReceiveCallback
raz w dowolnym momencie i nie musimy używać synchronizacji wątków. Jeśli jednak zmienisz kolejność, aby wywołać następny odbiór natychmiast po pobraniu danych, co może być trochę szybsze, musisz upewnić się, że poprawnie zsynchronizowałeś wątki.Poza tym hackowałem dużo swojego kodu, ale pozostawiłem istotę tego, co się dzieje. To powinien być dobry początek dla twojego projektu. Zostaw komentarz, jeśli masz więcej pytań na ten temat.
źródło
Send
metody będą blokować na czas nieokreślony po obu stronach, ponieważ nikt nie czyta danych wejściowych.Istnieje wiele sposobów wykonywania operacji sieciowych w języku C #. Wszystkie z nich używają różnych mechanizmów pod maską, a zatem mają poważne problemy z wydajnością przy wysokiej współbieżności. Operacje typu Begin * to jedna z tych, które wiele osób często myli za najszybszy / najszybszy sposób tworzenia sieci.
Aby rozwiązać te problemy, wprowadzili zestaw metod * Async: Z MSDN http://msdn.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx
Klasa SocketAsyncEventArgs jest częścią zestawu ulepszeń klasy System.Net.Sockets .. ::. Socket, które udostępniają alternatywny wzorzec asynchroniczny, który może być używany przez wyspecjalizowane aplikacje gniazd o wysokiej wydajności. Ta klasa została specjalnie zaprojektowana dla aplikacji serwerów sieciowych, które wymagają wysokiej wydajności. Aplikacja może używać ulepszonego wzorca asynchronicznego wyłącznie lub tylko w docelowych obszarach aktywnych (na przykład podczas odbierania dużych ilości danych).
Główną cechą tych ulepszeń jest uniknięcie powtarzającego się przydzielania i synchronizacji obiektów podczas asynchronicznych operacji we / wy o dużej objętości. Wzorzec projektowy Begin / End aktualnie zaimplementowany przez klasę System.Net.Sockets .. ::. Socket wymaga przydzielenia obiektu System .. ::. IAsyncResult dla każdej asynchronicznej operacji gniazda.
Pod okładkami * Async API wykorzystuje porty zakończenia we / wy, które są najszybszym sposobem wykonywania operacji sieciowych, zobacz http://msdn.microsoft.com/en-us/magazine/cc302334.aspx
Aby Ci pomóc, dołączam kod źródłowy serwera telnet, który napisałem przy użyciu * Async API. Uwzględniam tylko odpowiednie porcje. Należy również zauważyć, że zamiast przetwarzać dane w tekście, zamiast tego decyduję się na wypchnięcie ich do kolejki bez blokady (bez oczekiwania), która jest przetwarzana w osobnym wątku. Zwróć uwagę, że nie uwzględniam odpowiedniej klasy Pool, która jest po prostu prostą pulą, która utworzy nowy obiekt, jeśli jest pusta, oraz klasą Buffer, która jest tylko samorozszerzającym się buforem, który nie jest naprawdę potrzebny, chyba że otrzymujesz indeterministyczny ilość danych. Jeśli chcesz uzyskać więcej informacji, wyślij mi PM.
źródło
Kiedyś była naprawdę dobra dyskusja na temat skalowalnego TCP / IP przy użyciu .NET, napisana przez Chrisa Mullinsa z Coversant, niestety wygląda na to, że jego blog zniknął z poprzedniego miejsca, więc spróbuję zebrać jego rady z pamięci (kilka przydatnych komentarzy o jego pojawieniu się w tym wątku: C ++ vs. C #: Opracowanie wysoce skalowalnego serwera IOCP )
Przede wszystkim należy zauważyć, że zarówno używanie, jak
Begin/End
iAsync
metody wSocket
klasie wykorzystują porty IO Completion Port (IOCP) w celu zapewnienia skalowalności. To powoduje znacznie większą różnicę (przy prawidłowym użyciu; patrz poniżej) w skalowalności niż ta, którą z dwóch metod faktycznie wybierzesz do wdrożenia rozwiązania.Posty Chrisa Mullinsa były oparte na używaniu
Begin/End
, z którym osobiście mam doświadczenie. Zauważ, że Chris stworzył oparte na tym rozwiązanie, które przeskalowało do 10 000 jednoczesnych połączeń klientów na 32-bitowej maszynie z 2 GB pamięci i do 100 000 na platformie 64-bitowej z wystarczającą ilością pamięci. Z własnego doświadczenia z tą techniką (chociaż nigdzie w pobliżu tego rodzaju obciążenia) nie mam powodu, aby wątpić w te orientacyjne liczby.IOCP a prymitywy typu wątek na połączenie lub „wybierz”
Powodem, dla którego chcesz użyć mechanizmu, który korzysta z IOCP pod maską, jest to, że używa on bardzo niskiego poziomu puli wątków systemu Windows, która nie budzi żadnych wątków, dopóki nie będą rzeczywiste dane w kanale IO, z którego próbujesz odczytać ( zwróć uwagę, że IOCP może być również używany do plików IO). Zaletą tego jest to, że system Windows nie musi przełączać się do wątku tylko po to, aby stwierdzić, że i tak nie ma jeszcze danych, więc zmniejsza to liczbę przełączeń kontekstu, które będzie musiał wykonać serwer do niezbędnego minimum.
Przełączniki kontekstu z pewnością zabiją mechanizm „wątek na połączenie”, chociaż jest to realne rozwiązanie, jeśli masz do czynienia tylko z kilkoma tuzinami połączeń. Ten mechanizm nie jest jednak w żadnym wypadku „skalowalny”.
Ważne uwagi dotyczące korzystania z IOCP
Pamięć
Przede wszystkim ważne jest, aby zrozumieć, że IOCP może łatwo spowodować problemy z pamięcią w .NET, jeśli Twoja implementacja jest zbyt naiwna. Każde
BeginReceive
wywołanie IOCP spowoduje „przypięcie” bufora, do którego czytasz. Aby uzyskać dobre wyjaśnienie, dlaczego jest to problem, zobacz: Blog Yun Jina: OutOfMemoryException and Pinning .Na szczęście tego problemu można uniknąć, ale wymaga to trochę kompromisu. Sugerowanym rozwiązaniem jest przydzielenie dużego
byte[]
bufora podczas uruchamiania aplikacji (lub jego zamknięcia), o wielkości co najmniej 90 KB (od .NET 2 wymagany rozmiar może być większy w późniejszych wersjach). Powodem tego jest to, że duże alokacje pamięci automatycznie kończą się w segmencie pamięci niekompaktowanym (The Large Object Heap), który jest skutecznie automatycznie przypinany. Przydzielając jeden duży bufor podczas uruchamiania, upewniasz się, że ten blok pamięci nieporuszalnej ma stosunkowo „niski adres”, gdzie nie będzie przeszkadzał i nie spowoduje fragmentacji.Następnie możesz użyć przesunięć, aby podzielić ten jeden duży bufor na oddzielne obszary dla każdego połączenia, które musi odczytać niektóre dane. Tutaj pojawia się kompromis; ponieważ ten bufor musi być wstępnie przydzielony, będziesz musiał zdecydować, ile przestrzeni bufora potrzebujesz na połączenie i jaki górny limit chcesz ustawić na liczbę połączeń, do których chcesz skalować (lub możesz zaimplementować abstrakcję które mogą przydzielić dodatkowe przypięte bufory, gdy będą potrzebne).
Najprostszym rozwiązaniem byłoby przypisanie każdemu połączeniu jednego bajtu z unikalnym przesunięciem w tym buforze. Następnie możesz
BeginReceive
zażądać odczytania pojedynczego bajtu, a resztę wykonać w wyniku otrzymanego wywołania zwrotnego.Przetwarzanie
Kiedy otrzymasz wywołanie zwrotne z
Begin
wywołania, które wykonałeś, bardzo ważne jest, aby zdać sobie sprawę, że kod w wywołaniu zwrotnym zostanie wykonany w wątku IOCP niskiego poziomu. Jest absolutnie konieczne , aby unikać długotrwałych operacji w tym wywołaniu zwrotnym. Używanie tych wątków do złożonego przetwarzania zabije skalowalność tak samo efektywnie, jak użycie „wątku na połączenie”.Sugerowanym rozwiązaniem jest użycie wywołania zwrotnego tylko w celu kolejkowania elementu roboczego w celu przetworzenia danych przychodzących, które zostaną wykonane w innym wątku. Unikaj wszelkich potencjalnie blokujących operacji wewnątrz wywołania zwrotnego, aby wątek IOCP mógł jak najszybciej wrócić do swojej puli. W .NET 4.0 sugerowałbym, że najłatwiejszym rozwiązaniem jest utworzenie pliku
Task
, podając mu odniesienie do gniazda klienta i kopię pierwszego bajtu, który został już odczytany przezBeginReceive
wywołanie. To zadanie jest następnie odpowiedzialne za odczytanie wszystkich danych z gniazda, które reprezentują przetwarzane żądanie, wykonanie go, a następnieBeginReceive
ponowne wywołanie kolejki gniazda dla IOCP. W wersji starszej niż .NET 4.0 można użyć puli wątków lub utworzyć własną implementację kolejki roboczej z wątkami.Podsumowanie
Zasadniczo sugerowałbym użycie przykładowego kodu Kevina dla tego rozwiązania z następującymi dodatkowymi ostrzeżeniami:
BeginReceive
jest już „przypięty”BeginReceive
nie robi nic więcej niż kolejkowanie zadania w celu obsługi faktycznego przetwarzania przychodzących danychGdy to zrobisz, nie wątpię, że możesz powtórzyć wyniki Chrisa, skalując do potencjalnie setek tysięcy jednoczesnych klientów (biorąc pod uwagę odpowiedni sprzęt i wydajną implementację własnego kodu przetwarzania;)
źródło
Większość odpowiedzi otrzymałeś już za pomocą powyższych przykładów kodu. Korzystanie z asynchronicznych operacji we / wy jest absolutnie właściwą drogą. Async IO to sposób, w jaki Win32 jest projektowany wewnętrznie w celu skalowania. Najlepszą możliwą wydajność, jaką można uzyskać, uzyskuje się za pomocą portów ukończenia, wiążących gniazda z portami zakończenia i puli wątków oczekujących na zakończenie portu zakończenia. Powszechnie uważa się, że na ukończenie czeka od 2 do 4 wątków na procesor (rdzeń). Gorąco polecam przejrzenie tych trzech artykułów autorstwa Ricka Vicika z zespołu ds. Wydajności systemu Windows:
Artykuły te dotyczą głównie natywnego interfejsu API systemu Windows, ale są one obowiązkową lekturą dla każdego, kto próbuje pojąć skalowalność i wydajność. Mają też kilka majtek na temat zarządzania.
Drugą rzeczą, którą musisz zrobić, jest przejrzenie książki Poprawianie wydajności i skalowalności aplikacji .NET , która jest dostępna online. Odpowiednie i ważne porady dotyczące używania wątków, wywołań asynchronicznych i blokad znajdziesz w rozdziale 5. Ale prawdziwe perełki znajdują się w rozdziale 17, gdzie znajdziesz takie gadżety, jak praktyczne wskazówki dotyczące strojenia puli wątków. Moje aplikacje miały poważne problemy, dopóki nie dostosowałem maxIothreads / maxWorkerThreads zgodnie z zaleceniami w tym rozdziale.
Mówisz, że chcesz stworzyć czysty serwer TCP, więc mój następny punkt jest fałszywy. Jednakże , jeśli znajdziesz się opanowany i używać WebRequest klasę i jego pochodne, powinien być ostrzeżony, że jest smok strzeże tych drzwi: the ServicePointManager . Jest to klasa konfiguracji, która ma jeden cel w życiu: zrujnować wydajność. Upewnij się, że uwolnisz swój serwer od sztucznie narzuconego ServicePoint.ConnectionLimit, bo inaczej Twoja aplikacja nigdy się nie skaluje (pozwolę Ci odkryć, jaka jest domyślna wartość ...). Możesz również ponownie rozważyć domyślną zasadę wysyłania nagłówka Expect100Continue w żądaniach http.
Teraz, jeśli chodzi o interfejs API zarządzanego przez gniazdo rdzenia, po stronie wysyłania są dość proste, ale są znacznie bardziej złożone po stronie odbioru. Aby osiągnąć wysoką przepustowość i skalowalność, należy upewnić się, że gniazdo nie jest sterowane przepływem, ponieważ nie ma bufora wysłanego do odbioru. Idealnie, aby uzyskać wysoką wydajność, należy wysłać 3-4 bufory z wyprzedzeniem i opublikować nowe, gdy tylko jeden z nich zostanie zwrócony ( przed przetworzeniem tego, który wrócił), aby mieć pewność, że gniazdo zawsze ma gdzie zdeponować dane pochodzące z sieci. Zobaczysz, dlaczego prawdopodobnie nie będziesz w stanie tego wkrótce osiągnąć.
Po zakończeniu zabawy z BeginRead / BeginWrite API i rozpoczęciu poważnej pracy zdasz sobie sprawę, że potrzebujesz bezpieczeństwa w swoim ruchu, tj. Uwierzytelnianie i szyfrowanie ruchu NTLM / Kerberos lub przynajmniej ochrona przed naruszeniem ruchu. Sposób, w jaki to robisz, polega na korzystaniu z wbudowanego System.Net.Security.NegotiateStream (lub SslStream, jeśli chcesz przejść przez różne domeny). Oznacza to, że zamiast polegać na asynchronicznych operacjach gniazda prostego, będziesz polegać na operacjach asynchronicznych AuthenticatedStream. Gdy tylko uzyskasz gniazdo (z połączenia na kliencie lub z akceptacji na serwerze), tworzysz strumień w gnieździe i przesyłasz go do uwierzytelnienia, wywołując BeginAuthenticateAsClient lub BeginAuthenticateAsServer. Po zakończeniu uwierzytelniania (przynajmniej Twojego sejfu z natywnego szaleństwa InitiateSecurityContext / AcceptSecurityContext ...) dokonasz autoryzacji, sprawdzając właściwość RemoteIdentity swojego uwierzytelnionego strumienia i wykonując dowolną weryfikację ACL, którą Twój produkt musi obsługiwać. Następnie wyślesz wiadomości za pomocą BeginWrite i będziesz je otrzymywać za pomocą BeginRead. To jest problem, o którym mówiłem wcześniej, że nie będzie można opublikować wielu buforów odbioru, ponieważ klasy AuthenticateStream tego nie obsługują. Operacja BeginRead zarządza wewnętrznie wszystkimi operacjami we / wy do momentu odebrania całej ramki, w przeciwnym razie nie mogłaby obsłużyć uwierzytelnienia wiadomości (odszyfrować ramkę i sprawdzić podpis na ramce). Chociaż z mojego doświadczenia wynika, że praca wykonana przez klasy AuthenticatedStream jest dość dobra i nie powinna mieć z tym żadnego problemu. To znaczy. powinieneś być w stanie nasycić sieć GB tylko 4-5% CPU. Klasy AuthenticatedStream narzucą również ograniczenia rozmiaru ramki specyficzne dla protokołu (16 kB dla SSL, 12 kB dla Kerberos).
To powinno pomóc Ci zacząć na dobrej drodze. Nie zamierzam tutaj pisać kodu, na MSDN jest doskonały przykład . Zrobiłem wiele takich projektów i bez problemu udało mi się skalować do około 1000 podłączonych użytkowników. Przede wszystkim musisz zmodyfikować klucze rejestru, aby umożliwić jądru dostęp do większej liczby uchwytów gniazd. i upewnij się, że wdrażasz w systemie operacyjnym serwera , czyli W2K3, a nie XP lub Vista (tj. system operacyjny klienta), to robi dużą różnicę.
BTW, upewnij się, że jeśli masz operacje na bazach danych na serwerze lub we / wy pliku, używasz również dla nich smaku asynchronicznego, w przeciwnym razie opróżnisz pulę wątków w mgnieniu oka. W przypadku połączeń SQL Server upewnij się, że do parametrów połączenia dodano „Asyncronous Processing = true”.
źródło
Mam taki serwer działający w niektórych moich rozwiązaniach. Oto bardzo szczegółowe wyjaśnienie różnych sposobów na zrobienie tego w .net: Bliżej sieci dzięki gniazdom o wysokiej wydajności w .NET
Ostatnio szukałem sposobów na ulepszenie naszego kodu i będę się temu przyglądał: „ Ulepszenia wydajności gniazd w wersji 3.5 ”, które zostały dołączone specjalnie „do użytku przez aplikacje, które używają asynchronicznych operacji we / wy sieci w celu uzyskania najwyższej wydajności”.
„Główną cechą tych ulepszeń jest unikanie powtarzania alokacji i synchronizacji obiektów podczas asynchronicznych operacji wejścia / wyjścia gniazda o dużej objętości. Wzorzec projektowy Begin / End obecnie zaimplementowany przez klasę Socket dla asynchronicznych operacji we / wy gniazd wymaga System. Obiekt IAsyncResult zostanie przydzielony dla każdej asynchronicznej operacji gniazda. "
Możesz kontynuować czytanie, podążając za linkiem. Jutro osobiście przetestuję ich przykładowy kod, aby porównać go z tym, co mam.
Edycja: tutaj możesz znaleźć działający kod zarówno dla klienta, jak i serwera, używając nowego 3.5 SocketAsyncEventArgs, dzięki czemu możesz go przetestować w ciągu kilku minut i przejść przez kod. Jest to proste podejście, ale jest podstawą do rozpoczęcia znacznie większej implementacji. Ciekawą lekturą był również ten artykuł sprzed prawie dwóch lat w MSDN Magazine.
źródło
Czy rozważałeś użycie powiązania TCP netto programu WCF i wzorca publikowania / subskrypcji? WCF pozwoliłoby Ci skupić się [głównie] na Twojej domenie zamiast na hydraulice.
Istnieje wiele przykładów WCF, a nawet struktura publikowania / subskrypcji dostępna w sekcji pobierania IDesign, która może być przydatna: http://www.idesign.net
źródło
Zastanawiam się nad jedną rzeczą:
Dlaczego? Windows mógł obsłużyć setki wątków w aplikacji od co najmniej Windows 2000. Zrobiłem to, bardzo łatwo jest pracować, jeśli wątki nie muszą być synchronizowane. Zwłaszcza biorąc pod uwagę, że wykonujesz dużo operacji we / wy (więc nie jesteś związany z procesorem, a wiele wątków zostanie zablokowanych na dysku lub w komunikacji sieciowej), nie rozumiem tego ograniczenia.
Czy przetestowałeś wielowątkowość i zauważyłeś, że czegoś brakuje? Czy zamierzasz również mieć połączenie z bazą danych dla każdego wątku (co zabiłoby serwer bazy danych, więc jest to zły pomysł, ale można go łatwo rozwiązać za pomocą projektu trójwarstwowego). Martwisz się, że zamiast setek będziesz mieć tysiące klientów, a wtedy naprawdę będziesz mieć problemy? (Chociaż spróbuję tysiąca wątków, a nawet dziesięciu tysięcy, gdybym miał ponad 32 GB pamięci RAM - znowu, biorąc pod uwagę, że nie jesteś związany z procesorem, czas przełączania wątków powinien być absolutnie nieistotny.)
Oto kod - aby zobaczyć, jak to wygląda podczas działania, wejdź na http://mdpopescu.blogspot.com/2009/05/multi-threaded-server.html i kliknij zdjęcie.
Klasa serwera:
Główny program serwera:
Klasa klienta:
Główny program klienta:
źródło
Używanie zintegrowanego Async IO (
BeginRead
itp.) .NET jest dobrym pomysłem, jeśli możesz uzyskać prawidłowe wszystkie szczegóły. Kiedy prawidłowo skonfigurujesz uchwyty gniazda / pliku, użyje on podstawowej implementacji IOCP systemu operacyjnego, umożliwiając wykonanie operacji bez użycia żadnych wątków (lub, w najgorszym przypadku, użycie wątku, który moim zdaniem pochodzi z puli wątków IO jądra puli wątków platformy .NET, co pomaga złagodzić przeciążenie puli wątków).Głównym problemem jest upewnienie się, że otwierasz gniazda / pliki w trybie nieblokującym. Większość domyślnych funkcji wygodnych (takich jak
File.OpenRead
) tego nie robi, więc musisz napisać własne.Jednym z innych głównych problemów jest obsługa błędów - prawidłowa obsługa błędów podczas pisania asynchronicznego kodu we / wy jest znacznie, dużo trudniejsza niż robienie tego w kodzie synchronicznym. Bardzo łatwo jest również skończyć z warunkami wyścigu i zakleszczeniem, nawet jeśli nie używasz bezpośrednio wątków, więc musisz być tego świadomy.
Jeśli to możliwe, powinieneś spróbować użyć wygodnej biblioteki, aby ułatwić proces wykonywania skalowalnych asynchronicznych operacji we / wy.
Microsoft Concurrency Coordination Runtime jest jednym z przykładów biblioteki .NET zaprojektowanej w celu ułatwienia wykonywania tego rodzaju programowania. Wygląda świetnie, ale ponieważ go nie używałem, nie mogę komentować, jak dobrze by się skalował.
Dla moich projektów, które trzeba zrobić asynchroniczne sieci lub dysku I / O, używam zestawu narzędzi .NET współbieżności / IO, że został zbudowany w ciągu ostatniego roku, zwany Squared.Task . Jest inspirowany bibliotekami, takimi jak imvu.task i twisted , a w repozytorium umieściłem kilka działających przykładów, które wykonują sieciowe I / O. Użyłem go również w kilku napisanych przeze mnie aplikacjach - największą publicznie wydaną jest NDexer (który używa go do bezgwintowego wejścia / wyjścia dysku). Biblioteka została napisana w oparciu o moje doświadczenia z imvu.task i posiada zestaw dość obszernych testów jednostkowych, więc gorąco zachęcam do jej wypróbowania. Jeśli masz z tym jakieś problemy, chętnie Ci pomogę.
Moim zdaniem, opierając się na moim doświadczeniu, używanie asynchronicznych / bezgwintowych operacji IO zamiast wątków jest opłacalnym przedsięwzięciem na platformie .NET, o ile jesteś gotowy, aby poradzić sobie z krzywą uczenia się. Pozwala uniknąć problemów ze skalowalnością narzucanych przez koszt obiektów Thread, aw wielu przypadkach można całkowicie uniknąć używania blokad i muteksów, ostrożnie wykorzystując prymitywy współbieżności, takie jak Futures / Promises.
źródło
Użyłem rozwiązania Kevina, ale on mówi, że w rozwiązaniu brakuje kodu do ponownego składania wiadomości. Programiści mogą użyć tego kodu do ponownego składania wiadomości:
źródło
Ładny przegląd technik można znaleźć na stronie problemów C10k .
źródło
Możesz spróbować użyć frameworka o nazwie ACE (Adaptive Communications Environment), który jest ogólnym frameworkiem C ++ dla serwerów sieciowych. Jest to bardzo solidny, dojrzały produkt, zaprojektowany do obsługi aplikacji o wysokiej niezawodności i dużej objętości, aż do klasy telekomunikacyjnej.
Framework obsługuje dość szeroką gamę modeli współbieżności i prawdopodobnie ma jeden odpowiedni dla twojej aplikacji po wyjęciu z pudełka. Powinno to ułatwić debugowanie systemu, ponieważ większość nieprzyjemnych problemów ze współbieżnością została już rozwiązana. Kompromisem jest to, że framework jest napisany w C ++ i nie jest najbardziej ciepłą i puszystą bazą kodu. Z drugiej strony otrzymujesz przetestowaną infrastrukturę sieciową klasy przemysłowej i wysoce skalowalną architekturę po wyjęciu z pudełka.
źródło
Użyłbym Seda lub lekki bibliotekę wątków (Erlang lub nowsze linux zobaczyć NTPL skalowalność po stronie serwera ). Kodowanie asynchroniczne jest bardzo uciążliwe, jeśli Twoja komunikacja nie jest :)
źródło
Cóż, gniazda .NET wydają się udostępniać metodę select () - to jest najlepsze do obsługi danych wejściowych. Dla danych wyjściowych miałbym pulę wątków zapisujących gniazdo nasłuchujących w kolejce roboczej, akceptujących deskryptor / obiekt gniazda jako część elementu roboczego, więc nie potrzebujesz wątku na gniazdo.
źródło
Użyłbym metod AcceptAsync / ConnectAsync / ReceiveAsync / SendAsync, które zostały dodane w .Net 3.5. Zrobiłem test porównawczy i są one około 35% szybsze (czas odpowiedzi i szybkość transmisji), a 100 użytkowników stale wysyła i odbiera dane.
źródło
aby ludzie kopiowali wklejając zaakceptowaną odpowiedź, możesz przepisać metodę acceptCallback, usuwając wszystkie wywołania _serverSocket.BeginAccept (nowy AsyncCallback (acceptCallback), _serverSocket); i umieść go w klauzuli last {}, w ten sposób:
możesz nawet usunąć pierwszy haczyk, ponieważ jego zawartość jest taka sama, ale jest to metoda szablonowa i powinieneś użyć wpisanego wyjątku, aby lepiej obsłużyć wyjątki i zrozumieć, co spowodowało błąd, więc po prostu zaimplementuj te połowy za pomocą przydatnego kodu
źródło
Poleciłbym przeczytać te książki o ACE
aby uzyskać pomysły dotyczące wzorców umożliwiających stworzenie wydajnego serwera.
Chociaż ACE jest zaimplementowane w C ++, książki obejmują wiele użytecznych wzorców, które można wykorzystać w dowolnym języku programowania.
źródło
Nie osiągniesz najwyższego poziomu skalowalności, jeśli zdecydujesz się wyłącznie na .NET. Przerwy w GC mogą zmniejszyć opóźnienie.
Nakładające się operacje we / wy jest ogólnie uważane za najszybszy interfejs API systemu Windows do komunikacji sieciowej. Nie wiem, czy to jest to samo, co twój Asynch API. Nie używaj funkcji select, ponieważ każde wywołanie musi sprawdzić każde otwarte gniazdo zamiast wywołań zwrotnych w aktywnych gniazdach.
źródło
Możesz użyć platformy Open Source Push Framework do tworzenia wysokowydajnych serwerów. Jest zbudowany na IOCP i nadaje się do scenariuszy wypychania i transmisji wiadomości.
http://www.pushframework.com
źródło