На днях решил глубже погрузится в технологию Windows Communication Foundation (сокращенно — WCF). Не буду рассказывать что это такое и с чем его едят, в интернете полно информации. Я вам лучше расскажу о том как сделать самый простейший чат. Что же нужно нам для чата. Во-первых сервер должен уметь оповещать клиента о том что кто-то написал сообщение в чат, о входе\выходе пользователей. Во-вторых сервер должен различать от какого клиента пришел вызов и какому отправить. Для реализации этих двух важных составляющих, канал должен поддерживать обратный вызов (duplex) и сессии (session). Поэтому для нас подходит NetTcpBinding. Запускаем студию, создаем новое решение (new Solution)->Консольное приложение (назвал его ChatService). Сразу же добавляем ссылку на сборку System.ServiceModel. Первое что нужно сделать, это описать интерфейс сервиса. Интерфейсу добавляем атрибут:

    [ServiceContract(SessionMode = SessionMode.Required)]
    

так как он обязательно должен поддерживать сессии.

        [OperationContract(IsInitiating = true, IsOneWay = false, IsTerminating = false)]
       

Метод начинающий связь с сервисом:

        String[] Join(string name)
        

Рассмотрим параметры атрибута OperationContract. IsInitiating – если true тогда связь с сервисом и сессия начинается, создается объект реализации на сервисе и запускается его конструктор. IsOneWay – если true данный метод ничего не возвращает и только в одну сторону. IsTerminating – значение true этого параметра приводит к тому что по окончанию его обработки на сервисе связь с клиентом прерывается и сессия закрывается, т.е. последующие обращения к сервису приведут к ошибкам.

Вот так объявил я остальные функции интерфейса:

        [OperationContract(IsInitiating = false, IsOneWay = true, IsTerminating = false)]
        void Send(string msg);

        [OperationContract(IsInitiating = false, IsOneWay = true, IsTerminating = false)]
        void SendPrivate(string name, string msg);

        [OperationContract(IsInitiating = false, IsOneWay = true, IsTerminating = true)]
        void Leave();
    

Далее добавляем новый интерфейс для обратной связи с клиентом:

        interface IChatCallback { 

        [OperationContract(IsOneWay = true)] 
        void Receive(string name, string msg); 

        [OperationContract(IsOneWay = true)] 
        void ReceivePrivate(string name, string msg); 

        [OperationContract(IsOneWay = true)] 
        void UserEnter(string name);

        [OperationContract(IsOneWay = true)] 
        void UserLeave(string name); 
        }
    

А в параметры аттрибуту IChat добавляем

        “CallbackContract = typeof(IChatCallback)”.
        

Теперь можно приступить непосредственно к реализации сервиса. Добавляем новый класс, называем ChatService. Добавляем ему аттрибут:

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)].    
    

Параметр = InstanceContextMode.PerSession означает что сервис поддерживает сессии. ConcurrencyMode.Multiple – на сколько я понял, механизм WCF таким образом создает сервисы в различных потоках и сам регулирует нагрузку чтобы все клиенты паралельно обрабатывались, а не создавали очередь. В классе я объявляю статический лист, чтобы сервис мог оповещать всех клиентов:

        private static List<ChatUser> _chatUsers = new List<ChatUser>();
     

а также самого клиента (сервис ведь должен себя идентифицировать):

     private ChatUser _user;
     

Приступим к реализации метода Join:

     //Получаем Интерфейс обратного вызова
    IChatCallback callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();
    //Массив всех участников чата, который мы вернем клиенту
    string[] tmpUsers = new string[_chatUsers.Count];
     for (int i = 0; i < _chatUsers.Count; i++)
     {
         tmpUsers[i] = _chatUsers[i].Name;
     }
     //Оповещаем всех клиентов что в чат вощел новый пользователь
    foreach (ChatUser user in _chatUsers)
    {
    user.Callback.UserEnter(name);
     }
     //Создаем новый экземплар пользователя и заполняем все его поля
    ChatUser chatUser = new ChatUser() { Name = name, Callback = callback };
    _chatUsers.Add(chatUser);
    _user = chatUser;
    Console.WriteLine(">>User Enter: {0}", name);
    return tmpUsers;
     

Метод выхода пользователя из чата Leave:

    _chatUsers.Remove(_user);
     //Оповещаем всех клиентов о том что пользователь нас покинул
    foreach (ChatUser item in _chatUsers)
    {
        item.Callback.UserLeave(_user.Name);
    }
    _user = null;
    //Закрываем канал связи с текущим пользователем
    OperationContext.Current.Channel.Abort();
    

Прием и рассылка сообщений Send:

    var usersSending = from u in _chatUsers
    where u.Name != _user.Name
    select u;
    foreach (ChatUser item in usersSending)
    {
        item.Callback.Receive(_user.Name, msg);
    }
    

Сервис мы создали, теперь остается его запустить:

        //Создаем непосредственно сам Хост
        ServiceHost host = new ServiceHost(typeof(ChatService));
        NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
        Uri adress = new Uri("net.tcp://localhost:20010/ChatService");
        host.AddServiceEndpoint(typeof(IChat), binding, adress.ToString());
        //Открываем порт и сервис ожидает клиентов
        host.Open();
        Console.WriteLine("Service running...");
        Console.ReadKey();
        host.Close();
    

Запустим, проверим работает ли сервис. Если все хорошо, тогда приступим к созданию клиента. Создадим новое консольное приложение, назовем его ChatClient. Добавляем новый класс:

    class ChatCallbackHandler: IChatCallback
    {
     public void Receive(string name, string msg)
     { Console.WriteLine("{0}: {1}", name, msg); }
     public void ReceivePrivate(string name, string msg)
     { Console.WriteLine("{0} private: {1}", name, msg); }
     public void UserEnter(string name)
     { Console.WriteLine("User enter: {0}", name); }
     public void UserLeave(string name)
     { Console.WriteLine("User leave: {0}", name); }
    }
     

Соединяемся с сервером и отправляем сообщения:

        //Создаем объект который отвечает за обратную связь
        InstanceContext context = new InstanceContext(new ChatCallbackHandler());
        NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
        DuplexChannelFactory<IChat> factory = new DuplexChannelFactory<IChat>(context, binding);
        Uri adress = new Uri("net.tcp://localhost:20010/ChatService");
        EndpointAddress endpoint = new EndpointAddress(adress.ToString());
        //Связь с сервером не устанавливается до тех пор, пока не будет вызван метод Join
        IChat chat = factory.CreateChannel(endpoint);
        Console.Write("Enter you name: ");
        string name = Console.ReadLine();
        string[] userInChat = chat.Join(name);
        foreach (string item in userInChat)
        {
        Console.WriteLine("User in chat: {0}", item);
        }
        ...
        chat.Leave();
      

Вот собственно и все! Пользуйтесь. Я не специалист в области WCF, идея была взята из примера с CodeProject, но пример мне не понравился поэтому я решил все с нуля начать и немного по другому. Приятного вам программирования с технологией WCF =)