ورود

View Full Version : مشکل عجیب در Indy



مهران رسا
جمعه 18 تیر 1389, 13:42 عصر
سلام ؛

تا حالا این مدلیش رو ندیده بودم .
در سرور یک شمارنده داریم که با هر بار اجرای رویداد OnExecute به مقدارش یک واحد اضافه میشه . در حالی که داریم کلاینت رو توسط یک نرم افزار Sniff میکنیم ، یک پیغام برای سرور ارسال میکنیم . سرور در رویداد OnExecute جوابی برای کلاینت ارسال میکنه . Sniffer تایید میکنه که کلاینت یک ارسال داشته و یک دریافت اما مقدار شمارنده هیچ تغییری نمیکنه . حتی با قرار دادن ShowMessage در رویداد OnExecute هم دیالوگ ShowMessage نشون داده نمیشه و این در حالیه که بقیه پیغام هایی که به سرور ارسال میکنیم هم در Sniffer ثبت میشند ، هم مقدار شمارنده رو زیاد میکنند و هم Showmessage نشون داده میشه . اما این پیغام ما با این مشکل مواجه میشه .

این هم پیغامی که به سرور ارسال میشه :


chr(3) + chr(1) + chr(3) + chr(0) + chr(0) + chr(5)

vcldeveloper
جمعه 18 تیر 1389, 18:33 عصر
اینجوری که نمیشه حدس زد شما چه کدی برای ارسال و دریافت داده نوشتید.

در ضمن، OnExecute در Context یک Worker Thread که به ازاء هر اتصال ساخته شده، اجرا میشه، پس نباید در اون از کدهای مرتبط با رابط کاربر استفاده کنید.

نکته دوم هم اینکه، شمارنده شما چطور پیاده سازی شده؟ شمارنده ایی که مقدارش در OnExecute تغییر میکنه، یعنی میتونه در یک لحظه واحد، مقدارش توسط چند Thread تغییر داده بشه! همچین داده ایی نیاز به Synchronization داره، و دسترسی به اون باید Serialize بشه.

مهران رسا
شنبه 19 تیر 1389, 12:06 عصر
در ضمن، OnExecute در Context یک Worker Thread که به ازاء هر اتصال ساخته شده، اجرا میشه، پس نباید در اون از کدهای مرتبط با رابط کاربر استفاده کنید.ببخشید آقای کشاورز متوجه نشدم . منظور از کدهای مرتبط با کاربر چیه ؟
تا اونجایی که یادم هست توی مقالاتی که خوندم نوشته شده بود حالت پرسش و پاسخ بین کلاینت و سرور در رویداد OnExecute سرور انجام میشه . در واقع کلاینت درخواست ارسال میکنه ، سرور در رویداد OnExecute عملیات مورد نظر رو بر روی درخواست انجام میده و در همون رویداد پاسخ رو برای کلاینت رسال میکنه .


نکته دوم هم اینکه، شمارنده شما چطور پیاده سازی شده؟ شمارنده ایی که مقدارش در OnExecute تغییر میکنه، یعنی میتونه در یک لحظه واحد، مقدارش توسط چند Thread تغییر داده بشه! همچین داده ایی نیاز به Synchronization داره، و دسترسی به اون باید Serialize بشه. به این صورت استفاده کردم :



procedure TMainForm.TCPServerExecute(AContext: TIdContext);
Begin
Requests := Requests + 1;
End;

Requests یک متغیر Global هست .

vcldeveloper
شنبه 19 تیر 1389, 13:23 عصر
ببخشید آقای کشاورز متوجه نشدم . منظور از کدهای مرتبط با کاربر چیه ؟
کدهای مرتبط با کاربر نه، بلکه کدهای مرتبط با رابط کاربر؛ یا بهتره بگیم؛ رابط کاربر گرافیکی کاربر.
شما نباید رابط گرافیکی کاربر رو از طریق هیچکدوم از Threadها، غیر از Thread اصلی برنامه، تغییر بدید.


به این صورت استفاده کردم

Requests یک متغیر Global هست .
اون کار اشتباه هست، و Request باید حتما با یک Synchronization Object محافظت بشه. ساده ترین حالتش این هست که یک Critical Section ایجاد کنید، و هر زمان که Threadایی میخواد به Request دسترسی پیدا کنه، کد مربوطه در داخل Critical Section اجرا بشه.

مهران رسا
شنبه 19 تیر 1389, 15:52 عصر
راستش من چیز زیادی در مورد Critical Section نمیدونم . اگه اشتباه نکنم Critical Section به عنوان راهی استفاده میشه تا Thread ها مختلف بتونند پشت سرهم به یک داده ی عمومی دسترسی داشته باشند . تعریف Critical Section به این صورت صحیح هست ؟


procedure TMainForm.TCPServerExecute(AContext: TIdContext);
Begin
ChangeCounter;
End;



procedure TMainForm.ChangeCounter;
begin
EnterCriticalSection(Cr);
Requests := Requests + 1;
LeaveCriticalSection(Cr);
end;Cr یک متغیر عمومی از نوع TRTLCriticalSection هست .

vcldeveloper
شنبه 19 تیر 1389, 17:56 عصر
تعریف Critical Section به این صورت صحیح هست ؟
بله، البته باید برای خواندن آن هم از Critical Section استفاده کنید. در ضمن، Critical Section را باید Create کنید (مثلا در متد Create سرور مربوطه). بهتر هست که این متغیر رو به صورت یک Property که متدهای setter و getter داره، بنویسید:

type
TMyServer = class
private
FSyncObj : TCriticalSection;
FRequestCount : Integer;
function GetRequestCount : Integer;
procedure SetRequestCount(Value: Integer);
public
constructor Create;
destructor Destroy; override;
property RequestCount : Integer; read GetRequestCount write SetRequestCount;
end;

implementation

constructor TMyServer.Create;
begin
inherited;
FSyncObj := TCriticalSection.Create;
end;

destructor TMyServer.; override;
begin
FSyncObj.Free;
inherited
end;

function TMyServer.GetRequestCount : Integer;
begin
FSyncObj.Enter;
try
Result := FRequestCount;
finally
FSyncObj.Leave;
end;
end;

procedure TMyServer.SetRequestCount(Value: Integer);
begin
FSyncObj.Enter;
try
FRequestCount := Value;
finally
FSyncObj.Leave;
end;
end;

مهران رسا
شنبه 19 تیر 1389, 22:51 عصر
برای استفاده از نوع TCriticalSection باید کتابخانه بخصوصی use بشه ؟ متاسفانه نه TCriticalSection رو میشناسه و نه در نوع TRTLCriticalSection متدی با نام Enter وجود داره .

vcldeveloper
دوشنبه 21 تیر 1389, 12:54 عصر
برای استفاده از نوع TCriticalSection باید کتابخانه بخصوصی use بشه ؟TCriticalSection یک Wrapper ساده برای Critical Section ویندوز هست، و در یونیت SyncObjs تعریف شده. اگر بخواید مستقیما از TRTLCriticalSection استفاده کنید، باید از توابع API ویندوز مثل EnterCriticalSection یا LeaveCriticalSection و غیره استفاده کنید.

مهران رسا
چهارشنبه 20 مرداد 1389, 10:24 صبح
آقای کشاورز در زمان استفاده از Critical Section به روشی که در بالا ذکر کردید ، وقتی تعداد زیادی درخواست به سرور ارسال میشه سرور کاملاً هنگ میکنه !


شما نباید رابط گرافیکی کاربر رو از طریق هیچکدوم از Threadها، غیر از Thread اصلی برنامه، تغییر بدید.اگه کد مربوط به تغییر رابط گرافیکی (مثلاً تغییر Caption برچسب) رو در یک متد در Thread اصلی برنامه بنویسم و از طریق OnExecute اون متد رو صدا بزنم بازم شرایط یکسان هست ؟

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
begin
Report(AContext.Connection.Socket.Binding.PeerIP);
end;

procedure TForm1.Report(ClientIP: string);
begin
Label1.Caption := ClientIP;
end;

vcldeveloper
جمعه 22 مرداد 1389, 18:52 عصر
آقای کشاورز در زمان استفاده از Critical Section به روشی که در بالا ذکر کردید ، وقتی تعداد زیادی درخواست به سرور ارسال میشه سرور کاملاً هنگ میکنه !
خب دیگه، برنامه نویسی Multi-threaded چندان ساده نیست! Critical Section دسترسی به یک بخشی از کد برنامه شما را فقط به صورت سریال (پشت سر هم) توسط Threadهای مربوطه فراهم میکنه. حالا اگر تعداد Threadهایی که میخوان وارد اون بخش بشند، زیاد باشه، مسلما ترافیک Threadهای منتظر ورود به اون بخش زیاد میشه. البته مسائل دیگه هم میتونه باشه، مثلا یکی از مشکلات شایع در طراحی برنامه های Multi-threaded رخ دادن Deadlock هست؛ یعنی شما در کدتان شرایط خاصی را در نظر نگیرید، و اون شرایط باعث بشه که تمامی Threadهای برنامه منتظر هم بمانند! حالا اینکه برای رفع مشکل شما چیکار میشه کرد، بستگی به برنامه تون، طراحی آن، و کدهایی که نوشتید داره. گاهی اوقات Critical Section بهترین گزینه در دسترس شما برای Synchronization نیست، مثلا حالتی رو تصور کنید که یک داده مشترک دارید، و تعداد کمی Thread این داده را تغییر میدند، ولی تعداد زیادی از Threadها این داده را می خوانند. در همچین حالتی، استفاده از Multi read exclusive write کارایی بهتری نسبت به یک Critical Section عادی داره.



آقای کشاورز در زمان استفاده از Critical Section به روشی که در بالا ذکر کردید ، وقتی تعداد زیادی درخواست به سرور ارسال میشه سرور کاملاً هنگ میکنه !
منظور از Thread اون کلاس TThread نیست. Thread یک مسیر اجرای کد هست، مثل یک جاده، شما هر کدی رو که در اون Thread اجرا کنید، در اون جاده حرکت میکنه. پس فرقی نمیکنه که شما یک متد را در Form اصلی برنامه تون تعریف کرده باشید، یا در یک کلاس TThread یا هر کلاس دیگه ایی، وقتی اون کد در متد Execute یک Thread اجرا میشه، یعنی اون کد در داخل اون Thread اجرا شده.