1 2 3 4 5 6 7 8 9 10 Створення керуючого процесуВ якості керуючого процесу створено консольний додаток. Код представлено нижче: namespace ControlProcess { using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Threading; using NamedPipeWrapper; using Сommon; public static class Program { /// /// Путь процессу своего поля /// private const string OwnFieldProcessName = @"..\..\..\OwnField\bin\Debug\OwnField.exe"; /// /// Путь к процессу своего поля /// private const string EnemyFieldProcessName = @"..\..\..\EnemyField\bin\Debug\EnemyField.exe"; /// /// Массив процессов /// private static readonly Process[] Process = new Process[] { new Process { StartInfo = new ProcessStartInfo( OwnFieldProcessName, $"1") }, new Process { StartInfo = new ProcessStartInfo( OwnFieldProcessName, $"2") }, new Process { StartInfo = new ProcessStartInfo( EnemyFieldProcessName, $"1") }, new Process { StartInfo = new ProcessStartInfo( EnemyFieldProcessName, $"2") } }; /// /// Именнованый канал /// private static readonly NamedPipeServer PipeServer = new NamedPipeServer ("SeaBattle"); /// /// Ходы игрока 1 для передачи по каналу /// private static readonly List /// /// Ходы игрока 2 для передачи по каналу /// private static List /// /// Корабли игрока 1 для передачи по каналу /// private static readonly List /// /// Корабли игрока 2 для передачи по каналу /// private static readonly List /// /// Мьютекс для синхронизации /// private static readonly Mutex Mtx = new Mutex(); /// /// Корабли игрока 1 /// private static List /// /// Корабли игрока 2 /// private static List /// /// Игровое поле игрока 1 /// private static bool[,] gameFieldPlayer1; /// /// Игровое поле игрока 2 /// private static bool[,] gameFieldPlayer2; /// /// Игрок 1 закончил расстановку /// private static bool done1 = false; /// /// Игрок 2 закончил расстановку /// private static bool done2 = false; /// /// Очередь хода /// private static int orderMove = 0; /// /// Убито корблей игроком 1 /// private static int kill1 = 0; /// /// Убито корблей игроком 2 /// private static int kill2 = 0; /// /// Имеем победителя /// private static bool hasWinner = false; /// /// Обработчик закрытия консольного приложения /// private static SignalHandler signalHandler; // Главная функция public static void Main(string[] args) { // подпись на события закрытия консоли signalHandler += HandleConsoleSignal; ConsoleHelper.SetSignalHandler(signalHandler, true); // подпись на события каналов PipeServer.ClientConnected += PipeServerOnClientConnected; PipeServer.ClientMessage += PipeServerOnClientMessage; PipeServer.ClientDisconnected += PipeServerOnClientDisconnected; PipeServer.Start(); // запуск канала Console.WriteLine("Сервер запущен."); Console.WriteLine(); // запуск процессов for (int i = 0; i < Process.Length; i++) { Process[i].Start(); Console.WriteLine($"Процесс {i + 1} запущен."); } Console.WriteLine(); Console.WriteLine("Для закрытия нажмите любою клавишу..."); Console.WriteLine(); Console.ReadKey(); Console.WriteLine(); // уничтожение процессов и остановка сервера for (int i = 0; i < Process.Length; i++) { Process[i].Kill(); Console.WriteLine($"Процесс {i + 1} закрыт."); } Console.WriteLine(); PipeServer.Stop(); Console.WriteLine($"Сервер остановлен."); Thread.Sleep(1000); } // событие завершение работы консольного приложения private static void HandleConsoleSignal(ConsoleSignal consoleSignal) { // уничтожение процессов и остановка сервера for (int i = 0; i < Process.Length; i++) { Process[i].Kill(); Console.WriteLine($"Процесс {i + 1} закрыт."); } } // прием сообщений от клиентов private static void PipeServerOnClientMessage(NamedPipeConnection connection, PipeMessage message) { switch (message.PipeMessageType) { case PipeMessageType.EndArrange: EndArrange(message); break; case PipeMessageType.Update: Update(connection, message); break; case PipeMessageType.SetMove: SetMove(connection, message); break; } } // установка хода private static void SetMove(NamedPipeConnection connection, PipeMessage message) { // если у нас есть победитель if (hasWinner) { // отправка сообщения connection.PushMessage( new PipeMessage() { PlayerId = 0, PipeMessageType = PipeMessageType.Winn }); return; } // нет очереди хода if (orderMove == 0) { Console.WriteLine("Расстановка кораблей не закончена!"); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Error, Message = $@"Расстановка кораблей не закончена!" }); return; } // первый игрок не готов if (!done1) { Console.WriteLine("Игрок 1 не готов!"); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Error, Message = @"Игрок 1 не готов!" }); return; } // второй игрок не готов if (!done2) { Console.WriteLine("Игрок 2 не готов!"); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Error, Message = @"Игрок 2 не готов!" }); return; } // ход первого игрока if (message.PlayerId == 1) { Console.WriteLine("Ходит игрок 1"); // если не его очередь if (orderMove != 1) { Console.WriteLine($@"Очередь ходить игрока {orderMove}"); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Error, Message = $@"Очередь ходить игрока {orderMove}" }); } else { // если ход уже был if (MoveData1.Contains(message.Index)) { Console.WriteLine($"Ход {message.Index.ToString()} уже был"); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Error, Message = $"Ход {message.Index.ToString()} уже был" }); return; } // получение типа хода var moveType = gameFieldPlayer2[message.Index.X, message.Index.Y] ? MoveType.Cross : MoveType.Circle; // добавление в коллекцию ходов MoveData1.Add(new MoveData(new Point(message.Index.X, message.Index.Y), moveType)); // проверка убийства корабля CheckKillShip(1, shipsPlayer2, MoveData1); connection.PushMessage( new PipeMessage() { PlayerId = 1, PipeMessageType = PipeMessageType.Update, MoveType = moveType, Index = message.Index, ShipData = ShipData1 }); Console.WriteLine($"Ход {message.Index.ToString()} {moveType.ToStringName().ToLower()}"); orderMove = moveType == MoveType.Cross ? 1 : 2; if (HaveWinner()) { Console.WriteLine($"Ход {message.Index.ToString()} приносит игроку 1 победу"); connection.PushMessage( new PipeMessage() { PlayerId = 1, PipeMessageType = PipeMessageType.Winn }); return; } Console.WriteLine($"Очередь ходить игрока {orderMove}"); } } else if (message.PlayerId == 2) { Console.WriteLine("Ходит игрок 2"); if (orderMove != 2) { Console.WriteLine($@"Очередь ходить игрока {orderMove}"); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Error, Message = $@"Очередь ходить игрока {orderMove}" }); } else { if (MoveData2.Contains(message.Index)) { Console.WriteLine($"Ход {message.Index.ToString()} уже был"); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Error, Message = $@"Ход уже был" }); return; } var moveType = gameFieldPlayer1[message.Index.X, message.Index.Y] ? MoveType.Cross : MoveType.Circle; MoveData2.Add(new MoveData(new Point(message.Index.X, message.Index.Y), moveType)); CheckKillShip(2, shipsPlayer1, MoveData2); connection.PushMessage( new PipeMessage() { PlayerId = 2, PipeMessageType = PipeMessageType.Update, MoveType = moveType, Index = message.Index, ShipData = ShipData2 }); Console.WriteLine($"Ход {message.Index.ToString()} {moveType.ToStringName().ToLower()}"); orderMove = moveType == MoveType.Cross ? 2 : 1; if (HaveWinner()) { Console.WriteLine($"Ход {message.Index.ToString()} приносит игроку 2 победу"); connection.PushMessage( new PipeMessage() { PlayerId = 2, PipeMessageType = PipeMessageType.Winn }); return; } Console.WriteLine($"Очередь ходить игрока {orderMove}"); } } } // проверка есть ли победитель (убиты все возможные корабли) private static bool HaveWinner() { if (kill1 == Constants.ShipCount() || kill2 == Constants.ShipCount()) hasWinner = true; return hasWinner; } // проверка убит ли корабль и добавление его в коллекцию для передачи private static void CheckKillShip(int playerId, List { RecalculateShipData(ships, moveData); foreach (var ship in ships) { if (ship.IsKilled) { if (playerId == 1 && !ShipData1.Contains(ship.PastePoint)) { ShipData1.Add(new ShipData(ship.PastePoint, ship.ShipType, ship.ShipOrientation)); kill1++; Console.WriteLine($"Игрок 1 потопил {ship.ShipType.ToStringName().ToLower()} противника"); Console.WriteLine($"Утоплено кораблей {kill1}"); } else if (playerId == 2 && !ShipData2.Contains(ship.PastePoint)) { ShipData2.Add(new ShipData(ship.PastePoint, ship.ShipType, ship.ShipOrientation)); kill2++; Console.WriteLine($"Игрок 2 потопил {ship.ShipType.ToStringName().ToLower()} противника"); Console.WriteLine($"Утоплено кораблей {kill2}"); } } } } // перерасчет точек кораблей private static void RecalculateShipData(List { var find = false; foreach (var move in moveData) { if (move.MoveType != MoveType.Cross) continue; foreach (var ship in ships.ToList()) { // if (ship.ShipPoints.All(x => x != move.Point)) break; foreach (var shipPoint in ship.ShipPoints.ToList()) { if (shipPoint == move.Point) { ship.ShipPoints.Remove(shipPoint); find = true; } if (find) break; } if (find) break; } if (find) break; } } // обновление данных private static void Update(NamedPipeConnection connection, PipeMessage message) { if (message.PlayerId == 1 && MoveData2.Count != 0) { Mtx.WaitOne(); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Update, PlayerId = 1, MoveData = MoveData2 }); Mtx.ReleaseMutex(); } else if (message.PlayerId == 2 && MoveData1.Count != 0) { Mtx.WaitOne(); connection.PushMessage( new PipeMessage() { PipeMessageType = PipeMessageType.Update, PlayerId = 2, MoveData = MoveData1 }); Mtx.ReleaseMutex(); } } // окончание игроком расстановки кораблей private static void EndArrange(PipeMessage message) { if (message.PlayerId == 1 && !done1) { gameFieldPlayer1 = message.GameField; shipsPlayer1 = message.ShipData.ToShipList().ToList(); Console.WriteLine($"Получена расстановка кораблей игрока {message.PlayerId}"); if (orderMove == 0) { orderMove = 1; Console.WriteLine($"Первый ходит игрок {message.PlayerId}"); } done1 = true; } else if (message.PlayerId == 2 && !done2) { gameFieldPlayer2 = message.GameField; shipsPlayer2 = message.ShipData.ToShipList().ToList(); Console.WriteLine($"Получена расстановка кораблей игрока {message.PlayerId}"); if (orderMove == 0) { orderMove = 2; Console.WriteLine($"Первый ходит игрок {message.PlayerId}"); } done2 = true; } } /// /// Подключения пользователя к серверу /// private static void PipeServerOnClientConnected(NamedPipeConnection connection) { /*if (connection.Id > 2) return; Console.WriteLine($"Игрок {connection.Id} подключен к серверу.");*/ } /// /// Отклчюение пользователя от сервера /// private static void PipeServerOnClientDisconnected(NamedPipeConnection connection) { /*if (connection.Id > 2) return; Console.WriteLine($"Игрок {connection.Id} отключен от сервера.");*/ } } } Результати роботи програмиРезультати роботи додатку показані на рис. 4-6 . Рис. 4. Зовнішній вигляд додатку Рис. 5. Розміщення кораблів Рис. 6. Хід гри Висновок Потік (thread) являє собою незалежну послідовність інструкцій в програмі. Потоки грають важливу роль як для клієнтських, так і для серверних додатків. Наприклад, під час введення якогось коду C # у вікні редактора Visual Studio проводиться аналіз на предмет різних синтаксичних помилок. Цей аналіз здійснюється окремим фоновим потоком. Те ж саме відбувається і в засобі перевірки орфографії в Microsoft Word. Один потік очікує введення даних користувачем, а інший в цей час виконує у фоновому режимі деякий аналіз. Третій потік може зберігати записуються дані в тимчасовий файл, а четвертий - завантажувати додаткові дані з Інтернету. У додатку, який функціонує на сервері, один потік завжди очікує надходження запиту від клієнта і тому називається потоком-слухачем (listener thread). При отриманні запиту він відразу ж пересилає його окремому робочому потоку (worker thread), який далі сам продовжує взаємодіяти з клієнтом. Потік-слухач після цього повинен бути відшкодований до своїх обов'язків по очікуванню надходження наступного запиту від чергового клієнта. Кожен процес складається з ресурсів, таких як віконні дескриптори, файлові дескриптори і інші об'єкти ядра, має виділену область у віртуальній пам'яті і містить як мінімум один потік. Потоки плануються до виконання операційною системою. У будь-якого потоку є пріоритет, лічильник команд, який вказує на місце в програмі, де відбувається обробка, і стек, в якому зберігаються локальні змінні потоку. Стек у кожного потоку виглядає по-своєму, але пам'ять для програмного коду і купа поділяються серед всіх потоків, які функціонують усередині одного процесу. Це дозволяє потокам всередині одного процесу швидко взаємодіяти між собою, оскільки всі потоки процесу звертаються до однієї і тієї ж віртуальної пам'яті. Однак це також і ускладнює справу, оскільки дає можливість безлічі потоків змінювати одну і ту ж область пам'яті. Список використаної літератури 1. Побегайло А. «Системное программирование в Windows». СПб.: БХВ-Петербург, 2006. 1056 с. 2. Шилдт Г. Полное руководство по С# 4.0.: Пер. с англ. - М.: ООО «И. Д. Вильямc», 2012. - 1047 с. 3. Джеффри Рихтер «Программирование на платформе Microsoft.NET Framework 4.5 на языке C#». 4. Потоки (threads) и многопоточное выполнение программ, http://www.intuit.ru/studies/courses/641/497/lecture/11284?page=1 5. Многопоточность и синхронизация, https://professorweb.ru/my/csharp/thread_and_files/1/1_7.php 6. Общие сведения о платформе .NET Framework, https://msdn.microsoft.com/ 7. Преимущества многопоточности, https://www.opennet.ru/docs/RUS/ipcbook/node43.html 8. Многопоточность в ООП, https://habrahabr.ru/post/164487/ 9. Пространство имен System.Threading. https://msdn.microsoft.com/ ru-ru/ library/ system.threading(v=vs.110).aspx 10. Класс Thread. https://msdn.microsoft.com/ru-ru/library/system. threading.thread(v=vs.110).aspx Класс Process. https://msdn.microsoft.com/ru-ru/library/system. diagnostics.process(v=vs.110).aspx Синхронизация потоков (C# и Visual Basic). https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms173179 (v=vs.120). aspx 1 2 3 4 5 6 7 8 9 10 |