سلام و وقت بخیر. در این جلسه درمورد مبحث مهم socket صحبت خواهیم کرد. معمولا در داخل یک سیستم یا بهتر بگوییم در داخل یک localhost، برای ارتباط میان 2 تا پروسه ( process )، سه تکنیک وجود دارد.
1- Pipes
2-Message queues
3-Shared memory
میتوان دسته بندی بیشتری هم کرد ولی موارد بالا، از همه کلاسیک تر و عام تر هستند. اما در شبکه چطور 2 تا پروسه با هم ارتباط برقرار میکنند؟؟ مثلا وقتی شما از کامپیوتر خود به یک وب سایت متصل میشوید، پروسه در حال اجرا درون کامپیوتر شما web browser و پروسه در حال اجرا در سرور مقصد، web server است. این دو پروسه از طریق یک تکنیک به نام Socket با یکدیگر ارتباط برقرار میکنند.
سوکت چیست؟
بنا بر تعریف، سوکت نقطه نهایی ارتباط بین ئو سیستم در یک شبکه است. یکم دقیق تر، سوکت به مجموع IP و Port درون یک سیستم گفته میشود. بنابراین در هر طرف ارتباط، یک سوکت وجود دارد که با سوکت دستگاه طرف مقابل ارتباط برقرار میکند.بطور کلی دو دسته ارتباط در بستر شبکه داریم:
1- ارتباط بر بستر مدل OSI
2- ارتباط بر بستر مدل TCP/IP
احتمالا میدونید که مدل OSI بیشتر جنبه آکادمیک و آموزشی دارد و فقط در کتاب های مرجع زیارتش میکنید و در عمل مدل TCP/IP زیرساخت ارتباطات شبکه ها است. سوکت را میتوان با بسیاری از زبان های برنامه نویسی تا اونجایی که من میدونم نوشت، مثل ++c و java و ... . اما در اینجا ما نوشتن یک سوکت خیلی ساده به زبان c را تمرین میکنیم.
پس اگر تا اینجا مطلب را دنبال کرده باشید میدانید که برای برقراری ارتباط، به دو طرف حداقل نیاز است که یکی سرویس دهنده ( سرور ) و یکی سرویس گیرنده ( کاربر ) باشد. یک فایل خالی جدید به نام server.c ایجاد کرده و کد های زیر را درون آن وارد کنید.
#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <time.h> int main(int argc, char *argv[]) { /*Variable*/ int listenfd = 0, connfd = 0; struct sockaddr_in serv_addr; char sendBuff[1025]; time_t ticks; /*Creat Socket*/ listenfd = socket(AF_INET, SOCK_STREAM, 0); memset(&serv_addr, '0', sizeof(serv_addr)); memset(sendBuff, '0', sizeof(sendBuff)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(5000); /*Call Bind*/ bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); /*Listen*/ listen(listenfd, 10); /*Accept*/ while(1) { connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); ticks = time(NULL); snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks)); write(connfd, sendBuff, strlen(sendBuff)); close(connfd); sleep(1); } }
کد بالا چکار میکند؟
1- تابع socket درون kernel یک سوکت بدون نام درست کرده و یک عدد int به عنوان socket descriptor برمیگرداند. این تابع domainfamily را به عنوان آرگومان اول میپذیرد. چون از IPv4 استفاده میکنیم، به عنوان Internet Family برای IPv4 از کلمه کلیدی AF_INET استفاده میکنیم. در آرگومان دوم این تابع مشخص میکنیم که از لایه انتقال مدل TCPIP استفاده کن. برای این آرگومان مهم است که مقداری که باریش مشخص میکنیم، خاصیت acknowledgement داشته باشد مثل TCP. ( اگه چیزایی که میگم رو متوجه نمیشید برید مباحث +network رو یه نگاه بندازید. ) آرگومان سوم هم معمولا صفر است تا kernel از پروتکل پیش فرض ارتباطی شی گراء یعنی TCP استفاده کند.
2- تابع bind جزئیاتی که در ساختار serv-addr وجود دارد را به سوکتی که در مرحله قبل ایجاد شد الحاق میکند. جزئیاتی که الحاق میشود شامل family/domain ، و interface ای که باید از آن گوش کند ( listen ) و پروتی که سرور روی آن گوش میدهد و منتظر درخواست کاربر است.
3- در تابع listen آرگومان دوم که عدد 10 گرفته یعنی اینکه سرور حداکثر 10 تا connection از طرف سوکت کاربر قبول میکند.
4- پس از اینکه تابع listen با موفقیت ایجاد شد، سوکت بصورت عملیاتی شروع به گوش دادن میکند.
5- هنگامی که تابع accept فراخوانی میشود، سرور وارد فاز sleep میشود و منتظر میماند تا درخواستی از سمت کاربر فرستاده شود. وقتی که درخواست کاربر به سرور رسید، three way TCP handshake انجام میشود (به مباحث +network مراجعه کنید) و socket descriptor را به عنوان سوکت کاربر برمیگرداند.
6- تابع accept در یک حلقه بی نهایت قرار دارد، پس سرور همیشه آماده بکار است. برای اینکه سرور منابع سیستم را یکباره اشغال نکند یک تاخیر 1 ثانیه ای گذاشتیم.
7- هنگامی که یک درخواست از طرف کاربر رسید، در سرور زمان و تاریخ محاسبه شده و از طریق descriptor که توسط تابع accept برگردانده میشود، درون سوکت ارتباطی با کاربر نوشته میشود و کاربر آنرا دریافت میکند.
حال یک فایل خالی دیگر با اسم client.c ایجاد کرده و کد های زیر را درون آن وارد کنید.
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sockfd = 0, n = 0; char recvBuff[1024]; struct sockaddr_in serv_addr; if(argc != 2) { printf("\n Usage: %s <ip of server> \n",argv[0]); return 1; } memset(recvBuff, '0',sizeof(recvBuff)); if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("\n Error : Could not create socket \n"); return 1; } memset(&serv_addr, '0', sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(5000); if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0) { printf("\n inet_pton error occured\n"); return 1; } if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("\n Error : Connect Failed \n"); return 1; } while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0) { recvBuff[n] = 0; if(fputs(recvBuff, stdout) == EOF) { printf("\n Error : Fputs error\n"); } } if(n < 0) { printf("\n Read error \n"); } return 0; }
حالا این کد چه میکند ؟
1- اینجا هم یک سوکت با تابع socket ایجاد میشود.
2- اطلاعاتی نظیر IP و پورت سرور در یک struct توسط تابع connect صدا زده میشود که قصد دارد با سوکت ایجاد شده با سرور ارتباط برقرار کند.
3- دقت کنید که در اینجا کاربر را ملزم به استفاده از یک پورت خاص برای ایجاد سوکت نکردیم، بلکه انتخاب پورت را به kernel واگذار کردیم. در حالی که پورتی که سرور با آن کار میکند باید مشخص باشد که معولا در سناریو های واقعی شماره پورت های زیر 1024 است مثل پورت 80 برای ارتباطات HTTP.
4- هنگامی که سوکت ایجاد شد، سرور دیتایی (در اینجا date + time ) را برای کاربر، از طریق سوکت ارتباطی برقرار شده میفرستد و کاربر به راحتی آنرا میخواند.
برای اجرای برنامه 2 تا ترمینال باز کنید. در یکی server.c را کامپایل و اجرا کرده و در دیگری client.c را کامپایل و اجرا کنید. به ابنصورت که هر دو کد را با gcc کامپایل کنید. سپس در ترمینالی که کد سرور را کامپایل کردید دستور server. را زده، خواهید دید که ترمینال را اشغال میکند و دستور دیگری نمیتوانید وارد کنید. سپس در ترمینالی که کد کاربر را کامپایل کردید دستور client. را با آرگومان IP سرور وارد کنید.
در ترمینال سرور:
[root@CentOS6 c]# gcc -o server server.c [root@CentOS6 c]# ./server
در ترمینال کاربر:
[root@CentOS6 c]# ./client 127.0.0.1 Sat Aug 13 19:37:52 2016 [root@CentOS6 c]#
پایان قسمت بیست و ششم
نویسنده : سید محمد باقر موسوی
منبع : جزیره برنامه نویسی وب سایت توسینسو
هرگونه نشر و کپی برداری بدون ذکر منبع و نام نویسنده دارای اشکال اخلاقی است