Форум программистов, компьютерный форум, киберфорум
Наши страницы
LostCoast
Войти
Регистрация
Восстановить пароль
Рейтинг: 5.00. Голосов: 1.

Обучающая статья по использованию асинхронных сокетов. Регистрация.

Запись от LostCoast размещена 04.12.2012 в 10:01
Обновил(-а) LostCoast 17.12.2012 в 09:53
Метки c-sharp

Содержание:
  1. Базовые функции
    1. Введение
    2. Передача данных
    3. Регистрация
    4. Авторизация
    5. Оптимизация
  2. Возможности для клиентов
    1. Изменение архитектуры
    2. Передача сообщений между клиентами
    3. Статус клиентов
    4. Список пользователей
    5. Передача файлов

Регистрация.

В предыдущих статьях мы научились передавать и принимать данные. Настало время сделать из этого что-то стоящее.

Часть 1. Описание процесса регистрации.

Начнем с создания регистрации на сервере нового пользователя.
Сначала я объясню, как все будет работать, а после покажу код.
Регистрация будет происходить в несколько этапов:
  • Отправка запроса на регистрацию от клиента
  • Отправка ввода нового логина со стороны сервера
  • Отправка логина от клиента
  • Отправка ввода нового пароля со стороны сервера
  • Отправка пароля от клиента
  • Сохранение полученных данных на стороне сервера
Передавать и сохранять данные полученные от клиента мы будем в обычный текстовый файл (в идеале в базу данных, но для нашего простого приложения это лишнее) и рассмотрим виды шифрования, а именно SHA. Если кому-то больше нравится MD5, то никаких проблем нет, все делаться будет также.
Как будет происходить обмен данными, мы знаем. Теперь перейдем к описанию логики на стороне сервера. Нам надо будет сообщать серверу о том, что клиент хочет начать регистрацию, и начинать делать определенные действия, а именно, запускать процесс регистрации. Он будет происходить в одном методе. С помощью специальной переменной мы будем изменять ее состояние и, в зависимости от значения этой переменной, будет делать определенные действия. Также надо будет проверять, не существует ли уже клиент с отправленным логином, в случае ошибки, уведомлять клиента и запускать процесс регистрации заново.
Сообщать серверу о намерении регистрации будем с помощью сообщения _CMD_REGISTR . В качестве переключателя в ходе регистрации будет использовать переменную REGISTRATION типа enum.

Часть 2. Подготовка клиента.

Начнем с клиентской части, так как нам надо будет добавить не больше 5-10 строк. Добавим переменную в метод Main типа string и назовем ее cmd, и присвоим значение посылаемой команды - _CMD_REGISTR .

C#
1
string cmd = "_CMD_REGISTR";
Чтобы как то обезопасить свое приложение, будем передавать логин и пароль в зашифрованном виде. Все что вам нужно это добавить метод, приведенный ниже в класс Program:

C#
1
2
3
4
5
6
7
public static string ToSHA1(string str)
        {
            SHA1 sha1 = new SHA1CryptoServiceProvider();
            byte[] original = ASCIIEncoding.Default.GetBytes(str);
            byte[] encoding = sha1.ComputeHash(original);
            return BitConverter.ToString(encoding).Replace("-", String.Empty);
        }
Для работы с классом SHA1 необходимо добавить библиотеку Cryptography :

C#
1
using System.Security.Cryptography;
Расскажу, вкратце, что делает код. Первая строчка метода ToSHA1 инициализирует класс SHA1. Вторая, переводит переданную строку в массив байт. Третья, высчитывает хеш код полученных байт из строки. Четвертая, преобразует преобразованные байты в строку и удаляет символы “-“ из полученной строки.
Осталось внести изменения в методе Main. Давайте сделаем небольшое меню в консоли, чтобы мы могли выбрать регистрацию, авторизацию, отправку сообщений и выход их программы. Будем делать это с помощью switch и ввода цифр. Сначала выведем меню (все у вас было в методе Main, можно стереть). Первая часть:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
            Client client = new Client(GetIp(), 5555);
            client.Connect();
            string cmd = "_CMD_REGISTR";
            Console.WriteLine("Connection completed");
            Console.WriteLine("Menu:");
            Console.WriteLine("[1] Registration");
            Console.WriteLine("[2] Authorize");
            Console.WriteLine("[3] Send message");
            Console.WriteLine("[4] Send file");
            Console.WriteLine("[0] Exit");
            while (true)
            {
            }
А теперь в цикле реализуем switch, для начала будет работать только регистрация и выход:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch (Convert.ToInt32(Console.ReadLine()))
                {
                    case 1 :
                        client.Send(cmd);
                        string send_regist = Console.ReadLine();
                        client.Send(ToSHA1(send_regist));
                        send_regist = Console.ReadLine();
                        client.Send(ToSHA1(send_regist));
                        break;
                    case 0 :
                        client.Disconnect();
                        return;
                    default :
                        Console.WriteLine("Unkhown command");
                        break;
                }
Немного опишу, что мы делаем. Первое, это наш оператор switch преобразовывает введенную строку в число. Далее, разбирает ее по case’ам: 1 – отправляем серверу сообщение с командой, получаем ответ, вводим логин, отправляем, получаем ответ, вводим пароль, получаем ответ о состоянии: удачно или нет; 0 – просто выходим из нашего приложения.
На данном этапе нам больше не понадобятся изменения на стороне клиента. Перейдем к сложному и интересному.

Часть 3. Подготовка сервера.

Вот мы и добрались до интересной части. Для начала добавим несколько переменных в классе ServerHandler:

C#
1
2
3
4
5
6
7
8
9
private string login;
private string password;
private string cmd;        //хранит принятое сообщение отсервера
        
private int conn_status;   //статус подключения
private int regist_status;
private string REG = "_CMD_REGISTR";   //служит сигналом о начале регистрации клиента
private enum CONECTIONSTATUS : int { NO_AUTH, REGISTR, AUTH, OK };
private enum REGISTRATION : int { NO_REGISTR, PROMPTED_LOGIN, PROMPTED_PASS, RECEIVED, SAVED };
Переменные прокомментированы, поэтому пока что все ясно. У нас есть переменные для хранения логина и пароля, также есть переменная для хранения статуса регистрации, т.е. нам надо отслеживать, что было отправлено, чтобы знать, что отправлять дальше. Переменная PASSWORD нужна нам для удобства (это намного удобнее, чем создавать 6 отдельных переменных). Переменная CONNECTION нужна нам для выставления статуса подключения к серверу, т.е. будет некий переключатель между авторизацией, регистрацией и посылкой сообщений. Пока переменная conn_status не станет равно OK, сервер не будет принимать от сервера сообщений, только команды.
Добавим в конструктор класса ServerHandler инициализацию новых переменных:

C#
1
2
     this.regist_status = (int)REGISTRATION.NO_REGISTR;
     this.conn_status = (int)CONECTIONSTATUS.NO_AUTH;
Перейдем к методам, необходимым нам для всей регистрации. Начнем с определения, что к нам поступила команда на регистрацию. Добавим новый метод в классе ServerHandler и назовем его CheckReceiveData. Этот метод будет обрабатывать сообщения, если они равны нашим командам то будем присваивать им соответствующие значения, заметьте, что обработка будет производиться только при статусе подключения не равным OK :

C#
1
2
3
4
5
6
7
8
private void CheckReceiveData(string data)
        {
            if (this.conn_status != (int)CONECTIONSTATUS.OK)
            {
                if (data == REG)
                    this.conn_status = (int)CONECTIONSTATUS.REGISTR;
            }
        }
Готово. Надо немного изменить обработку сообщений в методе ReceiveCallBack. Спуститесь до проверки, что принятая строка не нулевая, т.е. вставляем ниже представленный код в тело условия if (!String.IsNullOrEmpty(cmd)), все что было до этого в операторе if нужно удалить:

C#
1
2
3
4
5
6
CheckReceiveData(cmd);
//проверки статусов, в зависимости от статуса подключения, делаем определенные действия
if (this.conn_status == (int)CONECTIONSTATUS.REGISTR)
    HandleRegistration(cmd, this.regist_status);
else if (this.conn_status == (int)CONECTIONSTATUS.OK)
    HandleReceiveData(cmd);
Как вы видите, мы добавили вызов двух новых метод. Тот, который принимает один аргумент, содержит вывод сообщения и вызов метода Receive, поэтому начнем с него. Код:


C#
1
2
3
4
5
private void HandleReceiveData(string data)
        {
            Console.WriteLine("RECEIVED: " + data);
            Receive();
        }
Метод, принимающий два аргумента, будет выполнять всю логику регистрации. Если сразу не получится понять, как работает код, ничего страшного, просто попробуйте по изменять его и посмотреть, что из этого выйдет. Постараюсь объяснить его как можно подробней.
Вся суть метода будет заключаться в изменении состояния переменной regist_status и посылки определенных сообщений клиенту. Приведу пока что первую часть кода. Создадим сам метод и напишем отправку сообщения с вводом нового логина. Код:

C#
1
2
3
4
5
6
7
8
9
private void HandleRegistration(string data, int status)
        {
            if (status == (int)REGISTRATION.NO_REGISTR)
            {
                this.regist_status = (int)REGISTRATION.PROMPTED_LOGIN;
                Send("A new login: ");
                return;
            }
        }
Можете запустить сервер и просмотреть, что будет прислано клиенту, когда он отправит запрос на регистрацию.
Думаю, после моих объяснений не сложно будет догадаться, как метод будет устроен дальше. Добавим оправку пароля в метод. После оператора условия добавьте следующий код:

C#
1
2
3
4
5
6
7
if (status == (int)REGISTRATION.PROMPTED_LOGIN)
            {
                this.login = data;
                this.regist_status = (int)REGISTRATION.PROMPTED_PASS;
                Send("A new password: ");
                return;
            }
Нетрудно заметить, что теперь в операторе if идет сравнение с тем статусом, который мы задали в предыдущем шаге. Не проваливаться сразу по всем операторам if нам позволяет вызов return. Он вызывает выход из метода, тем самым блокирует дальнейшие действия метода.
Осталось обработать полученные данные и записать их в файл для хранения. Допишем обработку регистрации. Добавьте код ниже после последнего оператора if:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (status == (int)REGISTRATION.PROMPTED_PASS)
            {
                this.password = data;
                this.regist_status = status = (int)REGISTRATION.RECEIVED;
            }
            if (status == (int)REGISTRATION.RECEIVED)
            {
                if (SaveNewUser(this.login, this.password))
                {
                    this.regist_status = (int)REGISTRATION.SAVED;
                    this.conn_status = (int)CONECTIONSTATUS.NO_AUTH;
                    Console.WriteLine("New user registered");
                    Send("OK\n\r");
                }
                else
                {
                    this.regist_status = (int)REGISTRATION.NO_REGISTR;
                    this.conn_status = (int)CONECTIONSTATUS.NO_AUTH;
                    Send("LOGIN EXISTS!\n\r");
                }
            }
Здесь мы продолжаем проверять статусы и присваивать им новые значения. Как только доходим до последнего шага, необходимо сделать проверку на существующий логин. Проверка будет представлять собой открытие файла с логинами и пароля, и поиск по нему указанного логина. Логин и пароль в файле будет записываться как <логин>:<пароль>. Метод HandlerRegistretion закончен, но он вызывает метод SaveNewUser.
Метод SaveNewUser сначала ищет существующий логин, а после записывает данные в файл. Код:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private bool SaveNewUser(string login, string pass)
        {
            try
            {
                if (!CheckUser(login, pass))
                {
                    using (StreamWriter sw = new StreamWriter(new FileStream("users.txt", FileMode.Append, FileAccess.Write)))
                    {
                        sw.WriteLine(String.Format("{0}:{1}", login, pass));
                    }
                    return true;
                }
                else
                {
                    Console.WriteLine("A new user already exists");
                    return false;
                }
                
            }
            catch (Exception) { return false; }
        }
Рассмотрим метод CheckUser чуть позже. Наш метод открывает файл для записи и добавляет туда новую запись. Для открытия файла я использую класс StreamWriter. Для тех кто не знаком с использованием конструкции using () { }, расскажу в двух словах о ней. Конструкция using в теле метода, а не в начале файла, позволяет автоматически освобождать выделенные ресурсы под открытый файл, или еще что-нибудь. В данном случае, после выполнения записи в файл, будет автоматически вызван метод Close для открытого файла. Это очень удобный способ. Мы будем всегда знать, что наш файл закрыт после использования и у нас не произойдет никаких ошибок в дальнейшем. Также в методе используется блок try catch, советую его использовать при работе с файлами, так как с ними очень часто возникают ошибки. Если у нас произошла ошибка при записи в файл, то возвращаем false из нашего метода.
Осталось рассмотреть метод CheckUser. Он выполняет проверку по файлу, а именно вытаскивает каждую строку из файла, вырезает логин, и сравнивает его с полученным от пользователя логином. Код:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private bool CheckUser(string login, string pass)
        {
            try
            {
                using (StreamReader sr = new StreamReader(new FileStream("users.txt", FileMode.Open, FileAccess.Read)))
                {
                    while (!sr.EndOfStream)
                    {
                        string handler = sr.ReadLine();
                        string flogin = handler.Substring(0, handler.IndexOf(":"));
                        string fpass = handler.Substring(handler.IndexOf(":") + 1, handler.Length - handler.IndexOf(":") - 1);
                        if (login == flogin)
                          return true;
                    }
                    return false;
                }
            }
         catch (FileNotFoundException) { return false; }
            catch (Exception ex) { Console.WriteLine(ex.ToString()); return true; }
        }
В метод, вместо StreamWriter используется StreamReader. Этот класс позволяет получать данные, хранящиеся в файле. После того как мы открыли файл, запускается цикл, условием выхода из которого является достижения конца файла. В цикле мы берем строку из файла и с помощью Substring вырезаем логин из полученной строки. Далее происходит сравнение логинов. Если нашли существующий логин, то возвращаем true и выходим из метода. Также как и в предыдущем методе, мы обрамляем тело метода блоком try catch и в случае ошибки возвращаем true, чтобы ничего не записалось в файл неправильного, в случае отсутствия файла, возвращаем false, чтобы в методе SaveNewUser создался новый файл.
На этом этап регистрация нового пользователя заканчивается. Для проверки запустите сервер и попытайтесь с клиента зарегистрироваться. Заодно, посмотрите, как будет себя вести сервер и, что будет записываться в файл на стороне сервера.
Размещено в Без категории
Просмотров 1717 Комментарии 1
Всего комментариев 1
Комментарии
  1. Старый комментарий
    Аватар для Nowsoud

    Хеширование

    Всё прекрасно, с одной оговоркой.
    SHA - алгоритм хеширования а не шифрования.
    То есть, мы не можем расшифровать хеш, в отличии от шифра.
    А в остальном всё клёво.
    Запись от Nowsoud размещена 10.07.2015 в 18:01 Nowsoud вне форума
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru