.NET 3.0 en liten översikt - del 2
Förord
Vi fortsätter att utforska .NET 3.0 frameworket med genom en fallstudie på en chatt applikation. I denna artikel koncentrerar vi oss på server sidan.Innehåll
»»
»
»
»
»
»
Relaterade artiklar
» .NET 3.0 en liten översikt - del 1» .NET 3.0 en liten översikt - del 3
» .NET 3.0 en liten översikt - del 4 (WPF)
» .NET 3.0 en liten översikt - del 5 (WPF)
» .NET 3.0 en liten översikt - del 6 (WPF) - sista delen
I del 1 av artikelserien började vi med vårt chatt system och vi defineierade WCF gränssnittet för vår chatt service. Nästa grej vi tittar på hur vi implementerar server funtionaliteten för chatten.
En WCF består av några olika delar. För det första, så behöver den ”hostas” någonstans, ett sätt är att låta IIS sköta detta, men i detta fall har jag valt att sköta det själv. Ingendera versionen är speciellt komplicerad.
Vi dyker rakt in i koden...
Med WCF kommer ServiceHost klassen som gör det mesta av jobbet åt oss, det enda du behöver göra är att ge den klassen som implementerar service interfacet, dvs. vårt chatt interface, samt bas adress(er) där servicen skall göras tillgänglig.
För demonstrationens skull har jag valt att låta servicen publiceras både över http samt över named pipes (som är effektivare, men funkar enbart när klient och server finns på samma dator.)
Först har vi tämligen ointressant boilerplate kod
Sedan kommer vi till den intressanta delen. Vår service klass implementerar vårt IChat interface, och med ServiceBehavior attributet indikerar vi att vår service är en singleton som kan accesseras av flera klienter samtidigt. (Vilket ju är rätt naturligt för en chatt applikation)
Även fast vi tillåter många klienter samtidigt, så tillåts inte reentranta anrop, därför måste vi putta iväg den delen till en annan tråd, så att anropet till Say metoden får köras klart.
Det var själva kodbiten av det hela, sen är det bara en liten bit kvar, nämligen konfigureringen av servicen. Detta går naturligtvis även att göra inne i koden, men för att så att säga hålla dörrarna öppna är det bra att köra det från konfigurationsfilen.
Därför pillar vi in följande i app.config
Contract attributet här är alltså satt till det kvalificerade namnet på vårt service gränssnitt. Name attributet anger namnet på service klassen som implementerar gränssittet.
Som ni ser kan en service ha flera så kallade endpoints, det vill säga adresser/protokoll som kan användas för att ta kontakt med servicen.
Nu har vi alltså skapat ett service gränssnitt (i del 1), och skapat en service som implementerar detta gränssnitt. Då återstår bara att skapa en klient så att vi kan testa huruvida det funkar. Detta är då ämnet för nästa artikel.
Återigen, frågor och kommentarer mottages tacksamt.
Vad behövs för en server
En WCF består av några olika delar. För det första, så behöver den ”hostas” någonstans, ett sätt är att låta IIS sköta detta, men i detta fall har jag valt att sköta det själv. Ingendera versionen är speciellt komplicerad.
Host kod
Vi dyker rakt in i koden...
using System;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Description;
namespace LemonDesign.ChatServer {
class ServerProgram {
static void Main(string[] args) {
ServiceHost host = new ServiceHost(ChatProvider.Instance,
new Uri("http://" + Dns.GetHostName() + ":81"),
new Uri("net.pipe://" + Dns.GetHostName()));
Console.WriteLine("Chat server starting ...");
foreach (ServiceEndpoint endpoint in host.Description.Endpoints) {
Console.WriteLine("Serving at address {0}:", endpoint.Address);
}
host.Open();
Console.WriteLine("Press enter to shut down ...");
Console.ReadLine();
host.Close();
}
}
}
Med WCF kommer ServiceHost klassen som gör det mesta av jobbet åt oss, det enda du behöver göra är att ge den klassen som implementerar service interfacet, dvs. vårt chatt interface, samt bas adress(er) där servicen skall göras tillgänglig.
För demonstrationens skull har jag valt att låta servicen publiceras både över http samt över named pipes (som är effektivare, men funkar enbart när klient och server finns på samma dator.)
Service provider
Först har vi tämligen ointressant boilerplate kod
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Threading;
using LemonDesign.Chat.Contract;
namespace LemonDesign.ChatServer {
///
/// Helper class
///
internal class UserMessagePair {
public string User;
public string Message;
public UserMessagePair(string user, string message) {
User = user; Message = message;
}
}
Service implementation
Sedan kommer vi till den intressanta delen. Vår service klass implementerar vårt IChat interface, och med ServiceBehavior attributet indikerar vi att vår service är en singleton som kan accesseras av flera klienter samtidigt. (Vilket ju är rätt naturligt för en chatt applikation)
///
/// Implementation of the service interface
///
/// There will ever be only one instance of the server, but multiple clients are allowed concurrently.
[ServiceBehavior(Namespace="urn:LemonDesign:Chat", ConcurrencyMode=ConcurrencyMode.Multiple,
InstanceContextMode=InstanceContextMode.Single)]
public class ChatProvider : IChat {
private static readonly ChatProvider m_instance = new ChatProvider();
private static readonly object m_concurrencyLock = new object();
private Dictionary m_clients;
///
/// Service implementation singleton accessor
///
public static ChatProvider Instance {
get { return m_instance; }
}
private ChatProvider() {
m_clients = new Dictionary();
}
#region IChat Members
///
/// Called when a client connects.
///
/// the client username
public void Connect(string username) {
Console.WriteLine("{0} {1} connected ...", username, OperationContext.Current.SessionId);
lock (m_concurrencyLock) {
m_clients[OperationContext.Current] = username;
}
}
///
/// Called when a client disconnects.
///
public void Disconnect() {
Console.WriteLine("{0} disconnected ...", OperationContext.Current.SessionId);
lock (m_concurrencyLock) {
if (m_clients.ContainsKey(OperationContext.Current)) {
m_clients.Remove(OperationContext.Current);
}
}
}
Meddela klienterna
Även fast vi tillåter många klienter samtidigt, så tillåts inte reentranta anrop, därför måste vi putta iväg den delen till en annan tråd, så att anropet till Say metoden får köras klart.
///
/// Called when client says a message
///
/// the message
public void Say(string message) {
Console.WriteLine("{0} said {1}", OperationContext.Current.SessionId, message);
// check for obscenity
if (message.Contains("fuck") || message.Contains("shit")) {
throw new FaultException(new ObscenityException("You cannot use such obscenities in this chat..."));
}
// this code sucks, but oh well ...
string user = "alien";
foreach (OperationContext ctx in m_clients.Keys) {
if (ctx.SessionId == OperationContext.Current.SessionId) {
user = m_clients[ctx];
}
}
// need to hand this off to another thread, otherwise we'll get a deadlock...
ThreadPool.QueueUserWorkItem(TellClients,
new UserMessagePair(user, message));
}
///
/// Called from a threadpool thread, notifies all clients about a new message
///
/// the message
private void TellClients(object state) {
UserMessagePair pair = state as UserMessagePair;
lock (m_concurrencyLock) {
foreach (OperationContext clientCtx in m_clients.Keys) {
IChatEvents callbackInterface = clientCtx.GetCallbackChannel();
try {
if (callbackInterface != null) {
callbackInterface.MessageReceived(new Message(pair.User, pair.Message));
}
} catch (Exception e) {
Console.WriteLine("Err: something bad happened {0}", e.Message);
}
}
}
}
#endregion
}
}
Service konfiguration
Det var själva kodbiten av det hela, sen är det bara en liten bit kvar, nämligen konfigureringen av servicen. Detta går naturligtvis även att göra inne i koden, men för att så att säga hålla dörrarna öppna är det bra att köra det från konfigurationsfilen.Därför pillar vi in följande i app.config
contract="LemonDesign.Chat.Contract.IChat"/>
contract="LemonDesign.Chat.Contract.IChat"/>
Contract attributet här är alltså satt till det kvalificerade namnet på vårt service gränssnitt. Name attributet anger namnet på service klassen som implementerar gränssittet.
Som ni ser kan en service ha flera så kallade endpoints, det vill säga adresser/protokoll som kan användas för att ta kontakt med servicen.
Avslutningsvis
Nu har vi alltså skapat ett service gränssnitt (i del 1), och skapat en service som implementerar detta gränssnitt. Då återstår bara att skapa en klient så att vi kan testa huruvida det funkar. Detta är då ämnet för nästa artikel.Återigen, frågor och kommentarer mottages tacksamt.
0 Kommentarer