На днях решил глубже погрузится в технологию 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, идея была
взята из примера с , но пример
мне не понравился поэтому я решил все с нуля начать и немного по другому. Приятного
вам программирования с технологией WCF =)