Как написать программу клиент

Если Вам интересно посмотреть, как пишутся «Программы сервер – клиент» и как они работают на реальном примере, предлагаю прочитать пост до конца. Будет интересно!

Пример программы я решил писать на Delphi! Так как, это проще и хорошо подходит для маленького, но реального примера. Что касается грозного слова «Троян» которое я упомянул выше, то тут, получается действительно, самый настоящий троян, но с малым и безобидным функционалом.

Вы узнаете, по какому принципу пишутся «Трояны» и как они работают изнутри! Но, есть одно «НО» Трояны не пишут, так как – это будет описано ниже. Поскольку в нашем примере, «Серверная» часть программы будет много весить (Это не приемлемо для реального трояна), и мы не будет её скрывать в системе. В реальном, «Вредоносном ПО» дела обстоят немного по другому.

Серверную часть программы стараются разработать с малым размером, ну примерно «100 КБ» Плюс — минус сколько-то КБ. И скрывают её в системе, так, что искать её придется долго!…

Но все ровно, принцип разработки один! И данный пример идеально подойдёт для понимания, как работаю программы по принципу «Сервер — Клиент» Просто у нас не правильный тон разработки трояна, а оно нам надо? Правильно. НЕТ. Мы же хорошие ребята и хулиганить не собираемся!

КЛИЕНТ-СЕРВЕРНОЕ ПРИЛОЖЕНИЕ НА PYTHON | ЧАСТЬ 1 | СВЯЗЬ КЛИЕНТА И СЕРВЕРА

Как работают программы по принципу «Сервер – Клиент»

Просто и в двух словах картина выглядит вот так: Вы на своём компьютере запускаете «Клиентскую» часть программы, как правило, она имеет «GUI» то есть интерфейс пользователя (Если клиент не консольный)

На компьютере, к которому вы желаете получить доступ, запускается «Серверная» часть программы, она же открывает определённый порт на чужом компьютере и не видна в системе.

Через этот порт происходит соединения, Вы в клиенте указываете порт и IPадрес компьютера, на котором запущен сервер, подключаетесь к серверу и можете спокойно выполнять какие-то действия на чужом ПК со своего компьютера! Ещё можно прочитать мой прошлый пост и узнать:

Надеюсь, что здесь объяснил, вроде как понятно и простым человеческим языком! Если что-то не ясно, дальше, на примере все станет ясно! Далее давайте определимся, какие действия буду выполняться на удалённом ПК вследствие работы нашего маленького трояна!

Какой функционал в данном примере программы Сервер – Клиент.

Честно сказать, на этом этапе, когда размышлял, что бы показать я как-то замешкался и не как не мог придумать, что-то интересное! В общем пусть будет функционал из одной возможности и до жути простой:

— Пользователь будет получать Ваше сообщение.

Не вижу смысла добавлять какие-то другие возможности. Так как, считаю, что для примера и понимание этого вполне достаточно. Так же, думаю, не стоит писать много кода, то, что имеется ниже, то же достаточно, что бы посмотреть на программу изнутри!

А за место сообщение может быть все что угодно, зависит от фантазии человека решившего написать троян и от его знаний в программировании.

Сокеты в программировании. Пишем свой сервер и клиент.

Ну, а у нас будет такой прикол! Человек, сидя за компьютером, неожиданно получит сообщение, например

представляете реакцию человека? Думаю это смешно, было бы увидеть его выражение лица.

Разработка программы по принципу «Сервер – Клиент»

Приступаем к самому интересному! И начнём мы с разработки самого «Сервера» после чего напишем под него клиент! Я не буду объяснять код программы, просто, буду приводить примеры, все же у меня не блог по программированию, да и цель поста показать поэтапно процесс разработки подобных программ по типу «Сервер – Клиент»

Разработка Сервера!

Изначально, нужно научить «Сервер» открывать какой-то порт на компьютере, дабы в дальнейшем иметь возможность подключиться к нему из «Клиента» а уже после научим принимать команды и выполнять какие-то действия на ПК.

Откроем порт следующим кодом, который до боли прост:

Источник: snippcode.ru

Протокол TCP

Одним из наиболее распространенных протоколов взаимодействия в сети является протокол TCP (Transmission Control Protocol). Этот протокол гарантирует доставку сообщений и широко используется в различных существующих на сегодняшний день программах. Для работы с протоколом TCP в .NET предназначены классы TcpClient и TcpListener . Эти классы строятся поверх класса System.Net.Sockets.Socket . TcpClient и TcpListener упрощают создание клиента и сервера, которые реализуют протокол TCP. Если же функциональности этих классов недостаточно, то для более продвинутных и изощренных сценариев можно использовать тот же класс Socket. В данной главе мы рассмотрим различные подходы к построению tcp-клиента и tcp-сервера, как с помощью TcpClient и TcpListener, так и с помощью чистых сокетов.

TCP-клиент на сокетах

Рассмотрим определение простейшего клиента, который использует TCP-сокеты для подключения к хосту, отправки и получения данных. Прежде всего для определения сокета, который использует протокол TCP, необходимо для сокета указать в качестве типа протокола Tcp, а в качестве типа сокета — Stream :

Socket tcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Подключение к хосту

Для подключения к удаленному хосту применяется метод Connect() / ConnectAsync() . Оба этих метода имеют множество версий, но в общем случае для подключения к удаленному хосту нам необходим адрес хоста в виде ip-адреса или домена и порт. Отмечу пару перегрузок:

public Task ConnectAsync (string host, int port); public Task ConnectAsync (IPAddress address, int port);

Например, подключемся к хосту «google.com»:

using System.Net.Sockets; var port = 80; var url = «www.google.com»; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < // пытаемся подключиться используя URL-адрес и порт await socket.ConnectAsync(url, port); Console.WriteLine($»Подключение к установлено»); > catch (SocketException) < Console.WriteLine($»Не удалось установить подключение к «); >

В данном случае после успешного подключения выводим на консоль соответствующее сообщение.

При неудаче подключения будет сгенерировано исключение SocketException

Информация о подключении

После подключения к удаленному хосту мы можем получить его адрес (то есть ip-адрес+порт) с помощью свойства RemoteEndPoint . Кроме того, мы можем получить адрес самого сокета с помощью свойства LocalEndPoint :

using System.Net.Sockets; var port = 80; var url = «www.google.com»; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < // пытаемся подключиться используя URL-адрес и порт await socket.ConnectAsync(url, port); Console.WriteLine($»Подключение к установлено»); Console.WriteLine($»Адрес подключения «); Console.WriteLine($»Адрес приложения «); > catch (SocketException) < Console.WriteLine($»Не удалось установить подключение к «); >

Читайте также:
Не могу установить программу через ifunbox

Например, консольный вывод с моем случае:

Подключение к www.google.com установлено Адрес подключения 216.58.210.164:80 Адрес приложения 192.168.0.112:65103

Отключение от хоста

Если мы завершили взаимодействие с хостом, но планируем продолжать использовать сокет, чтобы соединение с удаленным хостом не висело, мы можем отключиться с помощью метода Disconnect() / DisconnectAsync() . Данный метод в качестве параметра принимает значение bool — если оно равно true , то после отключения можно заново использовать сокет для новых подключений:

using System.Net.Sockets; var port = 80; var url = «www.google.com»; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < await socket.ConnectAsync(url, port); await socket.DisconnectAsync(true); // отключаемся >catch (SocketException ex)

Чтобы гарантировать, что все данные отправлены и получены перед закрытием подключения, перед вызовом метода Disconnect/DisconnectAsync Microsoft рекомендует вызывать метод Shutdown.

Отправка данных

Для отправки данных применяется метод Send()/SendAsync() . Оба этих метода имеют различные версии. Рассмотрим самые простые версии:

public Task SendAsync (ArraySegment buffer); public Task SendAsync (ArraySegment buffer, SocketFlags socketFlags);

В качестве параметра он получает отправляемые данные в виде структуры ArraySegment — грубо говоря часть массива байтов. Дополнительно с помощью значений перечисления SocketFlags можно установить параметры отправки.

В качестве результата метод SendAsync() возвращает количество отправленных данных.

Например, отправим на google.com запрос с некоторыми данными:

using System.Net.Sockets; using System.Text; var port = 80; var url = «www.google.com»; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < await socket.ConnectAsync(url, port); // определяем отправляемые данные var message = $»GET / HTTP/1.1rnHost: rnConnection: closernrn»; // конвертируем данные в массив байтов var messageBytes = Encoding.UTF8.GetBytes(message); int bytesSent = await socket.SendAsync(messageBytes); Console.WriteLine($»на адрес отправлено байт(а)»); > catch (SocketException ex)

При взаимодействии с сервером надо понимать, какой протокол реализует данный сервер, то есть правила, по которым этим сервер получает запросы. Так, google.com, как любой стандартный сайт, принимает сообщения, которые соответствуют протоколу HTTP. Грубо говоря, чтобы удаленный хост нас понял, нам надо говорить на его языке. И в данном случае мы посылаем сообщение, которое соответствует протоколу HTTP:

var message = $»GET / HTTP/1.1rnHost: rnConnection: closernrn»;

Формат запроса HTTP включает прежде всего линию запроса, которая состоит из типа запроса, пути к запрошенному ресурсу и специфической версии протокола. То есть здесь в сообщении мы указываем, что отправляется запрос типа GET по пути «/» (то есть к корню сайта google.com»). При этом применяется протокол HTTP/1.1. Линия запроса должна завершаться двойным набором символов каретки и перевода строки rn .

Кроме того, запрос HTTP может содержать заголовки. Так, в данном случае отправляем заголовок «Host», который указывает на ажрес хоста. В данном случае это «www.gooogle.com:80». И также в данном случае отправляем заголовок «Connection», который имеет значение «close» — это значение предписывает серверу закрыть подключение.

Поскольку мы можем послать только байты, а не строки, то переводим строку в массив байтов:

var messageBytes = Encoding.UTF8.GetBytes(message);

И отправляем данные:

int bytesSent = await socket.SendAsync(messageBytes);

Стоит обратить внимание, что хотя метод SendAsync принимает объект ArraySegment, здесь мы передаем непосредственно массив с данными, который автоматически будет конвертироваться в структуру ArraySegment .

В итоге при выполнении запроса получим следющий консольный вывод:

на адрес www.google.com отправлено 62 байт(а)

Получение данных от хоста

Для получения данных класс Socket применяет методы Receive() / ReceiveAsync() . Оба метода имеют много перегруженных версий с разным набором параметров, но ключевой параметр — буфер, в который загружаются полученные данные. Для синхронного метода Receive в качестве буфера обычно выступает массив байт, а для асинхронного ReceiveAsync — структура ArraySegment :

Task ReceiveAsync (ArraySegment buffer); int Receive (byte[] buffer);

Результатом обоих методов является количество считанных байтов.

Например, получим от google.com ответ:

using System.Net.Sockets; using System.Text; var port = 80; var url = «www.google.com»; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < await socket.ConnectAsync(url, port); // определяем отправляемые данные var message = $»GET / HTTP/1.1rnHost: rnConnection: closernrn»; // конвертируем данные в массив байтов var messageBytes = Encoding.UTF8.GetBytes(message); // отправляем данные await socket.SendAsync(messageBytes); // буфер для получения данных var responseBytes = new byte[512]; // получаем данные var bytes = await socket.ReceiveAsync(responseBytes); // преобразуем полученные данные в строку string response = Encoding.UTF8.GetString(responseBytes, 0, bytes); // выводим данные на консоль Console.WriteLine(response); > catch (SocketException ex)

В качестве буфера отправляемых байтов определена переменная responseBytes , которая представляет массив в 512 байт. В размер буфера в данном случае не принципиально. Главное представлять, насколько большие могут быть полученные данные и в соответствии с этим определять размер для буфера. А чтобы отслеживать реальное количество считанных байт (которое может быть меньше размера буфера), определена переменная bytes.

Причем как и в случае с методом SendAsync в метод ReceiveAsync передается массив байт, который автоматически преобразуется в ArraySegment.

var bytes = await socket.ReceiveAsync(responseBytes);

Поскольку в данном случае ответ от google.com по сути представляет строку, то конвертируем преобразованные данные в строку и выводим ее на консоль.

string response = Encoding.UTF8.GetString(responseBytes, 0, bytes); Console.WriteLine(response);

Отправка и получение данных с помощью метода ReceiveAsync класса Socket в C# и .NET

Как мы видим, это обычный ответ HTTP, где вначале идут статус ответа, заголовки. Однако по ответу видно, что он не полный. Идеальна была бы ситуация, когда мы точно знаем, сколько байт пришлет удаленный хост. И соответственно могли бы определить соответствующий буфер. Но в данном случае мы этого точно не знаем — они могут быть больше размера буфера, а могут быть меньше. Очевидно, нам надо считывать данные в цикле, пока мы не получим последний байт ответа:

using System.Net.Sockets; using System.Text; var port = 80; var url = «www.google.com»; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < await socket.ConnectAsync(url, port); // определяем отправляемые данные var message = $»GET / HTTP/1.1rnHost: rnConnection: closernrn»; // конвертируем данные в массив байтов var messageBytes = Encoding.UTF8.GetBytes(message); // отправляем данные await socket.SendAsync(messageBytes); // буфер для получения данных var responseBytes = new byte[512]; // объект StringBuilder для склеивания ответа в строку var builder = new StringBuilder(); int bytes; // в цикле получаем данные do < bytes = await socket.ReceiveAsync(responseBytes); // преобразуем полученный набор байтов в строку string responsePart = Encoding.UTF8.GetString(responseBytes, 0, bytes); // добавляем в StringBuilder builder.Append(responsePart); >while (bytes > 0); // повторяем цикл, пока сервер отправляет более 0 байтов // выводим ответ на консоль Console.WriteLine(builder); > catch (SocketException ex)

теперь для считывания используем цикл do..while. Смотрим, сколько байтов возвращает ReceiveAsync. И пока он вернет 0 байтов, повторяем цикл. Полученные байты конвертируем в строку и добавляем в StringBuilder. В конце выводим полученное содержимое из StringBuilder на консоль:

Читайте также:
На какой программе готовить гречку в мультиварке

Отправка и получение данных с помощью сокетов TCP и Socket в C# и .NET

Теперь мы получили весь ответ от google.com. Но следует учитывать, что при отсутствии данных ReceiveAsync для tcp-сокетов завершает выполнение и возвращает количество байт только в том случае, когда буфер заполнен или когда одна из сторон завершает соединение. Поскольку TCP предполагает установление соединения, при котором хосты могут продолжительное время отправлять и получать данные. В примере выше сделано просто: на сервер google.com отправляется следующее сообщение:

var message = $»GET / HTTP/1.1rnHost: rnConnection: closernrn»;

Кроме метода HTTP, пути и протокола здесь также отправляется заголовок «Connection: Close», который предписывает закрыть подключение после завершения текущей транзакции. Благодаря этому мы выполнение не зависает на строке

bytes = await socket.ReceiveAsync(responseBytes);

Уберем этот заголовок

var message = $»GET / HTTP/1.1rnHost: rnrn»;

И сокет бы продолжал ждать от google.com новых данных. Поскольку в данном случае мы имеем дело с потоковой передачей, и сокет может бесконечно ждать новую порцию данных. Но данный пример довольно ситуативен, поскольку google.com принимает запросы HTTP, и здесь идет манипуляция с заголовками HTTP.

Но широта использования tcp-сокетов протоколом HTTP не ограничивается, и в других ситуациях могут потребоваться другие возможности (не говоря о том, что даже в новых версиях протокола HTTP отказались от заголовка Connection). И здесь же опять все зависит от удаленного хоста. Когда мы сами делаем клиент и сервер, мы сами можем определить любую логику взаимодействия. Когда мы никак не можем повлиять на работу удаленного хоста, то приходится сообразовать работу сокета с работой удаленного хоста. Тем не менее тут также есть варианты. Так, мы можем отключить получение и/или отправку данных на сокете с помощью метода Shutdown()

using System.Net.Sockets; using System.Text; var port = 80; var url = «www.google.com»; using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < await socket.ConnectAsync(url, port); var message = $»GET / HTTP/1.1rnHost: rnrn»; var messageBytes = Encoding.UTF8.GetBytes(message); await socket.SendAsync(messageBytes); // явным образом отключаем отправку данных на сокете socket.Shutdown(SocketShutdown.Send); var responseBytes = new byte[512]; var builder = new StringBuilder(); int bytes; do < bytes = await socket.ReceiveAsync(responseBytes); string responsePart = Encoding.UTF8.GetString(responseBytes, 0, bytes); builder.Append(responsePart); >while (bytes > 0); Console.WriteLine(builder); > catch (SocketException ex)

В данном случае после отправки данных сокет будет отключен от дальнейшей отправки данных с помощью вызова socket.Shutdown(SocketShutdown.Send) , и после получения данных в методе ReceiveAsync google.com также завершит отправку.

Свойство Available

Свойство Available хранит количество доступных для чтения байтов. Соответственно возникает вопрос, почему бы не использовать эти свойство для отслеживания наличия данных, например:

do < bytes = await socket.ReceiveAsync(responseBytes); string responsePart = Encoding.UTF8.GetString(responseBytes, 0, bytes); builder.Append(responsePart); >while (socket.Available > 0);

Но свойство Available будет иметь ненулевое значение, если в текущий момент в потоке есть доступные для чтения данные. Но природа протокола TCP такова, что крупные наборы данных отправляются отдельными пакетами.

Какой-то пакет может прийти быстрее, какой-то задержится, какой-то будет потерян, и потребуется переотправка. Поэтому может возникнуть ситуация, что сервер отправил данные, часть данных пришла. В какой-то момент свойство Available у сокета возратило 0, соответственно произошел выход из цикла. И в итоге мы получим неполные данные. Поэтому использование свойства Available в данном случае не лучший вариант.

Обычно при получении данных используют одну из следующих стратегий, которые позволяют определить завершение получения данных:

  • Использование буфера фиксированной длины, когда мы точно знаем, какой именно объем данных будет послан
  • Отправка в ответе информации о размере ответа, получив которую, нам будет проще считать нужное количество байтов
  • Использование маркера окончания ответа, получив который, мы завершим считывание данных

Выбор и реализация конкретной стратегии всецело зависит от сервера, который получает запрос и отправляет ответ.

Рефакторинг подключения

Для упрощения работы с сокетом мы можем вынести код подключения и код отправки-получения данных в отдельные методы:

using System.Net.Sockets; using System.Text; var port = 80; var url = «www.google.coms»; var response = await SocketSendReceiveAsync(url, port); Console.WriteLine(response); async Task ConnectSocketAsync(string url, int port) < Socket tempSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try < await tempSocket.ConnectAsync(url, port); return tempSocket; >catch(SocketException ex) < Console.WriteLine(ex.Message); tempSocket.Close(); >return null; > async Task SocketSendReceiveAsync(string url, int port) < using Socket? socket = await ConnectSocketAsync(url, port); if (socket is null) return $»Не удалось установить соединение с «; // отправляем данные var message = $»GET / HTTP/1.1rnHost: rnConnection: Closernrn»; var messageBytes = Encoding.UTF8.GetBytes(message); await socket.SendAsync(messageBytes); // получаем данные int bytes; // буфер для получения данных var responseBytes = new byte[512]; var builder = new StringBuilder(); do < bytes = await socket.ReceiveAsync(responseBytes); string responsePart = Encoding.UTF8.GetString(responseBytes, 0, bytes); builder.Append(responsePart); >while (bytes > 0); return builder.ToString(); >

Источник: metanit.com

Oracle Call Interface: как написать клиентское приложение на Си

Хабр, привет! Вообще-то я не настоящий сварщик программист, я системный администратор и администратор БД. Поэтому, когда несколько лет назад передо мной встала задача написать небольшое клиентское приложение для автоматизации рутинных процедур, я потратил немало времени, чтобы разобраться в этом вопросе. Если вдруг у вас примерно такая же ситуация, надеюсь, эта статья сэкономит ваше время и силы.

Что нужно?

Определившись с требованиями получаем следующее.

Желательно для бинарника:

  • Небольшой размер исполняемого файла.
  • Корректная обработка случаев, когда OCI-библиотека недоступна

Необходимо для бинарника:

  • Работать утилита должна на любом компьютере с ОС Windows выше Windows 7 или Windows Server 2008 без установки каких-либо фреймворков и Runtime Environment. Должен быть установлен только какой-то из продуктов Oracle, включающий OCI-библиотеку.

Очень желательно для исходников:

  • Чтобы готовый проект можно было без проблем скомпилировать на другом компьютере без установки каких-либо библиотек. Так как у нас есть похожий legacy проект, которую лет 20 назад писал давно ушедший сотрудник. Его рабочую станцию мы храним в виде образа ВМ, так как подготовить окружение на новой рабочей станции — задача крайне нетривиальная.

Какую технологию использовать?

Oracle предлагает следующие варианты:

  • Pro*C/C++ — Предкомпилятор Oracle. Инструмент программирования, который позволяет встраивать операторы SQL в хост-программу высокого уровня. Предварительный компилятор принимает основную программу в качестве входных данных, преобразует встроенные инструкции SQL в стандартные вызовы библиотеки Oracle во время выполнения и создает исходную программу, которую вы можете скомпилировать, связать и выполнить. Разобраться с этим у меня не получилось, поэтому ничего к этому описанию я добавить не могу
  • C++ Call Interface (OCCI) — API, который предоставляет приложениям C++ доступ к данным в базе данных Oracle. OCCI позволяет программистам на C++ использовать весь спектр операций с базами данных Oracle, включая обработку инструкций SQL и манипулирование объектами. Так как я С++ в реальных проектах не использовал, этот вариант мне явно не подходил
  • Oracle Call Interface (OCI) — API, который позволяет создавать приложения, использующие вызовы функций для доступа к базе данных Oracle и управления всеми этапами выполнения инструкций SQL. OCI поддерживает типы данных, соглашения о вызовах, синтаксис и семантику C и C++. Он предоставляет библиотеку стандартных функций доступа к базе данных и поиска в виде библиотеки динамической среды выполнения (библиотеки OCI), которая может быть связана в приложении во время выполнения. Также в описании указано, что все оракловые утилиты (типа sqlplus, exp, imp и прочие) написаны именно с использованием OCI. Что же еще нужно?
  • Oracle Database Programming Interface for C (ODPI-C) — это C-библиотека с открытым исходным кодом, которая упрощает использование общих функций интерфейса вызовов Oracle (OCI) для драйверов баз данных Oracle и пользовательских приложений. DPIC находится поверх OCI и требует клиентских библиотек Oracle. Проект лежит на Гитхабе. Про этот вариант я прочитал только когда писал эту статью. Но, посмотрев примеры программ, я нисколько не пожалел, что выбрал OCI. Может, это вопрос привычки и опыта, но простой эта библиотека мне не показалась.
Читайте также:
Прекращена работа программы rocket league возникшая проблема привела к прекращению работы программы

Пишем простое приложение

Подключение библиотеки

Для того, чтобы использовать OCI функции в своей программе, нужно подключить библиотеку и получить адреса функций:

Подключение библиотеки и получений адресов функций

//подкллючаем библиотеку в Windows hOCIDll = LoadLibraryW(L»oci.dll»); //или в Линукс ocimodule = dlopen(«libclntsh.so», RTLD_LAZY); //Определяем тип для указателя функции typedef sword(*pOCIEnvCreate)(OCIEnv **hOraEnvp, ub4 mode, const void *ctxp, const void *(*malocfp) (void *ctxp, size_t size), const void *(*ralocfp) (void *ctxp, void *memptr, size_t newsize), const void(*mfreefp) (void *ctxp, void *memptr), size_t xtramemsz, void **usrmempp); //выделяем переменную для адреса функции pOCIEnvCreate OCIEnvCreate; //Получаем адрес функции на Windows OCIEnvCreate = (pOCIEnvCreate)GetProcAddress(hOCIDll, «OCIEnvCreate»); // Или на Linux OCIEnvCreate = (pOCIEnvCreate)dlsym(ocilib,»OCIEnvCreate»);

Таком образом нужно получить адреса всех функций, которые мы будем использовать в нашей программе.

Определения функций можно найти либо на https://docs.oracle.com в Call Interface Programmer’s Guide для вашей версии библиотеки либо в заголовочном файле %ORACLE_HOME%OCIincludeociap.h

Инициализация окружения

Далее нам нужно инициализировать структуры OCIEnv (хендл окружения) и OCIError (хендл для обработки ошибок).

OCIEnv *hOraEnv = NULL; OCIError *hOraErr = NULL; OCIEnvCreate((OCIEnv **) OCIHandleAlloc((const void *)hOraEnv, (void **)

Создание сессии

Для каждой сессии нам нужно инициализировать хендл OCISvcCtx, именно он используется для выполнения sql-выражений. Я, руководствуясь демонстрационными примерами от Oracle, создавал сессии так:

OCIServer *hOraServer = NULL; OCISvcCtx *hOraSvcCtxOCI = NULL; OCISession *hOraSession = NULL; //Аллоцируем хендл OCIServer OCIHandleAlloc((const void *)hOraEnv, (void **) char *dbconnectstring = «servername:1521/ORCL»; //Подключаемся к серверу OCIServerAttach(hOraServer, hOraErr, (const OraText *)dbconnectstring, (sb4)strlen(dbconnectstring), (ub4)OCI_DEFAULT); //Аллоцируем хендл сервисного контекста OCIHandleAlloc((const void *)hOraEnv, (void **) //Помещаем хендл сервера в сервисного контекста OCIAttrSet((void *)hOraSvcCtx, OCI_HTYPE_SVCCTX, (void *)hOraServer, (ub4)0, OCI_ATTR_SERVER, (OCIError *)hOraErr)); //Аллоцируем хендл для сессии OCIHandleAlloc((const void *)hOraEnv, (void **) char * username = «SCOTT»; //Помещаем имя пользователя в хендл сессии OCIAttrSet((void *)hOraSession, (ub4)OCI_HTYPE_SESSION, (void *)username, (ub4)strlen(username), (ub4)OCI_ATTR_USERNAME, hOraErr); char *password = «tiger»; //Помещаем пароль в хендл сессии OCIAttrSet((void *)hOraSession, (ub4)OCI_HTYPE_SESSION, (void *)password, (ub4)strlen(password), (ub4)OCI_ATTR_PASSWORD, hOraErr); //флаг для указания, является ли пользователь sysdba bool assysdba = 1; //Начинаем сессию OCISessionBegin(hOraSvcCtx, hOraErr, hOraSession, OCI_CRED_RDBMS, (ub4)(OCI_DEFAULT | (assysdba ? OCI_SYSDBA : 0))); //Помещаем сессию в сервисный контекст OCIAttrSet((void *)hOraSvcCtx, (ub4)OCI_HTYPE_SVCCTX, (void *)hOraSession, (ub4)0, (ub4)OCI_ATTR_SESSION, hOraErr);

Уже в процессе написания статьи, я увидел, что для этих же целей можно использовать функцию OCILogon2. Судя по всему, это намного проще и удобнее. А главное, нужно меньше писать кода. Но мы уже оставим все, как есть.

Вставляем данные

Пример функции для загрузки данных в базу:

char * insert_statement = «INSERT INTO simple_table (id, textfield) VALUES (:id, :string)»; sword status; int id; char stringBuffer[100]; OCIStmt *hOraPlsqlStatement = NULL; //Аллоцируем хендл для sql-выражения OCIHandleAlloc((const void *)hOraEnv, (void **) //Подготавливаем его OCIStmtPrepare(hOraPlsqlStatement, hOraErr, (const OraText *)insert_statement, (ub4)strlen(insert_statement), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); //Биндим наши переменные id и stringBuffer в sql-выражение OCIBind *bnd1p = NULL; OCIBind *bnd2p = NULL; OCIBindByName(hOraPlsqlStatement, :id», -1, (void *) OCIBindByName(hOraPlsqlStatement, :string», -1, (void *)stringBuffer, (sb4)(sizeof(stringBuffer)), SQLT_STR, (void *)0, (ub2 *)0, (ub2 *)0, (ub4)0, (ub4 *)0, OCI_DEFAULT); //вставляем данные в цикле for (id = 1; id < 10; id++) < sprintf(stringBuffer, «This is the %d string», id); status = OCIStmtExecute(hOraSvcCtx, hOraPlsqlStatement, hOraErr, (ub4)1, (ub4)0, (CONST OCISnapshot *) NULL, (OCISnapshot *)NULL, OCI_DEFAULT); if (status != OCI_SUCCESS status != OCI_SUCCESS_WITH_INFO) < checkerr(hOraErr, status); OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT); return FALSE; >> //освобождаем хендл OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT);

В целях увеличения быстродействия и экономии ресурсов базы в случае множественных инсертов нужно вначале подготовить выражение с заполнителями типа :id и :string, связать их с соответствующими переменными, и затем изменять значение переменных и выполнять выражение.

Получаем данные

Код для получения данных из базы:

char * select_statement = «select id, textfield from simple_table order by id»; sword status; int id; char stringBuffer[100]; OCIStmt *hOraPlsqlStatement = NULL; OCIHandleAlloc((const void *)hOraEnv, (void **) OCIStmtPrepare(hOraPlsqlStatement, hOraErr, (const OraText *)select_statement, (ub4)strlen(select_statement), (ub4)OCI_NTV_SYNTAX, (ub4)OCI_DEFAULT); OCIDefine *OraIdDefine = NULL; //Привязываем переменную id к первому полю в запросе OCIDefineByPos(hOraPlsqlStatement, id, (sword)sizeof(id), SQLT_INT, (void *)0, (ub2 *)0, (ub2 *)0, OCI_DEFAULT); OCIDefine *OraStringDefine = NULL; //Привязываем stringBuffer ко второму полю в запросе OCIDefineByPos(hOraPlsqlStatement, //Выполняем запрос, не получая никаких данных status = OCIStmtExecute(hOraSvcCtx, hOraPlsqlStatement, hOraErr, (ub4)0, (ub4)0, (CONST OCISnapshot *) NULL, (OCISnapshot *)NULL, OCI_DEFAULT); if (status != OCI_SUCCESS status != OCI_SUCCESS_WITH_INFO) < checkerr(hOraErr, status); OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT); return FALSE; >printf(«id | textfieldn»); //Получаем данные в цикле while ((status = OCIStmtFetch2(hOraPlsqlStatement, hOraErr, 1, OCI_DEFAULT, 0, OCI_DEFAULT)) == OCI_SUCCESS || status == OCI_SUCCESS_WITH_INFO) < printf(«%d | %sn», id, stringBuffer); >OCIHandleFree(hOraPlsqlStatement, OCI_HTYPE_STMT);

При получении данных поступаем аналогично, привязываем переменные с запрошенными полями в селекте, выполняем выражение и затем в цикле одну за другой получаем строки

Закрываем сессию

После нужных операции нужно закрыть сессию:

OCISessionEnd(hOraSvcCtx, hOraErr, hOraSession, OCI_DEFAULT); OCIHandleFree(hOraSession, OCI_HTYPE_SESSION); OCIHandleFree(hOraSvcCtx, OCI_HTYPE_SVCCTX); OCIServerDetach(hOraServer, hOraErr, (ub4)OCI_DEFAULT); OCIHandleFree(hOraServer, OCI_HTYPE_SERVER);

Закрываем окружение

После того, как взаимодействие с базой больше не нужно, закрываем окружение:

OCIHandleFree(hOraErr, OCI_HTYPE_ERROR); OCIHandleFree(hOraEnv, OCI_HTYPE_ENV); OCITerminate(OCI_DEFAULT);

Итоги работы

Вот ссылки на проекты на гитхабе для Windows(Visual Studio) и Linux(NetBeans)

Что утилита делает

Утилита подключается к базе данных, создает таблицу simple_table, заполняет ее данными, получает из нее данные и удаляет таблицу.

Как скомпилировать

Как и было запланировано с самого начала, для компиляции утилиты не требуется никаких дополнительных файлов и библиотек. Для работы, само собой, требуется установленный Oracle Client либо Instant Oracle Cllient, главное, чтобы была доступна OCI-библиотека.

  • Oracle
  • C
  • Разработка под Linux
  • Разработка под Windows

Источник: habr.com

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru