Решение проблемы долговременных состояний в многопоточной среде

В искомом решении сочетаются несколько компонентов:

• Библиотека DLL, в которой содержатся функции, обеспечивающие отправку и прием сообщений.

• Функция, представляющая точку входа в DLL.

• Локальная область хранения потока (TLS, глава 7). Подключение процесса к библиотеке сопровождается созданием индекса DLL, а отключение — уничтожением. Значение индекса хранится в статическом хранилище, доступ к которому имеют все потоки.

• Структура, в которой хранится буфер и его текущее состояние. Структура распределяется всякий раз, когда к библиотеке подключается новый поток, и его адрес сохраняется в записи TLS для данного потока. При отсоединении потока от библиотеки память, занимаемая его структурой, освобождается.

Таким образом, TLS играет роль статического хранилища, и у каждого потока имеется собственная уникальная копия этого хранилища.

Пример: безопасная многопоточная DLL для обмена сообщениями через сокет

Программа 12.4 представляет собой DLL, содержащую две функции для обработки символьных строк (в именах которых в данном случае присутствует "CS", от character string — строка символов), или потоковые функции сокета (socket streaming functions): SendCSMessage и ReceiveCSMessage, а также точку входа DllMain (см. главу 5). Указанные две функции играют ту же роль, что и функция ReceiveMessage, а также функции, использованные в программах 12.1 и 12.2, и фактически заменяют их.

Функция DllMain служит характерным примером решения проблемы долговременных состояний в многопоточной среде и объединяет TLS и библиотеки DLL.

Освобождать ресурсы при отсоединении потоков (случай DLL_THREAD_DETACH) особенно важно в случае серверной среды; если этого не делать, то ресурсы сервера, в конечном счете, исчерпаются, что может привести к сбоям в его работе или снижению производительности или к тому и другому одновременно.

Примечание

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

Использующие эту DLL коды клиента и сервера, незначительно измененные по сравнению с программами 12.1 и 12.2, доступны на Web-сайте книги.

Программа 12.4. SendReceiveSKST: безопасная многопоточная DLL

/* SendReceiveSKST.с — DLL многопоточного потокового сокета. */

/* В качестве разделителей сообщений используются символы конца */

/* строки ('\0'), так что размер сообщения заранее не известен. */

/* Поступающие данные буферизуются и сохраняются в промежутках между */

/* вызовами функций. */

/* Для этой цели используются локальные области хранения потоков */

/* (Thread Local Storage, TLS), обеспечивающие каждый из потоков */

/* собственным закрытым "статическим хранилищем". */

 

#define _NOEXCLUSIONS

#include "EvryThng.h"

#include "ClntSrvr.h" /* Определяет записи запроса и ответа. */

 

typedef struct STATIC_BUF_T {

/* "static_buf" содержит "static_buf_len" байтов остаточных данных. */

/* Символы конца строки (нулевые символы) могут присутствовать, а могут */

/* и не присутствовать. */

char static_buf[MAX_RQRS_LEN] ;

LONG32 static_buf_len;

} STATIC_BUF;

 

static DWORD TlsIx = 0; /* Индекс TLS – ДЛЯ КАЖДОГО ПРОЦЕССА СВОЙ ИНДЕКС.*/

/* Для однопоточной библиотеки использовались бы следующие определения:

static char static_buf [MAX_RQRS_LEN];

static LONG32 static_buf_len; */

/* Основная функция DLL. */

 

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {

STATIC_BUF * pBuf;

switch (fdwReason) {

case DLL_PROCESS_ATTACH:

TlsIx = TlsAlloc();

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

case DLL_THREAD_ATTACH:

/* Указать, что память не была распределена. */

TlsSetValue(TlsIx, NULL);

return TRUE; /* В действительности это значение игнорируется. */

case DLL_PROCESS_DETACH:

/* Отсоединить также основной поток. */

pBuf = TlsGetValue(TlsIx);

if (pBuf != NULL) {

free(pBuf);

pBuf = NULL;

}

return TRUE;

case DLL_THREAD_DETACH:

pBuf = TlsGetValue(TlsIx);

if (pBuf != NULL) {

free(pBuf);

pBuf = NULL;

}

return TRUE;

}

}

 

_declspec(dllexport)

BOOL ReceiveCSMessage(REQUEST *pRequest, SOCKET sd) {

/* Возвращаемое значение TRUE указывает на ошибку или отсоединение. */

BOOL Disconnect = FALSE;

LONG32 nRemainRecv = 0, nXfer, k; /* Должны быть целыми со знаком. */

LPSTR pBuffer, message;

CHAR TempBuf[MAX_RQRS_LEN + 1];

STATIC_BUF *p;

p = (STATIC_BUF *)TlsGetValue(TlsIx);

if (p == NULL) { /* Инициализация при первом вызове. */

/* Распределять это хранилище будут только те потоки, которым оно */

/* необходимо. Другие типы потоков могут использовать TLS для иных целей. */

р = malloc(sizeof(STATIC_BUF));

TlsSetValue(TlsIx, p);

if (p == NULL) return TRUE; /* Ошибка. */

p->static_buf_len = 0; /* Инициализировать состояние. */

}

message = pRequest->Record;

/* Считать до символа новой строки, оставляя остаточные данные в статическом буфере. */

for (k = 0; k < p->static_buf_len && p->static_buf[k] != '\0'; k++) {

message[k] = p->static_buf[k];

} /* k – количество переданных символов. */

if (k < p->static_buf_len) { /* В статическом буфере обнаружен нулевой символ. */

message[k] = '\0';

p->static_buf_len –= (k + 1); /* Скорректировать состояние статического буфера. */

memcpy(p->static_buf, &(p->static_buf[k + 1]), p->static_buf_len);

return FALSE; /* Входные данные сокета не требуются. */

}

 

/* Передан весь статический буфер. Признак конца строки не обнаружен.*/

nRemainRecv = sizeof(TempBuf) – 1 – p->static_buf_len;

pBuffer = message + p->static_buf_len;

p->static_buf_len = 0;

while (nRemainRecv > 0 && !Disconnect) {

nXfer = recv(sd, TempBuf, nRemainRecv, 0);

if (nXfer <= 0) {

Disconnect = TRUE;

continue;

}

nRemainRecv –= nXfer;

/* Передать в целевое сообщение все символы вплоть до нулевого, если таковой имеется. */

for (k =0; k < nXfer && TempBuf[k] != '\0'; k++) {

*pBuffer = TempBuf[k];

pBuffer++;

}

if (k >= nXfer) { /*Признак конца строки не обнаружен, читать дальше*/

nRemainRecv –= nXfer;

} else { /* Обнаружен признак конца строки. */

*pBuffer = '\0';

nRemainRecv = 0;

memcpy(p->static_buf, &TempBuf[k + 1], nXfer – k – 1);

p->static_buf_len = nXfer – k – 1;

}

}

return Disconnect;

}

 

_declspec(dllexport)

BOOL SendCSMessage(RESPONSE *pResponse, SOCKET sd) {

/* Послать запрос серверу в сокет sd. */

BOOL Disconnect = FALSE;

LONG32 nRemainSend, nXfer;

LPSTR pBuffer;

pBuffer = pResponse->Record;

nRemainSend = strlen(pBuffer) + 1;

while (nRemainSend > 0 && !Disconnect) {

/* Отправка еще не гарантирует, что будет отослано все сообщение. */

nXfer = send(sd, pBuffer, nRemainSend, 0);

if (nXfer <= 0) {

fprintf(stderr, "\nОтключение сервера до посылки запроса завершения");

Disconnect = TRUE;

}

nRemainSend –=nXfer;

pBuffer += nXfer;

}

return Disconnect;

}