1

Тема: C# і MySQL Connector. Питання до богів програмування!

Привіт, милий друже, який вирішив допомогти заблукавшій душі в цьому шаленому і воістину незбагненному світі. Мається сервер, який асинхронно виконує методи, котрі здійснюють підключення до бази даних MySQL. Сама структура коду така: спочатку створюється об'єкт для підключення до бази даних, в асинхронних методах відкривається з'єднання з базою даних використовуючи об'єкт для підключення, далі виконується запит і з'єднання закривається.
Проблема: при роботі декількох методів створюється виняток, який твердить, що з'єднання, яке ми намагаємося відкрити в методі номер 2, вже відкрито в методі номер 1.
Питання: як убезпечити себе від такої проблеми?
Примітка: до сервера підключається клієнт і їх може бути багато, і запити від кожного клієнта обробляються в асинхронних методах в яких і відкривається з'єднання до бази даних.

2

Re: C# і MySQL Connector. Питання до богів програмування!

Існує більше одного способу асинхронно виконувати методи, що звертаються до БД. І, на превеликий жаль, я не телепат... і взагалі тут телепатів нема. Жодного.
Або код у студію - або ніц не буде.
Ну і про м'ютекси пошукайте щось...

3

Re: C# і MySQL Connector. Питання до богів програмування!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Net;
using System.Threading;
using System.Net.Sockets;
using MySql.Data.MySqlClient;

namespace ConsoleApplication3
{
    class Program
    {
        class ThreadedServer
        {
            private static int numberOfConnections;
            private Socket _serverSocket;
            private int _port;
            private string db_connect = "Database=supercourse;Data Source=localhost;User Id=root;Password=qwerasdf";
            private MySqlConnection myConnection;
            private MySqlConnection myConnection2;
            private int id;
            public ThreadedServer(int port)
            {
                _port = port;
                numberOfConnections = 0;
            }
            public void Start()
            {
                myConnection = new MySqlConnection(db_connect);
                myConnection2 = new MySqlConnection(db_connect);
                SetupServerSocket();
                for (int i = 0; i < 10; i++)
                    _serverSocket.BeginAccept(new
                    AsyncCallback(AcceptCallback), _serverSocket);
            }
            private class ConnectionInfo
            {
                public Socket Socket;
                public byte[] Buffer;
                public int user_id;
                public int char_id;
                public int id;
                public string nick;
                public float x;
                public float y;
                public float z;
            }
            private List<ConnectionInfo> _connections = new List<ConnectionInfo>();
            private void SetupServerSocket()
            {
                // Получаем информацию о локальном компьютере
                IPHostEntry localMachineInfo =
                Dns.GetHostEntry(Dns.GetHostName());
                IPEndPoint myEndpoint = new IPEndPoint(IPAddress.Any, _port);
                // Создаем сокет, привязываем его к адресу
                // и начинаем прослушивание
                _serverSocket = new Socket(
                myEndpoint.Address.AddressFamily,
                SocketType.Stream, ProtocolType.Tcp);
                _serverSocket.Bind(myEndpoint);
                _serverSocket.Listen((int)
                SocketOptionName.MaxConnections);
            }
            private void AcceptCallback(IAsyncResult result)
            {
                ConnectionInfo connection = new ConnectionInfo();
                try
                {
                    // Завершение операции Accept
                    Console.WriteLine("Подключился чел");
                    Socket s = (Socket)result.AsyncState;
                    connection.Socket = s.EndAccept(result);
                    connection.Buffer = new byte[256];
                    connection.id = _connections.Count() + 1;
                    lock (_connections) _connections.Add(connection);
                    // Начало операции Receive и новой операции Accept
                    connection.Socket.BeginReceive(connection.Buffer,
                    0, connection.Buffer.Length, SocketFlags.None,
                    new AsyncCallback(ReceiveCallback),
                    connection);
                    _serverSocket.BeginAccept(new AsyncCallback(
                    AcceptCallback), result.AsyncState);
                }
                catch (SocketException exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("95 Socket exception: " +
                    exc.SocketErrorCode);
                }
                catch (Exception exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("Exception: " + exc);
                }
            }
            private void ReceiveCallback(IAsyncResult result)
            {
                byte[] b;
                ConnectionInfo connection =
                (ConnectionInfo)result.AsyncState;
                try
                {
                    int bytesRead =
                    connection.Socket.EndReceive(result);
                    if (0 != bytesRead)
                    {
                        Console.WriteLine("Receive: Bytes: " + bytesRead);
                        lock (_connections)
                        {
                            string req = Encoding.UTF8.GetString(connection.Buffer, 0, 3);
                            switch (req)
                            {
                                case "lgn":
                                    Console.WriteLine("send req to db");
                                    b = LogIn(connection.Buffer);
                                    connection.Socket.Send(b);
                                    break;
                                case "gcr":
                                    b = GetChars(connection.Buffer);
                                    connection.Socket.Send(b);
                                    break;
                                case "crt":
                                    b = CreateChar(connection.Buffer);
                                    connection.Socket.Send(b);
                                    break;
                                case "dlt":
                                    b = DeleteChar(connection.Buffer);
                                    connection.Socket.Send(b);
                                    break;
                            }
                            /* else
                            foreach (ConnectionInfo conn in _connections)
                            {
                            if (conn != connection)
                            {
                            conn.Socket.Send(connection.Buffer, bytesRead, SocketFlags.None);
                            Console.WriteLine("bytes sent: "+bytesRead);
                            }
                            }*/
                        }
                        connection.Socket.BeginReceive(
                        connection.Buffer, 0,
                        connection.Buffer.Length, SocketFlags.None,
                        new AsyncCallback(ReceiveCallback),
                        connection);
                    }
                    else CloseConnection(connection);
                }
                catch (SocketException exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("216 Socket exception: " +
                    exc.SocketErrorCode);
                }
                catch (Exception exc)
                {
                    CloseConnection(connection);
                    Console.WriteLine("Exception: " + exc);
                }
            }
            private void CloseConnection(ConnectionInfo ci)
            {
                ci.Socket.Close();
                lock (_connections) _connections.Remove(ci);
                numberOfConnections--;
            }
            byte[] LogIn(byte[] b)
            {
                byte[] bf = new byte[32];
                string log = Encoding.UTF8.GetString(b, 10, BitConverter.ToInt32(b, 50));
                string psw = Encoding.UTF8.GetString(b, 30, BitConverter.ToInt32(b, 55));
                string request = "call SP_LogIn(" + "'" + log + "'" + "," + "'" + psw + "'" + ")";
                MySqlCommand myCommand = new MySqlCommand(request, myConnection);
                Int32 value = 0;
                try
                {
                    Console.WriteLine("trying open connection to db");
                    myConnection.Open();
                    value = Int32.Parse(myCommand.ExecuteScalar().ToString());
                    myConnection.Close();
                    Console.WriteLine("value = " + value);
                    Console.WriteLine("done sending req to bd. Value = " + value);
                }
                catch (Exception e)
                {
                    myConnection.Close();
                    Console.WriteLine(e.Message);
                }
                Encoding.UTF8.GetBytes("lgn").CopyTo(bf, 0);
                BitConverter.GetBytes(value).CopyTo(bf, 10);
                return bf;
            }
            byte[] GetChars(byte[] b)
            {
                byte[] bf = new byte[1024];
                int i = 0;
                string request = "call SP_GetChars(" + BitConverter.ToInt32(b, 3) + ")";
                MySqlCommand myCommand = new MySqlCommand(request, myConnection);
                try
                {
                    myConnection.Open();
                    MySqlDataReader MyDataReader;
                    MyDataReader = myCommand.ExecuteReader();
                    while (MyDataReader.Read())
                    {
                        int id = MyDataReader.GetInt32(0);
                        string name = MyDataReader.GetString(1);
                        Encoding.UTF8.GetBytes(name).CopyTo(bf, 10 + (i * 30));
                        BitConverter.GetBytes(id).CopyTo(bf, 30 + (i * 30));
                        i++;
                        // Console.WriteLine("id = "+id+" : name = "+name);
                    }
                    MyDataReader.Close();
                    myConnection.Close();
                    Console.WriteLine("i = " + i);
                    BitConverter.GetBytes(i).CopyTo(bf, 3);
                    Encoding.UTF8.GetBytes("gcr").CopyTo(bf, 0);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    myConnection.Close();
                }
                Console.WriteLine("sent chars = " + bf.Length + "bytes and i = " + i);
                return bf;
            }
            byte[] CreateChar(byte[] b)
            {
                byte[] bf = new byte[8];
                int id = BitConverter.ToInt32(b, 3);
                string name = Encoding.UTF8.GetString(b, 10, 20);
                string req = "call SP_CreateChar('" + id + "','" + name + "')";
                MySqlCommand command = new MySqlCommand();
                command.CommandText = req;
                command.Connection = myConnection;
                try
                {
                    myConnection.Open();
                    command.ExecuteScalar();
                    myConnection.Close();
                    BitConverter.GetBytes(1).CopyTo(bf, 3);
                }
                catch (Exception e)
                {
                    myConnection.Close();
                    Console.WriteLine(e.Message);
                    BitConverter.GetBytes(0).CopyTo(bf, 3);
                }
                Encoding.UTF8.GetBytes("crt").CopyTo(bf, 0);
                return bf;
            }
            byte[] DeleteChar(byte[] b)
            {
                byte[] bf = new byte[8];
                int id = BitConverter.ToInt32(b, 3);
                MySqlCommand command = new MySqlCommand();
                command.Connection = myConnection;
                command.CommandText = "call SP_DeleteChar('" + id + "')";
                try
                {
                    myConnection.Open();
                    command.ExecuteScalar();
                    myConnection.Close();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    myConnection.Close();
                }
                BitConverter.GetBytes(id).CopyTo(bf, 3);
                Encoding.UTF8.GetBytes("dlt").CopyTo(bf, 0);
                Console.WriteLine("deleted");
                return bf;
            }
        }
        static void Main(string[] args)
        {
            ThreadedServer ts = new ThreadedServer(9095);
            ts.Start();
            Console.ReadLine();
        }
    }
}

Що за м'ютекси? оО Вперше чую про них

4 Востаннє редагувалося Cyan (27.06.2013 22:02:57)

Re: C# і MySQL Connector. Питання до богів програмування!

TRYCUKI_V_KROVI написав:

Що за м'ютекси? оО Вперше чую про них

для паралельного виконання потоків (вид семара, себто лічильник)
він може мати стан "зайнятий" або "вільний" та відповідні функції: "зайняти" або "звільнити"
використовується дл того, щоб не виникло блокування
це те, що пам'ятаю з інтитуту

Подякували: Очі.завидющі, koala2

5

Re: C# і MySQL Connector. Питання до богів програмування!

Cyan написав:
TRYCUKI_V_KROVI написав:

Що за м'ютекси? оО Вперше чую про них

для паралельного виконання потоків (вид семара, себто лічильник)
він може мати стан "зайнятий" або "вільний" та відповідні функції: "зайняти" або "звільнити"
використовується дл того, щоб не виникло блокування
це те, що пам'ятаю з інтитуту

аааа, згадав згадав, я так розумію, Ви пропонуєте блокувати мютекс, якщо асинхронний метод виконується і організувати таким чином щось типу черги, ну одні методи не можуть виконуватись, поки не закінчив виконання інший метод? Так це не рішення, сервер має одночасно обслуговувати багацько клієнтів і тисячний клієнт не має чекати, поки сервер буде опрацьовувати запити від інших 999 клієнтів.

6

Re: C# і MySQL Connector. Питання до богів програмування!

TRYCUKI_V_KROVI написав:

Так це не рішення, сервер має одночасно обслуговувати багацько клієнтів і тисячний клієнт не має чекати, поки сервер буде опрацьовувати запити від інших 999 клієнтів.

скажу по секрету: паралельного виконання потоків не існує
можна взяти один потік, обробити його частину, потім інший - теж обробити частину, потім повернутись до першого, доробити і т.д.
наші комп'ютери так працюють (тому вінда і висне) - складається ілюзія паралельності, тому-шо ці кроки виконуються дуже швидко
хоч чесно кажучи, я не уявляю, як можна зробити щось подібне з програмого

7

Re: C# і MySQL Connector. Питання до богів програмування!

Cyan написав:
TRYCUKI_V_KROVI написав:

Так це не рішення, сервер має одночасно обслуговувати багацько клієнтів і тисячний клієнт не має чекати, поки сервер буде опрацьовувати запити від інших 999 клієнтів.

скажу по секрету: паралельного виконання потоків не існує
можна взяти один потік, обробити його частину, потім інший - теж обробити частину, потім повернутись до першого, доробити і т.д.
наші комп'ютери так працюють (тому вінда і висне) - складається ілюзія паралельності, тому-шо ці кроки виконуються дуже швидко
хоч чесно кажучи, я не уявляю, як можна зробити щось подібне з програмого

вот млинець! а як же багатоядерні компи?
p.s. почитаю про ті мютекси і спробую запускати обробку запитів в одному потоці. Там, майбуть, треба використовувати пул потоків чи щось типу того?

8

Re: C# і MySQL Connector. Питання до богів програмування!

Читайте про м'ютекси\семафори\критичні секції. Вам потрібна асинхронність
Наспавді сервери працюють по-іншому:
Клієнт відправив запит на отримання інформаці - сервер звернувся до файлової системи з цим запитом, а сам перейшов до виконання іншого запиту. як тільки інформація отримана - передає її користувачеві. Сьогодні вже існують багатоядерні процесори, тому й асинхронність справжня існує, вже не кажучі про багатопроцесорні системи, які завжди існували.

9

Re: C# і MySQL Connector. Питання до богів програмування!

Використання С# принципово?

10

Re: C# і MySQL Connector. Питання до богів програмування!

М'ютекси слід використовувати тільки для критичних зон. Наприклад, в сервера має бути тільки одне під'єднання до бази, але різні методи його використовують одночасно. Тоді м'ютексом блокується саме створення під'єднання, а після цього м'ютекс звільняється і все працює паралельно.
Про багатопроцесорні (багатоядерні) системи ви таки маєте рацію, хоча й Cyan теж частково права - час кожного ядра ділиться між багатьма потоками, і неможливо завчасно передбачити, в якому порядку будуть працювати різні завдання. Можуть і одночасно на двох різних ядрах, можуть і послідовно на одному.
Код читати зараз вже нема сил, та й ліньки якось 300 рядків розбирати. Які саме методи у вас взаємно виключаються?

11

Re: C# і MySQL Connector. Питання до богів програмування!

Очі.завидющі написав:

Використання С# принципово?

угу, я його найкраще розумію і клієнт пишется на ньому + нема часу переписувати на іншій мові

12

Re: C# і MySQL Connector. Питання до богів програмування!

Й оце почитайте - http://en.wikipedia.org/wiki/C10k_problem

13

Re: C# і MySQL Connector. Питання до богів програмування!

Очі.завидющі написав:

Й оце почитайте - http://en.wikipedia.org/wiki/C10k_problem

дуже цікаво. але толку з того, буду робити массив з 100 об'єктів для з'єднання і використовувати по 1 для кожного користувача

14

Re: C# і MySQL Connector. Питання до богів програмування!

koala написав:

М'ютекси слід використовувати тільки для критичних зон. Наприклад, в сервера має бути тільки одне під'єднання до бази, але різні методи його використовують одночасно. Тоді м'ютексом блокується саме створення під'єднання, а після цього м'ютекс звільняється і все працює паралельно.
Про багатопроцесорні (багатоядерні) системи ви таки маєте рацію, хоча й Cyan теж частково права - час кожного ядра ділиться між багатьма потоками, і неможливо завчасно передбачити, в якому порядку будуть працювати різні завдання. Можуть і одночасно на двох різних ядрах, можуть і послідовно на одному.
Код читати зараз вже нема сил, та й ліньки якось 300 рядків розбирати. Які саме методи у вас взаємно виключаються?

LogIn, GetChars, CreateChar, DeleteChar

15

Re: C# і MySQL Connector. Питання до богів програмування!

Там, майбуть, треба використовувати пул потоків чи щось типу того?

Ви ж не ОС пишете.
Я нажаль не писав ніколи пз для високонавантажених серверів, тому не знаю скільки зєднань оптимально для MySQL - одне на всю програму чи окреме на кожне з'єднання. Я мережевих демонів нажаль не писав.

16

Re: C# і MySQL Connector. Питання до богів програмування!

Ну тоді я спробую спочатку купу з'єднань, а потім використання мютексів

17

Re: C# і MySQL Connector. Питання до богів програмування!

Cyan написав:
TRYCUKI_V_KROVI написав:

Так це не рішення, сервер має одночасно обслуговувати багацько клієнтів і тисячний клієнт не має чекати, поки сервер буде опрацьовувати запити від інших 999 клієнтів.

скажу по секрету: паралельного виконання потоків не існує
можна взяти один потік, обробити його частину, потім інший - теж обробити частину, потім повернутись до першого, доробити і т.д.
наші комп'ютери так працюють (тому вінда і висне) - складається ілюзія паралельності, тому-шо ці кроки виконуються дуже швидко
хоч чесно кажучи, я не уявляю, як можна зробити щось подібне з програмого

Якщо 1 процесор та 1 ядро, то так. Якщо ж більше ядер або кілька процесорів, то потоки чи то процеси можуть виконуватись одночасно (паралельно).

18

Re: C# і MySQL Connector. Питання до богів програмування!

Я ніколи не працював з C#+MySQL в зв'язці; але маю дурне питання - чи дійсно потрібно кожного разу відкривати/закривати з'єднання? Може, варто відкрити з'єднання в конструкторі, а закрити в деструкторі?

19

Re: C# і MySQL Connector. Питання до богів програмування!

koala написав:

Я ніколи не працював з C#+MySQL в зв'язці; але маю дурне питання - чи дійсно потрібно кожного разу відкривати/закривати з'єднання? Може, варто відкрити з'єднання в конструкторі, а закрити в деструкторі?

Потрібно. У конструкторі ми не можемо передбачити майбутнього у силу його непередбачуваності. Зате додатковими методами відкрити коннект - не проблема, навіть коли мають місце провали по зв'язку.

У якості блок-засобів у даному випадку - семафор та механізм транзакцій БД. Ну й створення окремих потоків для клієнтських запитів.

20

Re: C# і MySQL Connector. Питання до богів програмування!

Bartash написав:

Потрібно. У конструкторі ми не можемо передбачити майбутнього у силу його непередбачуваності. Зате додатковими методами відкрити коннект - не проблема, навіть коли мають місце провали по зв'язку.

У якості блок-засобів у даному випадку - семафор та механізм транзакцій БД. Ну й створення окремих потоків для клієнтських запитів.

За такою логікою і чинна програма неробоча - бо ми ж не можемо передбачити, що станеться між

myConnection.Open();
command.ExecuteScalar();

Якщо був розрив, то треба його відповідно обробляти в події ConnectionLost.
Можна ще запропонувати робити щось на кшталт

if(myConnection.State!=ConnectionState.Open)//чи як там в шарпах ENum-и іменуються
  myConnection.Open();

Тільки варіантів більше, див. ConnectionState.

Але логіка підказує, що це краще обробляти саме в ConnectionLost, а програму писати так, ніби під'єднання завжди відкрите (бо так воно для програми і є).