آموزش

 
Socket programming (part 2)
نویسنده : Hossein - ساعت ۱٢:٠٢ ‎ق.ظ روز چهارشنبه ۸ آذر ،۱۳۸٥
 
به نام خدا

برنامه نويسي و كنترل پشته TCP IP با زبان C :
نويسنده : يونس فرهادنيا
E.Mail:farhadnia_p@yahoo.com
بخش دوم / آخر


نحوه ساخت يك برنامه Client:

مقدمه:
بعد از مطالعه قسمت اول مقالات (معرفي توابع مهم مورد استفاده در برنامه نويسي TCPIP با زبان C) در اين قسمت ما آمادگي داريم كه رسما شروع به برنامه نويسي شبكه وكنترل پشته TCPIP به زبان سي بنمائيم.
در اين مرحله ما فرا مي گيريم كه چگونه كد يك برنامه مشتري (Client) را تنظيم كنيم و برنامه را به بهربرداري برسانيم براي اين منظور ما ابتدا يك پروژه تعريف كرده و بعد شروع به ساخت برنامه مورد نظر مي كنيم و در هر قسمت به صورت مرحله به مرحله توضيحات را ارائه مي نمائيم.
تعريف پروژه:
در اين قسمت ما قصد داريم برنامه اي را كه مي خواهيم به اتفاق هم بنويسيم شرح دهيم و مشخص كنيم كه برنامه دقيقا قرار است چه كاري انجام دهد و چه امكاناتي براي ما محيا مي كند.
شرح برنامه:
برنامه ما يك برنامه client است يعني با گرفتن مشخصات سرور براي انجام كاري خاص به سرور متصل مي شود.
چون برنامه ما جنبه آموزشي دارد پس بهتر است برنامه را به گونه اي بنويسيم كه قابليت اتصال و ارتباط با هر پورتي را داشته باشد و همچنين آدرس سرور را هم از كاربر دريافت كند پس تا به اينجا مشخص است كه برنامه مدنظر ما دو ورودي دارد و كار اصلي اين برنامه هم اين باشد كه به سرور متصل شود و پيامي را به سرور ارسال كند و همچنين منتظر گرفتن پيامي از سرور نيز باشد .
به نظر مي رسد نوشتن قدم به قدم اين برنامه در يادگيري نحوه نوشتن برنامه هاي مشتري (client ) مسمر ثمر باشد.
در خلال نوشتن برنامه كد هاي خاص و همچنين تكنيك هاي بكار رفته توضيح داده مي شود.
كد برنامه:
خوب براي شروع نوشتن اين برنامه ابتدا بايد هدر winsock2.h را كه توابع مورد نظر ما براي كنترل سوكت هاي شبكه در آن قرار دارند را به برنامه اضافه كنيد همچنين هدر stdio.h كه توابع مهمي براي كار با صفحه كليد و مانيتور در ان قرار دارند و دو هدر مهم ديگر به نام هاي stdlib.h و string.h كه در خلال برنامه از توابعي كه در اين هدر ها نيز هستند استفاده مي كنيم (در جاي خود توابعي را كه از اين هدر ها استخراج كرده اينم را توضيح خواهم داد)خوب با اين تفاصير چند خط اول كد برنامه ما از اين قرار هستند:

كد:
#include
#include
#include
#include


بعد از تعريف هدر هاي مورد استفاده در برنامه نوبت به تعريف تابع اصلي لرنامه يعني تابع main() مي رسد به صورت زير عمل كنيد:

كد:
void main(int argc,char **argv){

}

خوب تابع اصلي برنامه ما كه main() نام دارد مقدار بازگشتي ندارد اما آرگومان هاي اين تابع كه از خط فرمان به تابع ارسال مي شوند (argv و argc) براي گرفتن ورودي هاي برنامه ما يعني آي پي آدرس ماشين سرور و شماره پورت ارتباطي كاربرد دارند حتما مي دانيد كه در آرگومان argc تعداد رشته هايي كه از ورودي دريافت شده اند قرار دارند و در argv كه يك ارايه دو بعدي از نوع كاراكتري است رشته هاي ورودي نگهداري مي شوند يعني اگر ما در خط فرمان تايپ كنيم:
C:\>client.exe 66.33.96.2 45
Argc برابر 3 مي شود ( يكي براي نام خود برنامه و دو تاي ديگر هم براي آرگومان هاي ورودي) و argv هم بدين صورت مي شود:

نقل قول:
6 6 . 3 3 . 9 6 . 2 Null
4 5 NULL


خوب تا به اين جا مقدمات آماده شده اما براي كامپايل برنامه با كامپاير vc++ يك چيز ديگر هم نياز است!
كتابخانه WS_32.lib كه براي اضافه كردن آن به پروژه خو بايد بدين صورت عمل كنيد:
ابتدا منوي Project زير منوي add to Project را انتخاب كرده و گزينه Files را كليك كنيد بعد نوع فايل انتخابي را lib قرار دهيد و بعد دكمه open را كليك كرده و به ادرسي كه ويژوال استديو را نصب كرده ايد برويد و در انجا پوشه vc6 را باز كرده و بعد پوشه Lib را باز كنيد و در فايل هاي ظاهر شده فايل WS_32.lib را برگزينيد و ok كنيد كتابخانه مورد نظر به برنامه شما اضافه شده است حالا بايد به سراغ مرحله بعد يعني تعريف متغير ها رفت.
در اين مرحله ما شروع به تعريف متغير هاي مورد استفاده در برنامه خود مي كنيم ابتدا بايد دانست كه براي بارگزاري و آماده سازي مقدماتي ما بايد از تابع WSAStartup(); استفاده كنيم كه اين تابع دو آرگومان دارد اولي يك عدد كه معرف نگارش هدر مورد استفاده (winsock2.h) است و ديگري يك ساختمان داده از نوع WSADATA است كه در ان اطلاعات مهمي ذخيره مي شود براي آشنايي با ساختار اين نوع داده اي عينا اعضاي موجود در اين ساختار داده اي را براي شما نمايش مي دهم:

كد:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, *LPWSADATA;


در مورد جزئيات بيشتر مي توانيد به مراجعي كه در انتهاي مقاله اورده مي شود رجوع كنيد.
خوب پس تا به اينجا ما بايد دو متغير تعريف كنيم :

كد:
WORD wVersionRequested;
WSADATA wsaData;


اين دو متغير هر دو در تابع WSAStartup به كار مي روند اولي كه لز نوع WORD است براي نگهداري شماره نگارش هدر و دومي براي نگهداري اطلاعات مهمي كه مورد استفاده براي اماده سازي سيستم عاما است.
ما براي ساخت يك سوكت نياز به يك متغير از نوع SOCKET داريم كه اين نوع كه همسان با نوع داده اي int است براي نگهداري توصيفات مربوط به پورت ما مي باشد كه در توابع گوناگوني مورد استفاده قرار مي گيرد شايان ذكر است كه نوع SOCKET يك نوع داده اي استانداد نيست و در هدر winsock.h تعريف شده است:
SOCKET intSocket;
براي نگهداري شماره پورت ارتباطي مورد نظر ما همچنين آي پي آدرس ماشين سرور و نوع پروتكل ارتباطي نياز به يك ساختار داده اي به نام sockaddr_in است در نتيجه يك متغير از اين نوع بايد در برنامه تعريف شود:

كد:
struct sockaddr_in recSin;


ما به يك بافر براي نگهداري اطلاعات دريافتي و همچنين ذخيره موقت اطلاعات ارسالي قبل از انجام عمليات ارسال نيازمنديم در نتيجه يك بافر بنا به نياز خود تعريف مي كنيم توجه داشته باشيد چون اطلاعات ارسالي و دريافتي به صورت رشته اي از كاراكتر ها مي باشد ما نيز بايد اين بافر را از نوع كاراكتري در نظر بگيريم يعني آرايه اي از كاراكتر ها كه طول آن هم بنا به نياز ما مي تواند متغير باشد.

نقل قول:
char * pchrBuffer;


براي تست صحت درستي كار توابع يك متغير ديگر براي نگهداري مقدار بازگشتي توابع نيز تعريف مي كنيم.

كد:
int intErr;


خوب تا به اينجا متغير هاي مورد نياز برنامه ما تعريف شدند در مرحله بعد ما بايد براي برنامه خود آي پي آدرس ماشين ميزبان و همچنين پورت مورد نظر ما براي تبادل اطلاعات و نوع ارتباط را مشخص كنيم كه اين سلسله از كار ها به سادگي با مقدار دهي به اعضاي ساختمان داده اي wsaData صورت مي پذيرد اما قبل از هر كاري بايد كنترل شود كه آيا كاربري كه برنامه را اجرا كرده آرگومان هاي تابع اصلي برنامه ( main) را نيز وارد كرده يا خير و در صورت بروز هر اشكالي به كاربر اطلاع داده شود.
پس بدين گونه عمل مي كنيم:

كد:
if(argc<=2){
printf("::Error on The call Program::\n");
printf("%s RemoteIPAddress RemotePort",argv[0]);
exit(1);
}


تابع exit() هم كه در هدر stdlib.h قرار دارد وظيفه اش اتمام برنامه است كه آرگومان اين تابع عينا به سيستم عامل داده مي شود (مي توان از ان براي تشخيص خطاي رخ داده استفاده كرد).
بعد از اجراي كد بالا مي توانيم مطمئن شويم كه كاربر اطلاعات ورودي مورد نظر ما را وارد كرده پس كد زير را در برنامه درج مي كنيم:

كد:
recSin.sin_addr.S_un.S_addr=inet_addr(argv[1]);
recSin.sin_family=AF_INET;
recSin.sin_port=htons(atoi(argv[2]));


شرح اعضاي ساختار WSADATA در قسمت قبلي مقاله آورده شده است براي توضيحات بيشتر مي توانيد به آن مراجعه كنيد فقط تابع atoi() است كه در مورد آن بايد گفت كه اين تابع كه در هدر stdlib.h قرار دارد مقدار رشته اي را به مقدار عددي تبديل مي كنيد يعني ارزش عددي آرگومان خود را كه از نوع يك رشته كاراكتري است برمي گرداند.
حالا بايد مرحله آماده سازي (initiates) سيستم عامل را صورت دهيم كه اين تابع ترتيبي مي دهد كه ما بتوانيم از Ws2_32.dll استفاده كنيم پس كد ما در مرحله بعد از تعريف متغير ها به صورت زير است:

كد:
wVersionRequested=MAKEWORD(2,0);
if(WSAStartup(wVersionRequested,&wsaData)){
printf("\n::Error On init Socket::\n");
exit(1);
}


خوب در اين كد از ماكرو makeword() استفاده شده است كه وظيفه آن اين است كه اعداد موجود در آرگومانش را كه بيانگر نگارش هدر winsock.h ما هست را به نوعي قابل فهم (WORD ) توسط تابع WSAStartup() تبديل كند و ما چون از نگارش 2 هدر استفاده كرديم آرگومان ها به ترتيب 2 و0 خواهند بود (MAKEWORD(2,0) ) .

چون تابع WSAStarup وظيفه اش قرار دادن مقادير در ساختمان wsaData هست ما اين آرگومان را با نوع فراخاني با ارجاع ( call by reference ) براي تابع مشخص كرده ايم. يعني به جاي جايگزاري مقدار در آرگومان تابع (call by value ) ادرس محلي كه متغير ساختماني ما (wsaData) در ان قرار دارد را براي تابع ارسال كرده ايم.
در صورت بروز خطا تابه WSAStartup مقداري غير صفر بر مي گرداند كه ما مقدار برگشتي تابع را با دستور if كنترل كرده ايم و در صورت بروز اشكال به برنامه با چاپ يك پيغام خاتمه مي دهيم.
در اينجا به مرحله اي رسيده ايم كه مي توانيم سوكت خود را با استفاده از تابع socket() تعريف كنيم:

كد:
intSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(intSocket = = INVALID_SOCKET){
printf("\n::Error On Create Socket::\n");
WSACleanup();
exit(1);
}


خوب تابع سوكت اگر كار خود را با موفقيت انجام دهد مقدار برگشتيش توصيف كننده سوكت مورد نظر ماست و در غير اين صورت ثابتي برابر INVALID_SOCKET را برمي گرداند كه ما براي تست صحت درستي انجام تابع اين مقدار را با مقدار بازگشتي تابع مقايسه مي كنيم. در آرگومان دوم نيز مشخص شده است كه ارتباط ما از نوع TCP است و اگر مي خواستيم كه از نوع UDP باشد كافي بود كه مقدار ثابت SOCK_DGRAM قرار بدهيم.

تابع WSACleanup() نيز براي اتمام بارگزاري سيستم عامل و خالي كردن حافظه مي باشد ( عكس تابع WSAStartup() عمل مي كند).

حالا همه چيز اماده است كه ما با استفاده از تابع connect() به سرور متصل شويم:

كد:
intErr=connect(intSocket,&recSin,sizeof(recSin));
if(intErr==INVALID_SOCKET){
printf("\n::Error On Connect to Socket::\n");
WSACleanup();
exit(1);
}


در مورد تابع connect() و آرگومان هايش به تفضيل در قسمت اول سري مقالات توضيح داده شده است.
تا بدين جا همه چيز مهياست كه ما اطلاعات مورد نظر خورد را براي سرور ارسال كنيم.
از تابع send() براي انجام عمليات ارسال اطلاعات به سرور استفاده مي كنيم ولي قبل از ان بافر را با اطلاعات مورد نظر خود پر مي كنيم:

كد:
pchrBuffer="Salam\0";
intErr=send(intSocket,pchrBuffer,strlen(pchrBuffer),0);
if(intErr==SOCKET_ERROR){
printf("\n::Error On Send Data::\n");
printf("Error Code:%d",WSAGetLastError());
WSACleanup();
exit(1);
}


در اين حالت مقدار رشته Salam به طرف سرور ارسال مي سود توجه داشته باشيد كه علامت \0 براي مشخص كردن انتهاي رشته است آرگومان سوم هم كه تعداد كاركاتر هاي ارسالي را مشخص مي كند كه در اينجا ما با استفاده از تابه strlen() كه در هدر string.h تعريف شده است طول رشته ارسالي خود را براي تابع send مشخص مي كنيم.
بعد از مرحله ارسال اطلاعات نوبت به دريافت اطلاعات از سرور است كه در اينجا به شرح آن مي پردازيم:
با استفاده از تابع recv() شما قادر خواهيد بود كه اطلاعاتي از ديگر كامپيوتر موجود در شبكه دريافت كنيد در اين مثال كد مربوط به دريافت بدين صورت است:

كد:
memset(pchrBuffer,’\0’,strlen(pchrBuffer));
intErr=recv(intSocket,pchrBuffer,strlen(pchrBuffer),0);
if(intErr==SOCKET_ERROR){
printf("\n::Error On Recev Data::\n");
printf("Error Code:%d",WSAGetLastError());
exit(1);
}
printf("%s",pchrBuffer);



با استفاده از تابع memset() كه در هدر string.h مشخص شده است ما بافر خود را از وجود اطلاعات قبلي پاك مي كنيم تا اطلاعات دريافت شده را در آن ذخيره كنيم.
تابه memset() بدين صورت عمل مي كند كه ارگومان اول اين تابع آدرس بافر ما خواهد بود آرگومان دوم مقداري ايت كه در هر يك از خانه هاي بافر ما قرار است جايگزين مقدار قبلي آن خانه از بافر ما شود و آرگومان سوم تعداد خانه هايي از بافر را مشخص مي كند كه ما مي خواهيم مقدار جديد را در آن جايگزين كنيم. مقدار برگشتي اين تابع بافر تغيير يافته خواهد بود.
در تابع recv كه آرگومان هايش دقيقا شبيه آرگومان هاي تابع send است مقدار رشته كاراكتري كه از كامپيوتر ديگر دريافت مي شود در بافر ما ذخيره مي شوند و ما مي توانيم در مرحله بعد از آن استفاده كنيم.
كه ما در اينجا عينا خود عبارت دريافتي را با دستور printf() در خرئجي نمايش داده ايم.
خوب كار برنامه ما در اينجا به پايان رسيده است و ما بايد در اين مرحله socket خود را ببنديم و حافظه را آزاد كنيم كه اين كار با دو تابع زير صورت مي پزيرد:

كد:
closesocket(intSocket);
WSACleanup();
getch();
}


در نهايت كل كد برنامه ما به صورت زير خواهد بود:

كد:
#include
#include
#include
#include
//define main function:
void main(int argc,char **argv){
WSADATA wsaData;
WORD wVersionRequested;
SOCKET intSocket;
struct sockaddr_in recSin;
int intErr;
char *pchrBuffer;
if(argc<=2){
printf("::Error on The call Program::\n");
printf("%s RemoteIPAddress RemotePort",argv[0]);
exit(1);
}
recSin.sin_addr.S_un.S_addr=inet_addr(argv[1]);
recSin.sin_family=AF_INET;
recSin.sin_port=htons(atoi(argv[2]));
wVersionRequested=MAKEWORD(2,0);
if(WSAStartup(wVersionRequested,&wsaData)){
printf("\n::Error On init Socket::\n");
exit(1);
}
intSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(intSocket==INVALID_SOCKET){
printf("\n::Error On Create Socket::\n");
WSACleanup();
exit(1);
}
intErr=connect(intSocket,&recSin,sizeof(recSin));
if(intErr==INVALID_SOCKET){
printf("\n::Error On Connect to Socket::\n");
WSACleanup();
exit(1);
}
pchrBuffer="Salam\0";
intErr=send(intSocket,pchrBuffer,strlen(pchrBuffer),0);
if(intErr==SOCKET_ERROR){
printf("\n::Error On Send Data::\n");
printf("Error Code:%d",WSAGetLastError());
WSACleanup();
exit(1);
}
memset(pchrBuffer,’\0’,strlen(pchrBuffer));
intErr=recv(intSocket,pchrBuffer,strlen(pchrBuffer),0);
if(intErr==SOCKET_ERROR){
printf("\n::Error On Recev Data::\n");
printf("Error Code:%d",WSAGetLastError());
exit(1);
}
printf(“\nRecived Data:%s\n”,pchrBuffer);
closesocket(intSocket);
WSACleanup();
getch();
}


خوب تا بدين جا شما نحوه ساخت يك برنامه كلاينت با زبان سي را فراگرفته ايد اين برنامه تحت TCP كار مي كند يعني يك ارتباط اتصال گرا ( Connection Orinted) برقرار مي كند براي ساخت يك برنامه تحت UDP يعني غير اتصال گرا (Connection Less ) مسقيما شروع به ارسال و دريافت داده كنيد البته به شرطي كه سرور شما از پورت هاي UDP استفاده كند به عنوان تمرين تجزيه و تحليل برنامه زير كه يك برنامه بر مبناي ارتباط UDP را به خود شما واگزار مي كنم:

كد:
#include
#include
#include
#include
//define main function:
void main(int argc,char **argv){
WSADATA wsaData;
WORD wVersionRequested;
SOCKET intSocket;
struct sockaddr_in recSin;
int intErr;
char *pchrBuffer;
if(argc<=2){
printf("::Error on The call Program::\n");
printf("%s RemoteIPAddress RemotePort",argv[0]);
exit(1);
}
recSin.sin_addr.S_un.S_addr=inet_addr(argv[1]);
recSin.sin_family=AF_INET;
recSin.sin_port=htons(atoi(argv[2]));
wVersionRequested=MAKEWORD(2,0);
if(WSAStartup(wVersionRequested,&wsaData)){
printf("\n::Error On init Socket::\n");
exit(1);
}
intSocket=socket(AF_INET, SOCK_DGRAM,IPPROTO_UDP);
if(intSocket==INVALID_SOCKET){
printf("\n::Error On Create Socket::\n");
WSACleanup();
exit(1);
}
intErr=connect(intSocket,&recSin,sizeof(recSin));
if(intErr==INVALID_SOCKET){
printf("\n::Error On Connect to Socket::\n");
WSACleanup();
exit(1);
}
pchrBuffer="Salam\0";
intErr=send(intSocket,pchrBuffer,strlen(pchrBuffer),0);
if(intErr==SOCKET_ERROR){
printf("\n::Error On Send Data::\n");
printf("Error Code:%d",WSAGetLastError());
WSACleanup();
exit(1);
}
memset(pchrBuffer,’\0’,strlen(pchrBuffer));
intErr=recv(intSocket,pchrBuffer,strlen(pchrBuffer),0);
if(intErr==SOCKET_ERROR){
printf("\n::Error On Recev Data::\n");
printf("Error Code:%d",WSAGetLastError());
exit(1);
}
printf(“\nRecived Data:%s\n”,pchrBuffer);
closesocket(intSocket);
WSACleanup();
getch();
}


توجه داشته باشيد كه در تابه socket از دو ثابت SOCK_DGRAM و IPPROTO_UDP به جاي آرگومان دو م و سوم استفاده كرديم كه مشخص كننده نوع ارتباط UDP است.
در اينجا ديگر مي توانيد با كمي خلاقيت برنامه هاي كلاينت مد نظر خود را براي كاربرد هاي گوناگون بسازيد.
 
comment نظرات ()
 
 
Socket programming (part 1)
نویسنده : Hossein - ساعت ۱٢:٠٠ ‎ق.ظ روز چهارشنبه ۸ آذر ،۱۳۸٥
 
به نام خدا

برنامه نويسي و كنترل پشته TCP IP با زبان C :
نويسنده : يونس فرهادنيا
E.Mail:farhadnia_p@yahoo.com


در ابتداي بحث لازمه كه خوانندگان محترم توجه داشته باشند كه براي درك بهتر مفاهيم اين سلسله از مقالات لازمه كه آشنايي در حد مطلوب با مفاهيم برنامه نويسي و شبكه و خصوصا TCPIP Protocol داشته باشند در غير اين صورت مطالعه اين سلسله از مقالات براي عزيزان بي ثمر خواهد بود.
اين سلسله از مقالات شامل :
معرفي توابع مورد استفاده در برنامه نويسي TCPIP.
نحوه ساخت يك برنامه Client.
نحوه ساخت يك برنامه سرويس دهنده (Server ).
نحوه ساخت برنامه هاي كاربردي شبكه .
نحوه ساخت برنامه هاي نفوذ به سيستم ( ها Trojan )
و توضيحات متفرقه و سوال و جواب ها مي باشد.

قسمت اول اين سري از مقالات به صورت مجاني در سايت simorgh-ev.com انتشار مي يابد و قسمت هاي ديگر كه به صورت نيمه مجاني مي باشد به وسيله پست الكترونيكي به فروش مي رسد.
براي اطلاعات بيشتر و نحوه خريد مقالات از طريق پست الكترونيكي با من تماس بگيريد.
پرسش و پاسخ هاي اين بحث نيز در انجمن امنيتي سيمرغ صورت مي پذيرد.
يونس فرهادنيا.



معرفي توابع مهم مورد استفاده در برنامه نويسي TCPIP با زبان C :

مقدمه:
براي برنامه نويسي شبكه به زبان سي ما نيازمند يك سري ملزومات هستيم از جمله اين ملزومات مي توان به يك كامپايلر مناسب ، هدر مربوط به برنامه نويسي شبكه (or Other Header Winsock.h) و همچنين سيستم عاملي كه از سوكت هاي شبكه پشتيباني كند را مي توان نام برد.
همان طور كه مي دانيد زبان سي يك زبان قابل حمل مي باشد يعني مي توان برنامه هاي نوشته شده به اين زبان را روي سيستم هاي مختلف كامپايل و اجرا كرد منظور از سيستم هاي مختلف كامپيوتر هايي با معماري هاي گوناگون مي باشد البته اين امر به شرطي ميسر است كه ما به شيوه اي هوشمندانه كد برنامه خود را تنظيم كنيم كه وابسته گي به سيستم خاصي نداشته باشد به عنوان مثال هنگام تخصيص حافظه پويا نبايد طبق مشخصات سيستم خود اقدام به گرفتن يا آزاد كردن حافظه نمائيم و اقدامات ديگري نظير اين موضوع كه باعث مي شود برنامه اي كه ما نوشته ايم بخوبي كه روي سيستم خود اجرا مي شود بر روي ديگر PlateForm نيز اجرا شود.
در نتيجه در برنامه نويسي شبكه نيز اين امر بايد به خوبي مد نظر واقع شود به عنوان مثال هدر كار با سوكت ها در سي مي تواند از سيستم عاملي به سيستم عامل ديگر متفاوت باشد همينطور از سخت افزاري به سخت افزار ديگر به عنوان مثال براي برنامه نويسي شبكه در سيستم عامل ويندوز ما از هدر Winsock.h استفاده مي كنيم كه خود نگارش هاي مختلفي دارد و براي برنامه نويسي شبكه در سيستم عامل هاي خانواده *NIX از هدر sys/socket.h و هدر هاي ديگر استفاده مي كنيم كه البته مي توان با اقدامي هوشمندانه توسط راهنماهاي كامپاير (Compiler Directive ) نوع سيستم عامل را تشخيص داد و بعد هدر هاي مربوط به هر سيستم عامل را مورد استفاده قرار داد. بدين ترتيب برنامه ما در هر سيستم عاملي و با هر كامپايلر ( البته نه همه نوع كامپايلري) قابل ترجمه به زبان ماشين و اجرا مي باشد.
در اين سلسله از مقالات براي سهولت آموزش از سيستم عامل ويندوز و هدر winsock2.h به همراه كامايلر VC++ استفاده مي شود. و هر كجا كه نياز بود مثال هايي از سيستم هاي ديگر آورده خواهد شد.
البته بايد دانست كه توابع مورد استفاده در همه انواع هدر ها يكسان مي باشند مگر در معدود مواردي كه هر كجا كه احساس نياز شود ذكر خواهند شد.

شرح توابع مهم موجود در هدر winsock2.h:

كد:
SOCKET socket(
int af,
int type,
int protocol
);


اين تابع براي تعريف و ايجاد يك پورت به كار مي رود و مقدار برگشتي آن توصيف كننده پورت مورد نظر است.
آرگومانهاي تابع به ترتيب از اين قرار هستند:
Af كه مشخص كننده نوع آدرس است كه براي كاربري اينترنت برابر ثابت AF_INET است براي كنترل سوكت ها در يونيكس برابر ثابت AF_UNIX است و مقادير ديگر ...
Type مشخص كننده نوع ارتباط مي باشد كه براي يك ارتباط TCP برابر ثابت SOCK_STREAM و براي يك ارتباط از نوع UDP برابر SOCK_DGRAM است.
Protocol هم نمايانگر نوع پروتكل انتخابي ماست براي ارتباط براي TCP ثابت IPPROTO_TCP يا مقدار عددي 0 و براي UDP مقدار عددي 1 يا ثابت IPPROTO_UDP
اين تابع در صورت موفقيت مقدار برگشتيش توصيفات مربوط به سوكت مورد نظر ماست و در صورت بروز خطا ثابتي به نام INVALID_SOCKET را بر مي گرداند.

كد:
int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);


تابع Connect وظيفه برقراري ارتباط را با برنامه سرور بر عهده دارد و ارگومان هاي آن به شرح زير است:

s كه از نوع سوكت مي باشد (يك نوع داده اي عدد صحيح integer= ) و همان مقدار برگشتي تابع Socket است
آرگومان بعدي يك متغير از نوع ساختمان داده اي SockAddr_in مي باشد.
ساختمان داده اي sockaddr_in يك استراكچر تعريف شده در هدر winsock.h است كه محل نگه داري شماره پورت ارتباط و همچنين آدرس IP و ديگر اطلاعات است كه به صورت زير تعريف شده است:

كد:
struct sockaddr_in{
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};


عضوي به نام sin_family براي نگه داري نوع آدرس ارتباطي است كه براي كاربردهاي اينترنتي برابر ثابت AF_INET است. sin_port مشخص كننده شماره پورت ارتباطي است كه البته براي تبديل عدد به نوع قابل فهم توسط ساختمان از يك تابع تبديل كننده به نام htons(int PortNumber); استفاده مي شود و sin_addr كه خود يك ساختمان مي باشد براي نگه داري آدرس IP كه به صورت يك رشته كاراكتر مشخص مي شود مي باشد كه البته براي تبديل رشته كاراكتري كه معرف ادرس آي پي است براي نوع قابل فهم توسط استراكچر از تابعي به نام inet_addr(char *ipaddress); استفاده مي شود و در نهايت sin_zero براي حفظ تطابق اين استراكچر با گونه هاي قديمي به كار مي رود.
مثال:

كد:
sockaddr_in recSinIP;
recSinIP.sin_family=AF_INET; //Set Address Family
recSinIP.sin_port=htons(1362); //Set Port Number
recSinIP.sin_addr.S_un.s_addr=inet_addr(“192.168.0.1”);



و آرگومان آخر تابع connect يعني namelenاندازه استراكچر sockadd_in است كه مي توان به جاي مقدار ان نوشت :sizeof(sockaddr_in) .
اين تابع در صورت موفقيت مقدار صفر و در صورت بروز خطا ثابتي برابر با SOCKET_ERROR را برمي گرداند.

كد:
int bind(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);


اين تابع وظيفه پيوند دادن اطلاعات ارتباطي (مانند آدرس ) را به سوكت تعريف شده دارد و در برنامه سرور به كار مي رود.
آرگومان هاي آن نيز همانند تابع connect مي باشد.
اين تابع در صورت موفقيت مقدار صفر و در صورت بروز خطا ثابتي برابر با SOCKET_ERROR را برمي گرداند.


كد:
int listen(
SOCKET s,
int backlog
);


تابع listen وظيفه گوش دادن به خط را در برنامه سرور به عهده دارد .
آرگومان هاي اين تابع از قرار زير هستند:
S كه متغير سوكت تعريف شده در برنامه مي باشد.
Backlog هم اندازه بيشترين طول صف هاي ارتباطي است كه مي تواند برابر ثابتSOMAXCONN باشد .
اين تابع در صورت موفقيت مقدار صفر و در صورت بروز خطا ثابتي برابر با SOCKET_ERROR را برمي گرداند.

كد:
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
);


اين تابع براي قبول درخواست اتصال بعد از تابع listen به كار مي رود .
آرگومان هاي اين تابع مانند تابع Bind مي باشد با اين تفاوت كه در آرگومان addr مشخصات ماشين متصل شده به سرور قرار مي گيرد و آرگومان addrlen اندازه ساختماني كه اين اطلاعات را نگهداري مي كنند را مشخص مي كند.
اين تابع در صورت موفقيت مقدار برگشتيش توصيفات مربوط به سوكت ماشين راه دور مورد نظر ماست و در صورت بروز خطا ثابتي به نام INVALID_SOCKET را بر مي گرداند.


كد:
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);


اين تابع براي اماده سازي سيستم عامل براي اجراي برنامه مي باشد.
آرگومان هاي آن عبارت است از:
WVersionRequested كه شماره نگارش هدر winsock.h است كه براي تبديل اين شماره به نوع WORD مي توان از تابع makeword استفاده كرد به عنوان مثال براي winsock2.h :
wVersionRequested=makeword(2,0);
و براي نگارش winsock1.1.h :
wVersionRequested=makeword(2,1);

و آرگومان دوم يك متغير از نوع ساختمان داده اي WSADATA مي باشد كه در هدر winsock تعريف
شده است.
مثال:

كد:
WSADATA wsaData;
WORD wVersionRequested=makeword(2,0);
WSAStartup(wVersionRequested,wsaData);


اين تابع در صورت موفقيت مقدار صفر را بر مي گرداند و در صورت شكست كد خطاي رخ داده را بر مي گرداند.

int WSACleanup (void);
اين تابع براي اتمام بارگزاري مربوط به شبكه سيستم عامل مي باشد.
اين تابع در صورت موفقيت مقدار صفر و در صورت بروز خطا ثابتي برابر با SOCKET_ERROR را برمي گرداند.


كد:
int closesocket(
SOCKET s
);


براي بستن سوكت تعريف شده در برنامه بكار مي رود و ارگومان اين تابع متغير سوكت تعريف شده در برنامه مي باشد.
اين تابع در صورت موفقيت مقدار صفر و در صورت بروز خطا ثابتي برابر با SOCKET_ERROR را برمي گرداند.

كد:
int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
);


اين تابع براي ارسال اطلاعات به كار مي رود.
و ارگومان هاي آن از قرار زير مي باشند:
S متغير سوكت تعريف شده در برنامه buf آدرس محلي از حافظه كه اطلاعات كاراكتري براي ارسالي در ان قرار دارند len اندازه اطلاعاتي كه مي خواهيم ارسال كنيم بر حسب بايت flags هم شامل ثوابتي براي تنظيمات ارسال مي باشد. كه در حالت معمول برابر صفر است.
اين تابع تعداد كاراكتر هايي را كه با موفقيت ارسال شده را برمي گرداند.

نقل قول:
int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
);


اين تابع براي دريافت اطلاعات به كار مي رود و آرگومان هاي ان هم مانند تابع send مي باشد جزء آرگومان buf كه در اين تابع به جايي از حافظه كه اطلاعات دريافت شده نگهداري مي شود اشاره مي كند.
اين تابع به عنوان مقدار برگشتي تعدا كاركترهايي را كه با موفقيت دريافت كرده برنمي گرداند.

كد:
int sendto(
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
);


از اين تابع براي ارسال اطلاعات به ادرسي خاص كه در آرگومان to مشخص شده استفاده مي شود.
آرگومان tolen در اين تابع مشخص كننده طول آدرس مقصد است.

كد:
int recvfrom(
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR *from,
int FAR *fromlen
);


از اين تابع براي دريافت اطلاعات از ادرسي خاص كه در آرگومان from مشخص شده استفاده مي شود.
آرگومان tolen در اين تابع مشخص كننده طول آدرس مبداء است.

int WSAGetLastError (void);
اين تابع آخرين خطاي اتفاق افتاده در برنامه را بر مي گرداند.
مقدار برگشتي اين تابع شماره خطاي رخ داده است.

اين شرحي مختصر بر توايع مورد استفاده در برنامه نويسي شبكه با زبان قدرتمند C بود.
 
comment نظرات ()
 
 
برنامه نویسی تحت شبکه socket programming
نویسنده : Hossein - ساعت ۱٢:۳۱ ‎ق.ظ روز سه‌شنبه ٧ آذر ،۱۳۸٥
 
بسم الله الرحمن الرحیم


عنوان مقاله: with C++ Socket Programing

نویسنده : مهندس محمد صا بر ایرجی
ایمیل: Shantia20042004@yahoo.com
کلمات کلیدی:
IP: در شبکه هر کامپیوتر دارای آدرس میباشد , این عدد آدرس 32 بیتی , به عبارتی 4 قسمتی را IP گویند.
TCP: یکی از پروتکل های لایه انتقال در شبکه اتصالگرا میباشد
. (Transmission Control Protocol)
UDP: یکی از پروتکل های لایه انتقال در شبکه غیراتصالگرا میباشد .
CLIENT:کامپیوتری که در شبکه سرویس گیرنده است.
SERVER:کامپیوتری که به در خواستهای سرویس گیرنده ها پاسخ میدهد.
خلاصه:
امروزه با گسترش ارتبا طات و انواع شبکه های کامپیوتری جهت انجام امور نیاز مبرم به بر نامه های تحت شبکه از جمله socket programing احساس میشود , لذا در این مقاله به ا رائه مطالبی در این زمینه و ایجاد یک برنامه تحت شبکه(برنامه chat) میپردازم. در حقیقت این مقاله نقطه آغازی برای همه بر نامه نویسانی خواهد بود که به نحوی مجبور خو اهند شد برنامه کار بردی تحت شبکه اینترنت بنویسند.
برای یک ارتباط نیاز به موارد زیر است :
• ارتباط ادامه دار
• زبان مشترک
• معنای آدرس دهی
ارتباط عمومی:
پروتکلها : زبانهای مشترک اینترنت هستند.


انواع آدرس در اینترنت :
1- آدرس IP از جنس آدرس فیزیکی 161.12.188.167
2- نام Domain از جنس آدرس منطقی ex.iraji .com
آدرس IP آدرس 32 بیتی است که به 4 قسمت تقسیم می گردد محدوده اعداد در هر قسمت از 255- 0
می باشد.
5 طبقه بندی برای آدرس IP وجود دارد :



توابع API در C :
GETHOST BY NAME – NAME IP ADDRES
GET HOST BY ADDRES – IP ADDRES AQME

برنامه های کاربردی مشترک:
در هر مورد پروتکل نمونه تعریف شده است .



شماره پورت :
16 بیت صحیح است . برای ارتباط نیاز به آدرس IP و شماره پورت می باشد.

در TCP ارتباط تعریف می شود با


Unix در


در unix 1024 پورت اول برای هر دو نوع پورتکلها است و weu known ports نامیده می شود.



مدل tcp مبتنی بر اتصال است
قابل اطمینان smtr،http ،telnet ،ftp
رشته ای است (مسیل و نهر) (transmission control protocol / internet protocol)


مدلp ud غیر قابل اتصال
غیر قابل اطمینان tftp، nfs
دیتا گرامی است . ( بر اساس packet) (user datagram protocol) چکسام










مفهوم socket :
فرض کنید در محیطی کار میکنید که فقط با فایل ها سر و کار داریم ,در چنین سیستمی ورودی خروجی هم (I/O) میتواند تو سط سیستم فایلها مدیریت شود.به عنوان مثال, صفحه نمایش را همانند یک فایل متنی در نظر بگیرید برای ا ینکه اطلاعا ت را در صفحه نمایش بفرستیم باید فایل را از نوع نوشتنی باز کنیم و اگر محتوای اشاره گر فایل خالی نبود یعنی سیستم عامل به فایل حافظه اختصاص داد میتوان در فایل اطلاعات را نوشت.
میتوانیم این ساختار را برای ارتباط دو بر نامه کاربردی روی دو کامپیوتر گسترش دهیم , یعنی باید یک ارتباط با یک کامپیوترروی شبکه) با یک آدرس Ip خاص (و برنامه ای خاص روی کامپیوتر با آدرس پورت مشخص بگشاییم.
سوکت ، ترکيبی از يک آدرس IP و پورت TCP ويا پورت UDP است . يک برنامه ، سوکتی را با مشخص نمودن آدرس IP مربوط به کامپيوتر و نوع سرويس ( TCP برای تضمين توزيع اطلاعات و يا UDP) و پورتی که نشاندهنده برنامه است، مشخص می نمايد. آدرس IP موجود در سوکت ، امکان آدرس دهی کامپيوتر مقصد را فراهم و پورت مربوطه ، برنامه ای را که داده ها برای آن ارسال می گردد را مشخص می نمايد.درزیر نام چند پروتکل برنامه کاربردی و آدرس پورت آنها مشخص شده است:




بسیاری از برنامه های کاربردی تحت شبکه از یک شماره پورت خاصی برای ارسال و در یافت اطلاعات استفاده میکنند.به عنوان مثال برنامه فرستادن ایمیل به پورت 25 گوش میکند , ما در برنامه های کاربردی خود می توانیم از پورت دلخواه استفاده کنیم , به عنوان مثال شماره سوکت در یک برنامه تحت شبکه میتواند :2000 127.67.78.97 باشد که قالب کلی این آدرس به صورت Ip:Port میباشد .
مفهوم Sap :
در سمت client & server بسته های شکسته شده برنامه های کاربردی مربوطه با عبور از پورت مربوطه از لایه های دیگر شبکه عبور میکنند.این شماره پورت واسط بین لایه برنامه کاربردی و انتقال در حقیقت همان Sap میباشد .به شکل زیر نگاه کنید.



بنابراین ارتباط بین دو برنامه تحت شبکه مبادله اتلاعات بین دو نقطه TSap روی دو کامپیوتر در شبکه میباشد.همانطور که میتوان چندین فابل را همزمان باز نگه داشت در یک برنامه تحت شبکه همزمان میتوان چندین ارتباط باز و فعال داشته باشیم .مثلا از برنامه Yahoo messenger استفاده میکنیم در حقیقت ما از برنامه سمت client آن استفاده میکنیم و در server yahoo برنامه ای به پورتی که برنامه client اطلاعات را به آن میفرستد گوش میدهد.
انواع سوکت :
سوکت ها به طور کلی به 2 نوع میباشند:
1- سوکت های نوع stream که از نوع اتصالگرا هستند , یعنی باید قبل از مبادله اطلا عات یک اتصال به صورت لوله فرضی بین client و server بوجود میاید, این نوع سوکت ها بر مبنای tcp هستند.
2- سوکت های datagramکه از نوع بدون اتصال هستند , یعنی نیاز به برقراری ارتباط فرضی ندارند , پس هیچ امنیتی در این نوع ارتباط وجود ندارد, این نوع سوکت ها بر مبنایudp هستند.
همواره باید به این نکته توجه کرد که در برنامه های تحت شبکه ارتباطات دو طرفه میباشد, یک برنامه ای که در سمت سرویس گیرنده اجرا میشود و دیگری در سمت سرویس دهنده.برنامه نویسی سوکت مربوط به لایه کاربردی میباشد و انتقال بسته های اطلاعاتی به لایه های دگر بر عهده سیستم عامل میباشد.
قبل از وارد شدن در مقوله برنامه نویسی سوکت بد نیست الگوریتم کل کاری که بایستی در سمت سرویس دهنده همچنین در سمت مشتری انجام شود را بررسی نماییم :

الف) یک سوکت را که مشخصه یک ارتباط است بوجود بیاوریم . این قسمت باید نوع ارتباط خود با سیستم عامل را (tcp یا upd ) معرفی نماییم . این کار در محیط یونیکس توسط تابع ()socket انجام می گیرد .

ب) به سوکتی که باز کرده اید آدرس پورت نسبت می دهیم بدین صورت تمام بسته های Tcp یا upd ای که آدرس پورت مقصدشان با شماره پورت برنامه شما مطابقت دارد به سمت برنامه شما فرستاده می شود این کار توسط تابع سیستمی() bind انجام می شود.به این پورت ، پورت منفعل می گویند .



ج) حال باید به سیستم عامل بگوییم کارش را برای پذیرش تقاضاهای ارتباط شروع نماید.این کار توسط تابع سیستمی ()listen انجام می شود . چون ممکن است تعداد تقاضاهای ارتباط زیاد باشد باید حد اکثر تعداد ارتباط tcp را که می توان پذیرفت مشخص کرد و برای آن بافر در نظر گرفت.






د) در پایان باید از سیستم عامل بخواهیم که یکی از ارتباطات معلق را به برنامه شما معرفی کند . این کار توسط تابع () accept انجام می گیرد.


ه) از دستورات () send و ()recv برای مبادله داده استفاده می شود.
و) در پایان باید ارتباط خاتمه یابد . به دو صورت است:




در برنامه مشتری بایستی اعمال زیر را انجام داد :
الف) یک سوکت بوجود بیاوریم
ب) نیازی نیست مانند سرویس دهنده به سوکت خود آدرس پورت نسبت دهیم یعنی لزومی به استفاده از دستور bind نیست چرا که برنامه سمت client منتظر تقاضای دیگران نیست بلکه خودش متقاضی برقراری ارتباط است . بنا بر این با تابع connect تقاضا را به سمت server می فرستیم .
ج) از توابع ()send یا ()recv برای ارسال و در یافت داده اقدام کنید .
د) ارتباط را با توابع close یا shut down می بندیم .



توابع مورد استفاده در socket programming در :C
* توابع socket غیر واقعی :
Ret = parse _net work _args (args & argv & host & port &errmes)
تنظیمات مربوط به شبکه را انجام می دهد و ویرایش می کند. به دنبال host و port می گردد و آنها را منتقل می کند . در صورت موفقیت صفررا برمی گرداند.
**قسمت :server

1)-Port _sk = socket(af_inet ,sock_stream ):

2)-Port _sk = tcp _passive _ open (port)
در قسمت server اجرا می شود.
Port = تعداد rort هایی که به کار می رود .
است . Port- sk توصیفگر socket
3)-Bind (polt _ sk, &bind_ddr , addr _lenl);

4)- Client _sk = accept (port _sk , &bind _ addr ,&lenl);

:client قسمت ***
1)-Serr_ sk = tcp _active_open (host ,port)
Host = نام ماشین server
Port = شماره پورتی که به کار می رود
Server-sk = توصیفگر socket است .
آدرس IP و HOST پیدا می شود :
Host Ip = gethost by name(host name);
2) Ser v _sk = secket (af _ inetو sock _ stream;);
3) connect( ser v _skو &bind _addr و addr _len);
سوکت Serv _sk می تواند برای صحبت با سرور به کار رود .
Header files های اصلی در زبان c برای برنامه نویسی socket :

. لازمه انواع داده ای است
. ها می باشد error شماره
. و سیستمی را شامل میباشد socke ساختارها و ثابت های
. ساختارهای مربوطه به شبکه ای را شامل می باشد
. را شامل می شود socketتوابع مربوط . توابع کاربردی را شامل می باشد
توابع مدیریت داده در شبکه :
IP و نام dns یک host را بر می گردان * get host by name -
نام service و پروتکل را بر می گرداند. * get serv by name -
نام host را در شبکه محلی بر می گرداند. * get host name -
* get server by name get serv by port get servent
* getproto by name get proto by number get protobyeat
* get net by name get net by add get net ent
 
مقایسه ارسال و دریافت داده ها به روش : udp
روال در هر دو برنامه سمت client ،server به یک شکل است :
الف ) یک سوکت از نوع دیتا گرام ایجاد کنید . این کار با فراخوانی تابع ()socket با پارامتر sock-dgram انجام می شود
ب) با تابع ()bind به سکوت ایجاد شده آدرس پورت مورد نظرتان را نسبت دهید.
ج ) حال می توانید منتظر دریافت و یا ارسال داده ها باشید ( در دریافت آدرس برنامه مبداء ( آدرس ip و port ) مشخص می گردد و ارسال امکان پذیر است).
 
ارسال و دریافت با توابع انجام می شود.
 
 
د) سوکت ایجاد شده را ببندید .
 
 
نتیجه گیری:
 
بر اساس تمام برنامه های کاربردی تحت شبکه لایه چهارم مفهومی به نام سوکت است . این مفهوم به منظور برقراری ارتباط برنامه های تحت شبکه و تبادل جریان داده ها بین پروسه ها ابداع شد . لذا این مقاله نقطه آغازی برای همه برنامه نویسانی خواهد بود که به نحوی مجبور خواهند شد برنامه کاربردی تحت شبکه اینترنت بنویسند.
 
منابع :
 
* Internet working with tcp/ ip : uelumes I II III Douglas comer , prentice hall , 2000
* the whole internet user s guide & catalog by ed kroll; oreilly &associates
 
* socket Api programmer s refrence 2003

 
comment نظرات ()