<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>ZetBlog&#187; сетевое</title>
	<atom:link href="http://zetblog.ru/category/programming/network/feed/" rel="self" type="application/rss+xml" />
	<link>http://zetblog.ru</link>
	<description>Зеты говорят. Блог о программировании, администрировании и безопасности.</description>
	<lastBuildDate>Sat, 29 Oct 2011 18:59:40 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Delphi: Пишем шаблон клиент-серверного приложения.</title>
		<link>http://zetblog.ru/programming/201004/delphi-%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%88%d0%b0%d0%b1%d0%bb%d0%be%d0%bd-%d0%ba%d0%bb%d0%b8%d0%b5%d0%bd%d1%82-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd/</link>
		<comments>http://zetblog.ru/programming/201004/delphi-%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%88%d0%b0%d0%b1%d0%bb%d0%be%d0%bd-%d0%ba%d0%bb%d0%b8%d0%b5%d0%bd%d1%82-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd/#comments</comments>
		<pubDate>Fri, 02 Apr 2010 07:47:37 +0000</pubDate>
		<dc:creator>C0ffe1n</dc:creator>
				<category><![CDATA[прикладное]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[сетевое]]></category>
		<category><![CDATA[ClientSocket]]></category>
		<category><![CDATA[Delphi]]></category>
		<category><![CDATA[ServerSocket]]></category>
		<category><![CDATA[сеть]]></category>
		<category><![CDATA[файлы]]></category>

		<guid isPermaLink="false">http://zetblog.ru/?p=913</guid>
		<description><![CDATA[Продолжая тему разработки собственной утилиты администрирования, в данной статье я рассмотрю типовой шаблон программы клиент-сервер, на базе которой можно разрабатывать собственные клиент-серверные приложения. Данный типовой шаблон для простоты понимания и удобства применения будет рассмотрен на примере компонентов ClientSocket и ServerSocket. Примечание Для тех, кто не в курсе, что значит клиент-серверное приложение, поясню: это комплекс программ [...]]]></description>
			<content:encoded><![CDATA[<p>Продолжая тему разработки собственной утилиты администрирования, в данной статье я рассмотрю типовой шаблон программы клиент-сервер, на базе которой можно разрабатывать собственные клиент-серверные приложения. Данный типовой шаблон для простоты понимания и удобства применения будет рассмотрен на примере компонентов <strong>ClientSocket</strong> и <strong>ServerSocket</strong>.<br />
<span id="more-913"></span><strong></strong></p>
<p><strong>Примечание</strong></p>
<div class="codesnip-container" >Для тех, кто не в курсе, что значит клиент-серверное приложение, поясню: это комплекс программ (модулей) состоящий из двух частей &#8212; клиентской и серверной. Серверная часть является &#171;главной&#187;- так называемый командный пункт, на который возложена задача поддержания связи со всеми клиентами и раздача команд управления, соответствующих их функционалу (управление клиентами). Клиентская же часть является второстепенной, но не менее важной, так как является важным и связующим элементом, позволяющим выполнять удаленные команды (функции) на компьютере, на котором установлен.</div>
<div class="codesnip-container" >Компоненты <strong>ClientSocket</strong> и <strong>ServerSocket</strong>, которые мы будем использовать, находятся на вкладке <strong>Internet</strong>. Если на этой вкладке у Вас нет этих компонентов (а по умолчанию при установке <strong>Delphi7</strong> они не ставятся), необходимо установить их самостоятельно (пакет <strong>dclsocketsXX.bpl</strong>). Данный пакет можно найти на установочном диске<strong> Delphi7</strong> (или в папке<span style="text-decoration: underline;"> C:\Temp</span>, куда распаковывается дистрибутив перед установкой &#8212; <span style="text-decoration: underline;">C:\Temp\delphi7\install\program files\borland\delphi7\bin\</span>) или можете его скачать <a href="/forum/downloadf/components/dclsockets70.rar">отсюда</a>. Чтобы установить данный пакет, запустите <strong>Delphi7</strong>. Зайдите в меню <span style="text-decoration: underline;">Component-&gt;Install Packages</span>. В появившемся окне нажмите кнопку <strong>&#171;Add&#187;</strong> и укажите место, где расположен пакет <strong>dclsocketsXX.bpl</strong>. После этого жмите<strong> ОК</strong>. Компонент на месте. ;)</div>
<p>Надеюсь все объяснил доступно =). А теперь приступим к делу и начнем мы с серверной части. Для этого создадим проект и скинем на форму компонент <strong>ServerSocket</strong>. В настройках компонента <strong>ServerSocket</strong> укажем следующее:</p>
<div class="codesnip-container" >Active = false<br />
Name = ss<br />
Port = 4321<br />
ServerType = stNonBlocking</div>
<p>А также для удобства скинем компонент <strong>Memo</strong>, в который будем выводить всякую инфу. Дадим имя компоненту <strong>log</strong>.</p>
<p>Далее, для компонента <strong>ServerSocket</strong> определяем метод <strong>onClientConnect</strong>, чтобы определять момент подключения клиентов. Вставим следующий код:</p>
<pre class=".brush: pascal">procedure TForm1.ssClientConnect(Sender: TObject;
  Socket: TCustomWinSocket);
var
  myDate : TDateTime;
  formattedDateTime : string;
begin
  mydate:=Now;
  DateTimeToString(formattedDateTime, 'c', myDate);
  log.Lines.Add(formattedDateTime+': Есть коннект c '+Socket.RemoteAddress);
  { далее вы можете вставить свой код }
end;</pre>
<p>А теперь определим метод <strong>onClientRead</strong>, в котором будем обрабатывать получаемые сообщения от клиентов:</p>
<pre class=".brush: pascal">procedure TForm1.ssClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  s:string;
begin
  s:=Socket.ReceiveText;
  log.Lines.Add(s);
end;</pre>
<p>Определим метод<strong> onClientDisconnect</strong>, чтобы фиксировать факт отключения клиента:</p>
<pre class=".brush: pascal">procedure TForm1.ssClientDisconnect(Sender: TObject;
  Socket: TCustomWinSocket);
var
  myDate : TDateTime;
  formattedDateTime : string;
begin
  mydate:=Now;
  DateTimeToString(formattedDateTime, 'c', myDate);
  log.Lines.Add(formattedDateTime+': Клиент '+socket.RemoteAddress+' отключился.');
end;</pre>
<p>На этом с серверной частью покончено =).</p>
<p>Приступим к клиентской части. Для этого создадим новый проект, скинем на форму компонент <strong>ClientSocket</strong> и таймер. В настройках компонента укажем следующее:</p>
<div class="codesnip-container" >Active = false<br />
Name = сs<br />
Port = 4321<br />
ClientType = ctNonBlocking<br />
Address = 127.0.0.1</div>
<p>Для простоты пояснения я в <strong>Address</strong> указал <strong>&#171;петлю&#187;</strong>. Но для гибкости приложения рекомендую это поле обрабатывать программно, во время <strong>CreateForm</strong> при обработке конфигурационного файла, в котором можно указывать нужный <strong>IP-адрес сервера</strong>.</p>
<p>Здесь также для удобства скинем на форму компонент <strong>Memo</strong>, в который будем выводить техническую инфу. Имя дадим соответствующее<strong> log</strong>.</p>
<p>Для компонента <strong>ClientSocket</strong> определяем метод <strong>onError</strong> для того, чтобы обрабатывать исключения, возникшие в момент подключения клиента к серверу. Вставим следующий код:</p>
<pre class=".brush: pascal">procedure TForm1.csError(Sender: TObject; Socket: TCustomWinSocket;
  ErrorEvent: TErrorEvent; var ErrorCode: Integer);
var
  myDate : TDateTime;
  formattedDateTime : string;
begin
 if ErrorEvent= eeConnect  then
  begin
    cs.Active:=false;
      {добавить запись в журнал};
    mydate:=Now;
    DateTimeToString(formattedDateTime, 'c', myDate);
    log.lines.Add(formattedDateTime+': Невозможно установить соединение с сервером - '+cs.Address);
  end;
  ErrorCode:=0;
  { далее вы можете вставить свой код }
end;</pre>
<p>Теперь определим метод <strong>onConnect</strong>. Вставим следующий код:</p>
<pre class=".brush: pascal">procedure TForm1.csConnect(Sender: TObject; Socket: TCustomWinSocket);
var
  myDate : TDateTime;
  formattedDateTime : string;
begin
  mydate:=Now;
  DateTimeToString(formattedDateTime, 'c', myDate);
  log.lines.Add(formattedDateTime+': Соединение с сервером - '+socket.RemoteAddress+' установлено.');
end;</pre>
<p>И чтобы фиксировать момент потери связи с сервером, определим метод <strong>onDisconnect</strong>:</p>
<pre class=".brush: pascal">procedure TForm1.csDisconnect(Sender: TObject; Socket: TCustomWinSocket);
var
  myDate : TDateTime;
  formattedDateTime : string;
begin
  mydate:=Now; //получим текущее время
  DateTimeToString(formattedDateTime, 'c', myDate); //преобразуем в строку
  log.Lines.Add(formattedDateTime+': Соединение с сервером '+socket.RemoteAddress+' потеряно.');
end;</pre>
<p>Таймер настроим так:</p>
<div class="codesnip-container" >Enabled = true<br />
Name = te<br />
Interval = 5000</div>
<p>И определяем метод <strong>onTimer</strong>. Вставим следующий код:</p>
<pre class=".brush: pascal">procedure TForm1.Timer1Timer(Sender: TObject);
begin
if not cs.Active then
  begin
    cs.Active:=true;
  end;
  cs.Socket.SendText('ping client');
end;</pre>
<p>Данный код проверяет наличие соединения с сервером и в случае его отсутствия пытается его установить.<br />
И каждые 5 секунд отправляет серверу сообщение <strong>&#171;ping client&#187;</strong>.</p>
<p>Вот примерно так выглядит типовой шаблон приложения <strong>&#171;клиент-сервер&#187;</strong>.</p>
<p><strong>Примечание</strong></p>
<div class="codesnip-container" >Хотел бы обратить внимание на обработчик <strong>onError</strong> тех, кто ранее не знал как избавиться от злосчастного сообщения &#8212; <strong>&#171;Asynchronous socket error 10061&#8243;</strong>, возникающее при попытке осуществления подключения к серверу, который недоступен.</div>
<p>Рабочий пример можно скачать <a title="Рабочий пример" href="/forum/downloadf/simples/client-server.rar">отсюда</a>.</p>
<p>Если есть кому что добавить или задать вопрос по теме &#8212; прошу отписываться в комментах ;)</p>
]]></content:encoded>
			<wfw:commentRss>http://zetblog.ru/programming/201004/delphi-%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%88%d0%b0%d0%b1%d0%bb%d0%be%d0%bd-%d0%ba%d0%bb%d0%b8%d0%b5%d0%bd%d1%82-%d1%81%d0%b5%d1%80%d0%b2%d0%b5%d1%80-%d0%bf%d1%80%d0%b8%d0%bb%d0%be%d0%b6%d0%b5%d0%bd/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Python: Бесплатная отправка SMS через mail.ru.</title>
		<link>http://zetblog.ru/programming/200811/python-%d0%b1%d0%b5%d1%81%d0%bf%d0%bb%d0%b0%d1%82%d0%bd%d0%b0%d1%8f-%d0%be%d1%82%d0%bf%d1%80%d0%b0%d0%b2%d0%ba%d0%b0-sms-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-mailru/</link>
		<comments>http://zetblog.ru/programming/200811/python-%d0%b1%d0%b5%d1%81%d0%bf%d0%bb%d0%b0%d1%82%d0%bd%d0%b0%d1%8f-%d0%be%d1%82%d0%bf%d1%80%d0%b0%d0%b2%d0%ba%d0%b0-sms-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-mailru/#comments</comments>
		<pubDate>Tue, 18 Nov 2008 09:29:10 +0000</pubDate>
		<dc:creator>lizz</dc:creator>
				<category><![CDATA[Программирование]]></category>
		<category><![CDATA[сетевое]]></category>
		<category><![CDATA[Internet]]></category>
		<category><![CDATA[mail.ru]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[SMS]]></category>
		<category><![CDATA[SQLAlchemy]]></category>

		<guid isPermaLink="false">http://zetblog.ru/?p=15</guid>
		<description><![CDATA[В mail.ru агенте есть возможно одна полезная вещь - отправка до 50 sms в сутки с одного аккаунта. Протокол агента является открытым и его можно посмотреть на <a href="http://agent.mail.ru/ru/developers/protocol.html">сайте</a>, однако выложена не совсем свежая версия и не сказано ни слова об отправке sms, но это не проблема.]]></description>
			<content:encoded><![CDATA[<p>В mail.ru агенте есть возможно одна полезная вещь &#8212; отправка до 50 sms в сутки с одного аккаунта. Протокол агента является открытым и его можно посмотреть на <a href="http://agent.mail.ru/ru/developers/protocol.html">сайте</a>, однако выложена не совсем свежая версия и не сказано ни слова об отправке sms, но это не проблема.<br />
<span id="more-15"></span></p>
<p>Итак, смотрим описание протокола. Для начала нам надо подключится к mrim.mail.ru на порт 2042 или 443 и адрес сервера агента и порт в обычном текстовом формате &#171;ip:port&#187;. Затем надо отправить пакет MRIM_CS_HELLO. Как сказано в описании, заголовок пакета имеет следующий формат:</p>
<pre class="brush: cpp">
u_long	magic;
u_long	proto;
u_long	seq;
u_long	msg;
u_long	dlen;
u_long	from;
u_long	fromport;
u_char	reserved[16];
</pre>
<p>Размер u_long&#8217;а &#8212; 4 байта. Первое значение magic соответствует числу 0xDEADBEEF &#8212; своеобразный юмор разработчиков, если смотреть на это значение как на строку, то перевод будет что-то вроде &#171;мёртвая говядина&#187;. Однако разработчики mail.ru пошли дальше, что бы нас ещё больше рассмешить они решили отправлять все числовые значения задом наперёд, т.е. нам надо записать в заголовок не 0xDEADBEEF, а 0xEFBEADDE (пишем последний байт &#8212; EF, предпоследний &#8212; BE и т.д.). Строковый переменные отправляются в нормальном порядке. Всё это можно узнать запустив какой-нибудь снифер и посмотрев, что шлёт официальный агент.<br />
В итоге, что мы должны послать на сервер:</p>
<pre class="brush: cpp">magic = 0xEFBEADDE (соответствует 0xDEADBEEF)
proto = 0x07000100 (первые два байта - старшая версия протокола, вторые - младшая, 0x00010007)
seq = 0x01000000 (соответствует 0x000001)
msg = 0x01100000 (MRIM_CS_HELLO - 0x00001001)
dlen = 0x00000000 (длинна данных)
from = 0x00000000 (ip адрес клиента, указывать не обязательно)
fromport = 0x000000 (порт клиента, указывать не обязательно)
reserved[16] = 0x00000000000000000000000000000000 (зарезервировано)</pre>
<p>В ответ мы должны получить пакет с тем же номером последовательности (seq) и типом (msg) MRIM_CS_HELLO_ACK.</p>
<p>Оформим всё это в виде кода. Создаём файл packets.py в котором будут храниться определённые протоколом константы и класс packet с методами:</p>
<ul>
<li>setHost(host, port) для установки адреса и порта с которого происходит подключение к серверу (этот метод не очень то нужен);</li>
<li>getPacket(msg) &#8212; устанавливает тип пакета равный MSG, увеличивает текущий номер последовательности на 1 и возвращает готовый пакет для отправки в текстовом формате;</li>
<li>setPacket(p) &#8212; метод устанавливает из полученного пакета все свойства в классе, если в p содержится поле данных, то оно записывается в свойство data;</li>
<li>addRawData(data) добавляет к свойству data переданный параметр data и обновляет значение длины данных;</li>
<li>addLPSData(data) &#8212; добавляет к свойству data длину прикрепляемых данных (в обратном порядке, как и все числовые значения в протоколе) и сами данные;</li>
<li>addULData(data) &#8212; прикрепляет &#171;цифровые&#187; данные к пакету в обратном порядке;</li>
<li>clear() &#8212; сбрасывает свойства data, dlen, msg;</li>
<li>__d2s(d) &#8212; переводит число d в строку для передачи через сокеты на сервер;</li>
<li>__s2d(s) &#8212; обратный метода для __d2s(d);</li>
</ul>
<p>Вот как всё это безобразие выглядит:</p>
<pre class="brush: python">""" MRIM constants """
PROTO_VERSION_MAJOR             = 1
PROTO_VERSION_MINOR             = 12 #7
PROTO_VERSION                   = (PROTO_VERSION_MAJOR &lt;&lt; 16) | PROTO_VERSION_MINOR
CS_MAGIC                        = 0xDEADBEEF

MRIM_CS_HELLO                   = 0x1001  # C -&gt; S
MRIM_CS_HELLO_ACK               = 0x1002  # S -&gt; C
MRIM_CS_LOGIN_ACK               = 0x1004  # S -&gt; C
MRIM_CS_LOGIN_REJ               = 0x1005  # S -&gt; C
MRIM_CS_PING                    = 0x1006  # C -&gt; S
MRIM_CS_MESSAGE                 = 0x1008  # C -&gt; S
MESSAGE_FLAG_OFFLINE            = 0x00000001
MESSAGE_FLAG_NORECV             = 0x00000004
MESSAGE_FLAG_AUTHORIZE          = 0x00000008  # X-MRIM-Flags: 00000008
MESSAGE_FLAG_SYSTEM             = 0x00000040
MESSAGE_FLAG_RTF                = 0x00000080
MESSAGE_FLAG_CONTACT            = 0x00000200
MESSAGE_FLAG_NOTIFY             = 0x00000400
MESSAGE_FLAG_MULTICAST          = 0x00001000
MAX_MULTICAST_RECIPIENTS        = 50
MESSAGE_USERFLAGS_MASK          = 0x000036A8  # Flags that user is allowed to set himself
MRIM_CS_MESSAGE_ACK             = 0x1009  # S -&gt; C
MRIM_CS_MESSAGE_RECV            = 0x1011  # C -&gt; S
MRIM_CS_MESSAGE_STATUS          = 0x1012  # S -&gt; C
MESSAGE_DELIVERED               = 0x0000  # Message delivered directly to user
MESSAGE_REJECTED_NOUSER         = 0x8001  # Message rejected - no such user
MESSAGE_REJECTED_INTERR         = 0x8003  # Internal server error
MESSAGE_REJECTED_LIMIT_EXCEEDED = 0x8004  # Offline messages limit exceeded
MESSAGE_REJECTED_TOO_LARGE      = 0x8005  # Message is too large
MESSAGE_REJECTED_DENY_OFFMSG    = 0x8006  # User does not accept offline messages
MRIM_CS_USER_STATUS             = 0x100F  # S -&gt; C
STATUS_OFFLINE                  = 0x00000000
STATUS_ONLINE                   = 0x00000001
STATUS_AWAY                     = 0x00000002
STATUS_UNDETERMINATED           = 0x00000003
STATUS_FLAG_INVISIBLE           = 0x80000000
MRIM_CS_LOGOUT                  = 0x1013  # S -&gt; C
LOGOUT_NO_RELOGIN_FLAG          = 0x0010  # Logout due to double login
MRIM_CS_CONNECTION_PARAMS       = 0x1014  # S -&gt; C
MRIM_CS_USER_INFO               = 0x1015  # S -&gt; C
MRIM_CS_ADD_CONTACT             = 0x1019  # C -&gt; S
CONTACT_FLAG_REMOVED            = 0x00000001
CONTACT_FLAG_GROUP              = 0x00000002
CONTACT_FLAG_INVISIBLE          = 0x00000004
CONTACT_FLAG_VISIBLE            = 0x00000008
CONTACT_FLAG_IGNORE             = 0x00000010
CONTACT_FLAG_SHADOW             = 0x00000020
MRIM_CS_ADD_CONTACT_ACK         = 0x101A  # S -&gt; C
CONTACT_OPER_SUCCESS            = 0x0000
CONTACT_OPER_ERROR              = 0x0001
CONTACT_OPER_INTERR             = 0x0002
CONTACT_OPER_NO_SUCH_USER       = 0x0003
CONTACT_OPER_INVALID_INFO       = 0x0004
CONTACT_OPER_USER_EXISTS        = 0x0005
CONTACT_OPER_GROUP_LIMIT        = 0x6
MRIM_CS_MODIFY_CONTACT          = 0x101B  # C -&gt; S
MRIM_CS_MODIFY_CONTACT_ACK      = 0x101C  # S -&gt; C
MRIM_CS_OFFLINE_MESSAGE_ACK     = 0x101D  # S -&gt; C
MRIM_CS_DELETE_OFFLINE_MESSAGE  = 0x101E  # C -&gt; S
MRIM_CS_AUTHORIZE               = 0x1020  # C -&gt; S
MRIM_CS_AUTHORIZE_ACK           = 0x1021  # S -&gt; C
MRIM_CS_CHANGE_STATUS           = 0x1022  # C -&gt; S
MRIM_CS_GET_MPOP_SESSION        = 0x1024  # C -&gt; S
MRIM_CS_MPOP_SESSION            = 0x1025  # S -&gt; C
MRIM_GET_SESSION_FAIL           = 0
MRIM_GET_SESSION_SUCCESS        = 1
MRIM_CS_WP_REQUEST              = 0x1029  # C-&gt;S
PARAMS_NUMBER_LIMIT             = 50
PARAM_VALUE_LENGTH_LIMIT        = 64
MRIM_CS_ANKETA_INFO                = 0x1028  # S-&gt;C
MRIM_ANKETA_INFO_STATUS_OK         = 1
MRIM_ANKETA_INFO_STATUS_NOUSER     = 0
MRIM_ANKETA_INFO_STATUS_DBERR      = 2
MRIM_ANKETA_INFO_STATUS_RATELIMERR = 3
MRIM_CS_MAILBOX_STATUS             = 0x1033
MRIM_CS_CONTACT_LIST2              = 0x1037  # S-&gt;C
GET_CONTACTS_OK                    = 0x0000
GET_CONTACTS_ERROR                 = 0x0001
GET_CONTACTS_INTERR                = 0x0002
CONTACT_INTFLAG_NOT_AUTHORIZED     = 0x0001
MRIM_CS_LOGIN2                     = 0x1038  # C -&gt; S
MAX_CLIENT_DESCRIPTION             = 256

class packet:
  """ MRIM handler """
  magic     = CS_MAGIC
  proto     = PROTO_VERSION
  seq       = 0x00000000
  msg       = 0x00000000
  dlen      = 0x00000000
  from_addr = 0x00000000
  from_port = 0x00000000
  reserved  = 0x00000000
  data      = ""
  size      = 44

  def __init__(self):
    self.reserved  = self.__d2s(0x00000000, 16)  # 16 байт

  def setHost(self, host, port):
    self.from_host, self.from_port = host, port
    return True

  def getPacket(self, msg):
    self.seq += 1
    self.msg  = msg
    self.dlen = len(self.data)
    return self.__d2s(self.magic, 4) + self.__d2s(self.proto, 4) + \
      self.__d2s(self.seq, 4) + self.__d2s(self.msg, 4) + \
      self.__d2s(self.dlen, 4) + self.__d2s(self.from_addr, 4) + \
      self.__d2s(self.from_port, 4) + self.reserved + self.data

  def setPacket(self, p):
    if len(p) &lt; 44:
      return False
    self.t = p
    self.magic     = self.__s2d(p[   :  4])
    self.proto     = self.__s2d(p[4  :  8])
    self.seq       = self.__s2d(p[8  : 12])
    self.msg       = self.__s2d(p[12 : 16])
    self.dlen      = self.__s2d(p[16 : 20])
    self.from_addr = self.__s2d(p[20 : 24])
    self.from_port = self.__s2d(p[24 : 28])
    self.reserved  = self.__s2d(p[28 : 44])
    if len(p[44 : ]):
      self.data    = self.__s2d(p[44 : ])
    else:
      self.data    = ""
    return True

  def addRawData(self, data):
    self.data += data
    self.dlen = len(self.data)
    return True

  def addLPSData(self, data):
    dlen = len(data)
    self.data += self.__d2s(dlen, 4)
    self.data += data
    self.dlen = len(self.data)
    return True

  def addULData(self, data):
    self.data += self.__d2s(data, 4)
    return True

  def clear(self):
    self.data = ""
    self.dlen = ""
    self.msg = ""

  def __d2s(self, d, size = 4):
    d = hex(d)[2 : ]
    if d[-1] == "L":
      d = d[ : -1]
    ln = len(d)
    if ln % 2:
      ln += 1
      d = "0" + d
    i = 0
    r = ""
    while i &lt; ln:
      r = chr(int(d[i : i + 2], 16)) + r
      i += 2
    while len(r) &lt; size:
      r += chr(0x00)
    return r

  def __s2d(self, s):
    r = ""
    i = len(s)
    while i:
      t = hex(ord(s[i - 1: i]))[2 : ]
      if len(t) &lt; 2: t = "0" + t
      r += t
      i -= 1
    return int(r, 16)
</pre>
<p>Теперь создадим файл emails.py в котором у нас будут храниться функции, связанные с обработкой аккаунтов. Для хранения информации об аккаунтах я выбрал sqlite. На это меня побудило 2 причины: 1 &#8212; это удобно :), 2 &#8212; мне надо было разобраться особенностями работы с СУБД из-под python (в частности, надо было разобраться с sqlalchemy).</p>
<p>В emails.py у нас содержится описание таблицы с нашими аккаунтами, в которой будут следующие поля:</p>
<ul>
<li>login &#8212; собственно, сам email;</li>
<li>password &#8212; пароль от него;</li>
<li>limit &#8212; по умолчанию равен 50 &#8212; количество sms, которое можно отправить в сутки с аккаунта (mail.ru не позволяет больше);</li>
<li>last_sms &#8212; время отправки последней sms, mail.ru запрещает слать sms чаще, чем раз в минуту;</li>
</ul>
<p>А так же следующие функции:</p>
<ul>
<li>add_emails(emails, session) &#8212; открывает файл с мыльниками и паролями (разделены пробелами) и добавляет их в нашу базу, если таких аккаунтов там ещё нет, возвращает число добавленных аккаунтов;</li>
<li>update_limits(session) &#8212; устанавливает лимит sms равный 50 у тех аккаунтов, у которых со времени последней отправки sms прошло больше суток;</li>
</ul>
<p>Код у меня выглядит так:</p>
<pre class="brush: python">
import sqlalchemy as sa
from sqlalchemy.orm import mapper, sessionmaker
import time

engine = sa.create_engine("sqlite:///database.db", echo=False)
metadata = sa.MetaData()

emails_table = sa.Table("emails", metadata,
                        sa.Column("email", sa.String(convert_unicode=False), primary_key=True),
                        sa.Column("password", sa.String(convert_unicode=False), nullable=False),
                        sa.Column("limit", sa.Integer, nullable=False, default=50),
                        sa.Column("last_sms", sa.Integer, nullable=False, default=0)
                       )

metadata.create_all(engine)

class Email(object):
  def __init__(self, email, password, limit = 50, last_sms = 0):
    self.email      = email
    self.password   = password
    self.limit      = limit
    self.last_sms   = last_sms

  def __repr__(self):
    return "" % (self.email, self.password, self.limit, self.last_sms)

def add_emails(emails, session):
  try:
    femails = open(emails, "r")
  except:
    print "Error. Can't open file %s." % emails
    return False
  try:
    counter = 0
    for e in femails:
      login, password = e.strip("\r\n").split(" ", 1)
      c = session.query(Email).filter(Email.email == login).count()
      if not c:
        session.add(Email(login, password))
        counter += 1
    session.commit()
  except:
    femails.close()
    print "Error. Unknow exception :-(."
    return False
  femails.close()
  return counter

def update_limits(session):
  e = session.query(Email).filter(Email.last_sms &lt;= int(time.mktime(time.localtime()) - 86400))
  for email in e:
    email.limit = 50
  session.commit()

mapper(Email, emails_table)
Session = sessionmaker(bind = engine)
</pre>
<p>Теперь пришло время заняться самим клиентом. Создаём очередной файл clnt.py и описываем в нём класс со следующими методами:</p>
<ul>
<li>connect() &#8212; создание сокета и подключение к серверу;</li>
<li>hello() &#8212; отправка MRIM_CS_HELLO и получение в ответ MROM_CS_HELLO_ACK;</li>
<li>auth(login, passwd) &#8212; отправка запроса на авторизацию;</li>
<li>sendSMS(number, text) &#8212; отправить sms на номер number. Стоит отметить, что тип пакета с sms &#8212; 0&#215;00001039, сам пакет имеет следующий формат &#8212; UL 0&#215;00000000 LPS +71234567890, LPS текст sms, где +71234567890 &#8212; номер на который мы отправляем сообщение. Так же ещё одна особенность &#8212; если среди наших контактов в агенте нет ни одного с указанным номером телефона, то сообщения не будут доходить;</li>
<li>send(msg) &#8212; пишет msg в уже открытый сокет;</li>
<li>get() &#8212; читает из сокета и возвращает результат;</li>
<li>close() &#8212; закрывает сокет</li>
<li>start(login, passwd) &#8212; вызывает последовательно все необходимые методы для подключения к серверу;</li>
<li>__init__(rhost, rport) &#8212; конструктор, ему передаётся ip-адрес и порт к которому будет производиться подключение;</li>
</ul>
<pre class="brush: python"># -*- coding: cp1251 -*-

import socket
import os
import urllib
from packets import *

class clnt:
  rhost = ""
  rport = 0
  sock = False
  ping = 0
  packet = False
  rcvd_packet = False

  def __init__(self, rhost, rport):
    self.rhost, self.rport = rhost, rport
    self.packet = p_header()
    self.rcvd_packet = p_header()

  def connect(self):
    # Создаём сокет и подключаемся
    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
      self.sock.connect((self.rhost, self.rport))
      addr, port = self.sock.getsockname()
      self.packet.setHost(addr, port)
    except:
      print "\nError. Connection to server %s:%d failed." % (self.rhost, self.rport)
      return False
    return True

  def start(self, login, passwd):
    if not self.connect(): return False
    if not self.hello(): return False
    if not self.auth(login, passwd): return False

    return True

  def hello(self):
    self.send(self.packet.getPacket(MRIM_CS_HELLO))
    #print "Sent MRIM_CS_HELLO."
    try:
      self.rcvd_packet.setPacket(self.get())
      if self.rcvd_packet.msg == MRIM_CS_HELLO_ACK:
        #print "Recieved MRIM_CS_HELLO_ACK.",
        self.ping = self.rcvd_packet.data
        #print "Ping timeout %ds." % self.ping
      else:
        print "\nError. Not recieved MRIM_CS_HELLO_ACK."
        return False
    except:
      print "\nError. Not recieved MRIM_CS_HELLO_ACK."
      return False
    return True

  def auth(self, login, passwd):
    self.packet.clear()
    self.packet.addLPSData(login)
    self.packet.addLPSData(passwd)
    self.packet.addULData(STATUS_ONLINE)
    self.packet.addLPSData("STATUS_ONLINE")
    self.packet.addLPSData("FreeAgent v 0.1")
    print "Trying to authorize as %s..." % login,
    try:
      self.send(self.packet.getPacket(MRIM_CS_LOGIN2))
    except:
      print "\nError. Can't send MRIM_CS_LOGIN2."
      return False
    try:
      self.rcvd_packet.setPacket(self.get())
    except:
      print "\nError. Not recievd answer from server."
      return False
    if self.rcvd_packet.msg == MRIM_CS_LOGIN_ACK:
      print "success!"
      return True
    else:
      print "error :-(."
      return False

  def sendSMS(self, number, text):
    ftext = ""
    ftext = text
    self.packet.clear()
    self.packet.addULData(0x0)
    self.packet.addLPSData(number)
    self.packet.addLPSData(ftext)
    print "Sending to %s: %s..." % (number, text),
    try:
      self.send(self.packet.getPacket(0x00001039))
    except:
      print "\nError. Can't send sms"
      return False
    print "Ok!"
    return True

  def send(self, msg):
    try:
      self.sock.send(msg)
    except:
      print "\nError. Can't send data."
      return False
    return True

  def get(self):
    return self.sock.recv(4096)

  def close(self):
    self.sock.close()
    return True
</pre>
<p>Все основные функции и классы написаны, осталось их только использовать. Создаём главный файл sms.py.<br />
Первое, что мы в нём делаем &#8212; это &#171;обновляем&#187; лимиты в 50 сообщений в сутки, далее проверяем количество аргументов командной строки, если оно равно трём, то считаем, что нам переданы номер, на который надо отправить sms, и текст (т.е. программа была запущена так: sms.py +71234567890 &#171;testovoe soobshenie&#187;), иначе входим в интерактивный режим.<br />
Следующий шаг &#8212; пытаемся подключиться к адресу &#171;http://mrim.mail.ru:2042&#8243; и узнать адрес сервера к которому нам рекомендуют подключаться на данный момент.<br />
Выбираем из нашей БД список ящиков, у которых не превышен лимит, упорядочиваем (по возрастанию) по дате отправки последнего сообщения и отправляем sms с 1го, уменьшая лимит этого ящика на 1. Если со времени последней отправки sms не прошла ещё 61 секунда, то перед отправкой ждём.</p>
<pre class="brush: python"># -*- coding: cp1251 -*-

from clnt import *
import sys
import time
import urllib
from emails import *
import os

session = Session()
update_limits(session)

if len(sys.argv) == 3:
  number = sys.argv[1]
  text = sys.argv[2]
else:
  while True:
    action = raw_input("Select action:\n1 - add emails from file\n2 - send sms\n")
    if action == "1":
      ef = raw_input("Enter file name: ")
      print "%d new emails added." % add_emails(ef, session)
    elif action == "2":
      number = raw_input("Enter phone number: ")
      text = raw_input("Enter SMS text: ")
      break
    else:
      print "Invalid action!"

try:
  saddr = urllib.urlopen("http://mrim.mail.ru:2042").readlines()
  saddr = saddr[0]
  shost, sport = saddr[:-1].split(":")
  sport = int(sport)
except:
  print "\nError. Can't connect to mrim.mail.ru."
  sys.exit(0)

emails = session.query(Email).filter(Email.limit &gt; 0).order_by(Email.last_sms)
if not emails.count():
  print "Sorry, all limits exceeded."
  sys.exit(0)
if emails[0].last_sms &gt; (int(time.mktime(time.localtime()) - 61)):
  print "Waiting %d seconds." % (61 - (int(time.mktime(time.localtime())) - emails[0].last_sms))
  time.sleep(61 - (int(time.mktime(time.localtime())) - emails[0].last_sms))
c = clnt(shost, sport)
if not c.start(str(emails[0].email), str(emails[0].password)):
  print "Error."
  sys.exit(0)
c.sendSMS(number, text)
emails[0].last_sms = int(time.mktime(time.localtime()))
emails[0].limit -= 1
session.commit()
c.close()
session.commit()
</pre>
<p>На этом наша программа таки уже закончена. Стоит отметить что не проверяется длина сообщений и отправка кириллицы протестирована только в кодировке CP1251. Все использованные файлы можно скачать из <a href="http://zetblog.ru/wp-files/15/sms.rar">прикреплённого архива</a>.</p>
<p>Ссылки к статье:<br />
<a href="http://agent.mail.ru/ru/developers/protocol.html">http://agent.mail.ru/ru/developers/protocol.html</a> &#8212; описание протокола агента.<br />
<a href="http://sqlalchemy.org">http://sqlalchemy.org</a> &#8212; официальный сайт sqlalchemy.<br />
<a href="http://www.arechisoft.com/">http://www.arechisoft.com/</a> &#8212; пакетный снифер EtherSnoop.<br />
<a href="http://yo.jabber.ru/files/mrim/">http://yo.jabber.ru/files/mrim/</a> &#8212; Mrim &#8212; это Jabber-транспорт для IM-сервиса компании Mail.Ru написанный на python.</p>
]]></content:encoded>
			<wfw:commentRss>http://zetblog.ru/programming/200811/python-%d0%b1%d0%b5%d1%81%d0%bf%d0%bb%d0%b0%d1%82%d0%bd%d0%b0%d1%8f-%d0%be%d1%82%d0%bf%d1%80%d0%b0%d0%b2%d0%ba%d0%b0-sms-%d1%87%d0%b5%d1%80%d0%b5%d0%b7-mailru/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
	</channel>
</rss>

