PDA

View Full Version : مشکل در زمان ایجاد ارتباط میان Winsock و Indy



مهران رسا
جمعه 25 تیر 1389, 14:34 عصر
با استفاده از Visual Basic و کنترل Winsock سعی کردم به سرور Indy پیغام ارسال کنم :

برای اتصال به سرور Indy :
ـ
Winsock1.Connect "127.0.0.1", "3201"

برای ارسال پیغام به سرور Indy :
ـ
Winsock1.SendData "Hi There"

زمانی که اتصال بین Winsock و Indy برقرار میشه یک OnExecute در سرور اتفاق می افته . اما زمانی که برای ارسال پیغام اقدام میکنم ، پیغام ارسال نمیشه و هیچ رویداد OnExecuteی هم در سرور رخ نمیده .
نهایتاً وقتی کلاینت (برنامه Vb) رو از حافظه خارج میکنم یک دفعه سرور بعد از رسیدن به یک استثنا ، به صورت دیوانه وار شروع میکنه به نمایش بی نهایت پیغام Connection Closed Gracefully . (البته پیغام ها(MessageDlg) به صورتی پشت سرهم نمایش داده میشند . در واقع تنها بعد از بسته شدن دیالوگ فعلی ، دیالوگ بعدی نمایش داده میشه) .
و هر بار که استثنای Connection Closed Gracefully اتفاق می افته یک رویداد Onexecute هم رخ میده . ضمناً این در حالیست که اگر بعد از اتصال میان Winsock و Indy ، بدون ارسال پیغام (عدم اجرای خط دوم) برنامه vb رو از حافظه خارج کنم تنها یکبار Connection Closed Gracefully نمایش داده شده و همه چیز به خوبی و خوشی تموم میشه .

و در آخر ، این مشکل زمانی که با استفاده از متد WriteFile بین کلاینت/سرور Indy بخوایم فایل ردوبدل کنیم هم رخ میده . در واقع پس از اینکه متد WriteFile فراخوانی شد ، در صورتی که کلاینت رو از حافظه خارج کنیم(ارتباش رو قطع کنیم) دقیقاً مشکلی که در بالا ذکر شد رخ میده .

اگر اطلاعات کامل تری نیاز هست بگید توضیح بدم .

خیلی ممنون .

vcldeveloper
جمعه 25 تیر 1389, 17:26 عصر
به نظر میرسه مسئله در پیاده سازی اون کتابخانه WinSock در VB باشه؛ من نمی دونم که اون کتابخانه دقیقا چطور کار میکنه، ولی به نظر میرسه که بعد از هر درخواستی، خودش اتصال را میبنده! از اونجایی که از چگونگی کارکرد اون در VB اطلاع ندارم، نمی تونم اظهار نظر دقیقی بکنم. بهتره افرادی که روی نحوه کار اون کتابخانه در VB تسلط دارند، اظهار نظر کنند. اگر اینجا جواب نگرفتید، سوالتون رو در StackOverflow بپرسید؛ بعضی از افراد فعال در پروژه Indy اونجا به سوالات Indy پاسخ میدند؛ شاید اونها جواب دقیق تری بهتون بدند.

مهران رسا
جمعه 25 تیر 1389, 18:21 عصر
به نظر میرسه مسئله در پیاده سازی اون کتابخانه WinSock در VB باشه؛ من نمی دونم که اون کتابخانه دقیقا چطور کار میکنه، ولی به نظر میرسه که بعد از هر درخواستی، خودش اتصال را میبنده! از اونجایی که از چگونگی کارکرد اون در VB اطلاع ندارم، نمی تونم اظهار نظر دقیقی بکنم. بهتره افرادی که روی نحوه کار اون کتابخانه در VB تسلط دارند، اظهار نظر کنند. اگر اینجا جواب نگرفتید، سوالتون رو در StackOverflow بپرسید؛ بعضی از افراد فعال در پروژه Indy اونجا به سوالات Indy پاسخ میدند؛ شاید اونها جواب دقیق تری بهتون بدند.

سلام ؛

با تشکر از پاسختون ، اما این مشکل در زمان استفاده از WriteFile در خود Indy هم پدیدار میشه.


و در آخر ، این مشکل زمانی که با استفاده از متد WriteFile بین کلاینت/سرور Indy بخوایم فایل ردوبدل کنیم هم رخ میده . در واقع پس از اینکه متد WriteFile فراخوانی شد ، در صورتی که کلاینت رو از حافظه خارج کنیم(ارتباش رو قطع کنیم) دقیقاً مشکلی که در بالا ذکر شد رخ میده .

فایل های ضمیمه گویای موارد ذکر شده هستند .

vcldeveloper
یک شنبه 27 تیر 1389, 02:31 صبح
با تشکر از پاسختون ، اما این مشکل در زمان استفاده از WriteFile در خود Indy هم پدیدار میشه.
مشکل در استفاده ناصحیح شما از متد ReadLn هست. شما از متد ReadLn برای خواندن داده استفاده کردید (هیچ پارامتر خاصی هم براش مشخص نکردید)، متد ReadLn هم بعد از دریافت اولین کارکتر، تا رسیدن به کارکتر LF که نشان دهنده پایان داده هست، به خواندن ادامه میده، در حالی که فایل شما اصلا کارکتر LFایی نداره. سپس همچنان که ReadLn در حال تلاش برای خواندن هست، اتصال کلاینت را قطع می کنید، و از آنجایی که در سمت سرور اتصال هنوز به درستی قطع نشده، ReadLn مرتبا Exception میده.

اولا، وقتی در کلاینت از شیوه خاصی برای ارسال داده استفاده می کنید، سعی کنید در سمت دیگه اتصال هم شیوه متناظر با اون برای خواندن اطلاعات استفاده کنید. ReadLn متناظر WriteLn هست. WriteFile از Stream برای ارسال استفاده میکنه، پس استفاده از ReadStream برای خواندن داده در سمت دیگه اتصال برای آن بهتر هست.

در هر حال، با همون کدی که خودتون قرار دادید، اگر در انتهای اون خط از فایل متنی مربوطه یک بار Enter بزنید، و فایل را ذخیره کنید، مشکلات برطرف میشه (اگر ارسال با WriteLn بود، خودش کارکتر LF را به انتهای هر متن اضافه می کرد).

همچنین، در مواقعی که یک سمت از ارتباط اتصالش به طور ناگهانی و بدون اطلاع سمت دیگه ارتباط قطع میشه، یک استثناء از نوع EIdConnClosedGracefully دریافت می کنید. با دریافت این استثناء باید سمت دیگه روال های مربوط به خودش را در صورت نیاز انجام بده، و در نهایت اتصال را متوقف کنه (مثلا با فراخوانی متد Disconnect از AContext.Connection در سمت سرور).

مهران رسا
یک شنبه 27 تیر 1389, 11:16 صبح
اولا، وقتی در کلاینت از شیوه خاصی برای ارسال داده استفاده می کنید، سعی کنید در سمت دیگه اتصال هم شیوه متناظر با اون برای خواندن اطلاعات استفاده کنید
بله درسته ، اما شما شرایطی رو تصور کنید که یک کلاینت به صورت سهوی یا عمدی یک داده بدون کاراکتر CTRLF رو برای سرور ارسال کنه . در این صورت با ایجاد این مشکل ، به دلیل روی دادن استثناهای پی در پی سرور کاملاً هنگ میکنه . ضمن اینکه با اضافه کردن AContext.Connection.Disconnect در زمان روی دادن استثنا هم مشکل برطرف نشد .

vcldeveloper
یک شنبه 27 تیر 1389, 11:50 صبح
بله درسته ، اما شما شرایطی رو تصور کنید که یک کلاینت به صورت سهوی یا عمدی یک داده بدون کاراکتر CTRLF رو برای سرور ارسال کنه . در این صورت با ایجاد این مشکل ، به دلیل روی دادن استثناهای پی در پی سرور کاملاً هنگ میکنه .

در پست قبلی توضیح دادم: استفاده ناصحیح از ReadLn.
شما وقتی نمی دونید که محتوای داده ارسالی چی هست، و آیا در انتهای آن یک کارکتر پایان دهنده وجود داره یا نه، نباید از ReadLn استفاده کنید. ReadLn یک کاربرد خاص در سناریوهای مشخص داره. شما میخواید آن را در یک سناریویی که براش تعریف نشده، استفاده کنید، و انتظار دارید که درست عمل کنه. مثل این هست که شما ماشینی که برای راه رفتن در جاده طراحی شده را داخل دریا بیاندازید، و وقتی می بینید که ماشین در دریا راه نمیره، بگید ماشینش مشکل داره!

برای دریافت اون داده شما، متناسب با اطلاعاتی که از محتوای آن دارید، روش های خواندن متفاوتی وجود داره، مثل ReadStream یا ReadStrings و غیره.



ضمن اینکه با اضافه کردن AContext.Connection.Disconnect در زمان روی دادن استثنا هم مشکل برطرف نشد .
مشکلی نداره، اگر استثناء مربوطه EIdConnClosedGracefully باشه، با Disconnect کردن اتصال، دیگه تلاشی برای خواندن اطلاعات از سوکت مربوطه صورت نمیگیره.

مهران رسا
یک شنبه 27 تیر 1389, 16:05 عصر
مثل این هست که شما ماشینی که برای راه رفتن در جاده طراحی شده را داخل دریا بیاندازید، و وقتی می بینید که ماشین در دریا راه نمیره، بگید ماشینش مشکل داره!من مثال رو اینطوری بیان میکنم : فکر کنید یک دستگاه آب میوه گیری دارید که فقط باید میوه داخلش ریخته بشه . خودمون بلدیم چطور از دستگاه استفاده کنیم اما اگر فردی عمداً یا سهواً یک تکه آهن داخل دستگاه بندازه باعث شکسته شدن تیغه ها و خراب شدنش میشه و این درحالیست که ما در قبال قرار دادن دستگاه در دسترس دیگران انتظار داشتیم چیزی جز میوه داخلش ریخته نشه .


با Disconnect کردن اتصال، دیگه تلاشی برای خواندن اطلاعات از سوکت مربوطه صورت نمیگیره. من به سرور گفتم کلاً در صورت رسیدن به هر استثنایی ارتباط رو قطع کنه اما بازهم Connection Closed Gracefully ها پی در پی نمایان میشن .


برای دریافت اون داده شما، متناسب با اطلاعاتی که از محتوای آن دارید، روش های خواندن متفاوتی وجود داره، مثل ReadStream یا ReadStrings و غیره.ReadStrings با WriteLn همخوانی داره ؟

vcldeveloper
دوشنبه 28 تیر 1389, 04:21 صبح
ReadStrings با WriteLn همخوانی داره ؟
WriteLn کار خاصی انجام نمیده، فقط به انتهای هر داده ارسالی یک کارکتر پایان دهنده اضافه میکنه. در زمان خواندن همReadLn تا زمان رسیدن به کارکتر پایان دهنده مربوطه، خواندن را ادامه میده.
کل داده ایی که WriteLn در هر بار ارسال میکنه، برای ReadStrings میشه یک خط داده دریافتی.


فکر کنید یک دستگاه آب میوه گیری دارید که فقط باید میوه داخلش ریخته بشه . خودمون بلدیم چطور از دستگاه استفاده کنیم اما اگر فردی عمداً یا سهواً یک تکه آهن داخل دستگاه بندازه باعث شکسته شدن تیغه ها و خراب شدنش میشه و این درحالیست که ما در قبال قرار دادن دستگاه در دسترس دیگران انتظار داشتیم چیزی جز میوه داخلش ریخته نشه .
مثال اشتباهی هست. شما دو حالت دارید؛ یا سرور را خودتان نوشتید، یا دیگران نوشتند، و شما از سرور آنها استفاده می کنید.
اگر سرور را خودتان نوشتید، باز دو حالت دارید؛ یا محتوای پیام ها و نوع آنها برای سرور شما مشخص هست، و سرور شما یک پروتکل از پیش تعیین شده برای ارتباط داره، که در اون صورت، فقط مسئول پاسخگویی در چارچوب همون پروتکل هست، یا اینکه سرور شما هیچ اطلاعی از محتوا و نوع پیام ها نداره. اگر اطلاعی از محتوا نداره، پس نباید بره سراغ روش هایی که نیازمند پردازش محتوای پیام هستند. وقتی میخواید یک فایل رو که نمی دونید درش چی هست رو ارسال کنید، نباید انتظار داشته باشید که ReadLn براتون درست کار کنه، چون ReadLn پیام دریافتی رو پردازش میکنه، و شرایط خاصی برای پیام در نظر میگیره. حالا اینجا فایل شما LF نداشت، ReadLn استثناء داد. اگر فایل شما چند LF داشت، اون وقت یک مشکل دیگه براتون پیش میومد، اون هم اینکه همیشه فقط خط اول فایل خوانده می شد! حالا اگر فایل ارسالی باینری بود که اوضاع از این هم بدتر می شد! وقتی باید با پیام مربوطه به عنوان یک داده خام برخورد کرد، نباید برید سراغ همچین متدهایی، بلکه باید از متدهایی که به محتوای پیام کاری ندارند، مثل ReadStream استفاده کنید. برای ReadStream مهم نیست توی پیام چی هست، هر چی ارسال شده رو به صورت یک مجموعه از بایت ها میخونه و در یک stream میریزه. حالا برنامه نویس هر کاری خواست، با این stream انجام بده، این دیگه به اون متد مربوط نمیشه.

اگر سرور را شما ننوشتید، باید در چارچوب پروتکلی که سرور برای شما تعریف کرده، باهاش تعامل برقرار کنید، و اگر در اون چارچوب باهاش تعامل نداشتید، هر نتیجه ایی که گرفتید، سرور مسئولش نیست، مسئولش خودتون هستید که پروتکل مربوطه را به درستی رعایت نکردید.

مهران رسا
شنبه 02 مرداد 1389, 14:00 عصر
آقای کشاورز امکان داره نحوه استفاده از متد ReadStream رو توضیح بدید ؟ در واقع زمانی که در سرور از ReadStream استفاده میشه ، در سمت کلاینت اطلاعات رو با چه دستوری باید ارسال کرد ؟

ضمن اینکه چیز زیادی در مورد پارامترهای ReadStream متوجه نشدم :


ReadStream پروسیجری است که برای خواندن اطلاعات از IOHandler و ذخیره کردن آنها در AStream مورد استفاده قرار میگیرد .
پارامتر AByteCount به تعداد بایت هایی که از IOHandler در AStream خوانده میشوند اشاره میکند . وقتی پارامتر AByteCount مقدار 1- و پارامتر AReadUntilDisconnect مقدار False بگیرد ، ByteCount به عنوان یک داده Integer از IOHandler خوانده شده و اندازه AStream برای مطابقت با اندازه مورد انتظار IOHandler تنظیم میشود .

ممنون میشم راهنمایی بفرمایید

vcldeveloper
شنبه 02 مرداد 1389, 19:19 عصر
امکان داره نحوه استفاده از متد ReadStream رو توضیح بدید ؟


var
AStream : TMemoryStream;
begin
AStream := TMemoryStream.Create;
try
AContext.Connection.IOHandler.ReadStream(AStream);
finally
AStream.Free;
end;
end;


در واقع زمانی که در سرور از ReadStream استفاده میشه ، در سمت کلاینت اطلاعات رو با چه دستوری باید ارسال کرد ؟
کلاینت میتونه به هر شکلی داده را ارسال کنه؛ ReadStream با محتوای پیام کاری نداره، و پیام را به صورت جریانی از بایت ها میخونه. البته معمولا ReadStream زمانی استفاده میشه که در سمت کلاینت شما به جای string با طول مشخص، داده های باینری مثل یک شی، یا یک فایل را با استفاده از متدهایی مثل WriteStream یا WriteFile ارسال کنید.


ضمن اینکه چیز زیادی در مورد پارامترهای ReadStream متوجه نشدم
دو پارامتر آخرش اختیاری هستند. AByteCount زمانی استفاده میشه که شما اندازه داده دریافتی را می دانید، یا فقط میخواید اندازه مشخصی از داده دریافتی را بخوانید، و با مابقی داده دریافتی کاری ندارید. اگر مقدارش را تعیین نکنید، متناسب با وضعیت پارامتر آخر، مقدارش تعیین میشه. اگر پارامتر آخر False باشه، هر چی که در بافر ورودی دریافت شده باشه، در داخل AStream ریخته میشه.
AReadUntilDisconnect به ReadStream میگه که خواندن اطلاعات و ذخیره آن در Stream باید تا زمانی که کلاینت Disconnect میکنه، ادامه داشته باشه. در این حالت، تا زمانی که کلاینت Disconnect نکنه، هر چی که از کلاینت ارسال بشه، توسط ReadStream خوانده میشه و در Stream نوشته میشه.

مهران رسا
شنبه 02 مرداد 1389, 22:41 عصر
ممنون از پاسختون .
من کد رو به این صورت نوشتم :

سمت کلاینت :

TCPClinet.IOHandler.WriteLn('Salam');
ShowMessage('Data Sent!');

سمت سرور :
procedure TForm1.TCPServerExecute(AContext: TIdContext);
var
AStream: TMemoryStream;
S: string;
begin
try

AStream := TMemoryStream.Create;
try

AContext.Connection.IOHandler.ReadStream(AStream);
S := StreamToString(AStream);

if S = 'Salam' then
Application.Terminate;

finally
AStream.Free;
end;

except
on E: exception do
ShowMessage(E.Message);
end;
end;

این هم تابع StreamToString (از stackoverflow پیدا کردم):
function TForm1.StreamToString(AStream: TStream): string;
var
SS: TStringStream;
begin
if AStream <> nil then
begin
SS := TStringStream.Create('');
try
AStream.Position := 0;
SS.CopyFrom(AStream, AStream.Size);
Result := SS.DataString;
finally
SS.Free;
end;
end
else
begin
Result := '';
end;
end;

با این حال با اجرای کدهای بالا برنامه Terminate نمیشه .

vcldeveloper
یک شنبه 03 مرداد 1389, 00:24 صبح
این هم تابع StreamToString (از stackoverflow پیدا کردم):
نیازی به همچین کدی نبود، اگر داده های شما Text هستند، می تونستید به جای MemoryStream از TStringStream استفاده کنید.


با این حال با اجرای کدهای بالا برنامه Terminate نمیشه .
من کد را بررسی نکردم که ببینم دقیقا چه اتفاقی میافته، ولی میشه حدس زد که مشکل از فراخوانی Application.Terminate هست. کد OnExecute در Context مربوط به Thread مربوط به اون اتصال اجرا میشه؛ سرور در حال اجرا هست، اتصال کلاینت هنوز باز هست، در داخل یک Thread فرعی کل برنامه بسته میشه! این میتونه عامل مشکل باشه، ولی برای اینکه بشه دقیق جواب داد، یکی باید عملیاتی که در حین Terminate شدن برنامه اتفاق میافته، و بخصوص واکنش کامپوننت سرور به بسته شدن ناگهانی برنامه از طریق یک Thread فرعی را بررسی کنه.

مهران رسا
یک شنبه 03 مرداد 1389, 12:04 عصر
میشه حدس زد که مشکل از فراخوانی Application.Terminate هستشرط رو به این صورت تغییر دادم :

if S = 'Salam' then
self.Caption := S;

بازهم نتیجه ای نداشت و Caption فرم تغییری نکرد . اما با تغییر کد قبلی به صورت زیر (عدم استفاده از ReadStream) عنوان فرم تغییر میکنه و همه چیز Ok هست :


procedure TForm1.TCPServerExecute(AContext: TIdContext);
var
AStream: TMemoryStream;
S: string;
begin
try

//AStream := TMemoryStream.Create;
//try
//AContext.Connection.IOHandler.ReadStream(AStream);
//S := StreamToString(AStream);

S := AContext.Connection.IOHandler.ReadLn();
if S = 'Salam' then
self.Caption := S;

//finally
//AStream.Free;
//end;

except
on E: exception do
ShowMessage(E.Message);
end;
end;

پس مشکل در نحوه استفاده از ReadStream هست .

vcldeveloper
یک شنبه 03 مرداد 1389, 13:36 عصر
پس مشکل در نحوه استفاده از ReadStream هست .
ظاهرا وقتی Client به سرور Connect میشه؛ قبل از اینکه اصلا به ارسال متن با استفاده از WriteLn برسیم، رویداد OnExecute سرور اجرا میشه، و ReadStream فراخوانی میشه. ReadStream هم هیچ وقت برنمیگرده و اون Thread بلاک میشه! آخرین نقطه ایی که در کد اجرا میشه، و در اون نقطه اجرا گیر میکنه، تابع Select از WinSock هست.

علتش را می پرسم، اگر به جوابی رسیدم، اینجا میزارم.

vcldeveloper
سه شنبه 05 مرداد 1389, 21:07 عصر
علتش را می پرسم، اگر به جوابی رسیدم، اینجا میزارم.
http://stackoverflow.com/questions/3328752/why-does-iohandler-readstream-blocks-thread-when-client-connects-to-server-in-ind/3341245#3341245
لینک بالا جواب سوال هست. Remy Lebeau از اعضاء تیم توسعه دهنده Indy هست.

بر طبق جواب فوق، اگر پارامترهای AByteCount و AReadUntilDisconnect در متد ReadStream مقداردهی نشند و از مقدار پیش فرضشان استفاده بشه، ReadStream ابتدا 4 بایت (Integer) یا 8 بایت (Int64) ابتدای داده ورودی را برای دریافت اندازه Stream دریافتی میخوانه؛ پس یا باید اندازه Stream را از قبل مشخص کنیم و با استفاده از AByteCount به ReadStream بدیم؛ یا باید مقدار AReadUntilDisconnect برابر با True باشه، و کلاینت باید بعد از ارسال داده، از سرور Disconnect کنه، تا خواندن داده متوقف بشه، و یا کلاینت باید قبل از ارسال داده اصلی، اندازه آن را به سرور ارسال کنه. اندازه را هم میشه با یک فراخوانی مجزا از متد Write قبل از فراخوانی WriteFile ارسال کرد، یا اینکه میشه کلا یک FileStream را از طریق متد Write ارسال کرد، و پارامتر AWriteByteCount آن را True کرد، تا این متد خودش به طور خودکار، اندازه Stream را قبل از ارسال داده اصلی Stream، به سرور ارسال کنه.

مهران رسا
شنبه 09 مرداد 1389, 10:04 صبح
با استفاده از Visual Basic و کنترل Winsock سعی کردم به سرور Indy پیغام ارسال کنم :

برای اتصال به سرور Indy :
ـ
Winsock1.Connect "127.0.0.1", "3201"

برای ارسال پیغام به سرور Indy :
ـ
Winsock1.SendData "Hi There"

زمانی که اتصال بین Winsock و Indy برقرار میشه یک OnExecute در سرور اتفاق می افته . اما زمانی که برای ارسال پیغام اقدام میکنم ، پیغام ارسال نمیشه و هیچ رویداد OnExecuteی هم در سرور رخ نمیده .
نهایتاً وقتی کلاینت (برنامه Vb) رو از حافظه خارج میکنم یک دفعه سرور بعد از رسیدن به یک استثنا ، به صورت دیوانه وار شروع میکنه به نمایش بی نهایت پیغام Connection Closed Gracefully . (البته پیغام ها(MessageDlg) به صورتی پشت سرهم نمایش داده میشند . در واقع تنها بعد از بسته شدن دیالوگ فعلی ، دیالوگ بعدی نمایش داده میشه) .
و هر بار که استثنای Connection Closed Gracefully اتفاق می افته یک رویداد Onexecute هم رخ میده . ضمناً این در حالیست که اگر بعد از اتصال میان Winsock و Indy ، بدون ارسال پیغام (عدم اجرای خط دوم) برنامه vb رو از حافظه خارج کنم تنها یکبار Connection Closed Gracefully نمایش داده شده و همه چیز به خوبی و خوشی تموم میشه .

و در آخر ، این مشکل زمانی که با استفاده از متد WriteFile بین کلاینت/سرور Indy بخوایم فایل ردوبدل کنیم هم رخ میده . در واقع پس از اینکه متد WriteFile فراخوانی شد ، در صورتی که کلاینت رو از حافظه خارج کنیم(ارتباش رو قطع کنیم) دقیقاً مشکلی که در بالا ذکر شد رخ میده .

اگر اطلاعات کامل تری نیاز هست بگید توضیح بدم .

خیلی ممنون .

این مشکل با حذف بلوک Try-Except حل شد . توضیحات آقای Lebeau در این مورد :



you are not handling the disconnects correctly. What you describe can only happen if your server code is wrapping its reading in a try/except block that is discarding all exceptions that are caught. Do not discard any of Indy's own EIdException-derived exceptions. If you catch them, then re-throw them and let the server process them internally.