Arabic linux network programming in c
مساء الخير عليكم
ا
لدرس ده هيتكلم عن : Network programming in linux
اتوقع منك انك تكون قريت ال C tutorial الي انا كتبتها وتكون فهمتها بشكل كويس لان ديت هتوصلك لمستوي كويس يخليك تقدر تفهم الكلام الي انا هقوله دلوقتي.. نخش في الجد بقي
مقدمة : اول نقطه واهم نقطه انك تكون عارف ان كل حاجه في ال Linux هي file ملف . سامعك دلوقتي بتقول يعني ايه ؟ وايه علاقة الملفات بموضوعنا ؟ هقولك ان اللينكس بيتعامل مع كل حاجه كملف وده معناه ان اي حاجه بيتم الينكس التعامل معاها بيستخدم فيها دوال الكتابه والقرائه زي write/read. سواء ان كان بيكتب لملف عادي بيكتب ل device بيقرا من كارت الصوت او كارت الفيديو بيقرا او بيكتب لكارته النتورك عشان يستقبل او يبعت البيانات. طيب دلوقتي اذاي اقدر اتعامل
مع الملفات ديت ؟
الملفات ديت بيتم التعامل معاها عن طريق حاجه اسمها File descriptor بعيد عن كل التعقيدات التقنيه الي ممكن نتكلم عنها لو شرحنا يعني ايه File descriptor باستفاضه , ولذلك انا هشرحه بشكل مختصر
ال file descriptor هو الوسيلة الي من خلالها تقدر تقرا وتكتب لملف ما . بيتم الحصول علي ال file descriptor دوت باستخدام داله open بتقوم بفتح الملف وارجاع file descriptor تقدر من خلاله تكتب / تقرا من الملف الي انت طلبت فتحه
كل ده جميل بس ايه علاقته بال sockets وال network programming !!
wow sockets ?? يعني ايه socket ??
جميل جدا
تعريف ال Socket
Socket: وسيلة اتصال بين طرفين سواء ان كان الطرفين علي نفس الجهاز او كانوا علي جهازين
هو ده تعريف ال SOCKET 
وبما ان ال socket ككل حاجه في الينكس ملف , يبقي طريقة التعامل مع ال socket دوت هو فتحه عن طريق دالة open والحصول علي ال file descriptor بتاعه والقراية والكتابه بداخله باستخدام
write/read صح ؟
اه صح بس غلط

ال socket هو نوع متميز من الملفات لانه زي مقلنا بيتم من خلاله التواصل بين طرفين وده معناه اننا محتاجين خصائص معينه نقدر نتعامل بيها مع الملف دوت ! نوع ال protocol الي هنستخدمه الخ الخ . بس ده مش معناه ان دالة open مش بيتم استخدامها لفتح ال socket لا بيتم استخدامها ولكنها مغلفه داخل داله اخري تسمي
socket()
دالة socket ديت هي الداله المتخصصه بفتح ال socket وارجاع ال file descriptor الخاص بيه , حاجه تانيه جديره بالذكر ان التعامل مع ال socket هو مجرد قرائه وكتابه كأي ملف ! ولذلك يمكن استخدام الدوال write / read وفي بعض الاحيان بيتم استخدامهم . ولكن في دوال اخري متخصصه في الموضوع دوت وهي send/recv
استخدامها يعد كنوع من استخدام دوال متخصصه مع ملف خاص . ولكن مش
شرط اننا
نستخدمهم
الا اذا اردنا تحكم خاص في عملية ال sending او ال receiving جميل قوي ايه بردوا علاقة ال socket بال network programming ?
ال socket كما قلنا من قبل هو ملف خاص بيتم التعامل معاه بدوال القرائه والكتابه وبيتعامل مع ال network card لارسال او استقبال البيانات . وده منعنا انك تقوم بفتح ال device بتاع ال network card وتقوم بعمليات معقده جدا لتتحكم في ارسال واستقبال البيانات ولذلك بنقوم باستخدام ال socket.
جميل جدا الكلام دوت .في درسنا هنتعامل مع نوع واحد فقط من ال sockets يسمي بال INTERNET SOCKETS وذلك لان في انواع تانيه مش هقوم بسردها هنا ولكن هزودك ب link لل man pages الخاصه بيها عشان تقدر تطلع عليها
دلوقتي بعد معرفنا اننا حنتعامل مع ال INTERNET SOCKET يتبادر لذهن بعضكم الي يعرف في ال networking ان في بروتوكولات للتعامل مع الانترنت TCP/IP , UDP
مش هشرحهم هنا بردوا لان الموضوع برمجي بحت ولكن هوضح الفرق بينهم
TCP: يقوم بعمل تحكم في ال PACKETS المرسله والمستقبله عن طريق ال 3 hand shaking ويقوم بالتحقق من وصول البيانات بشكل سليم من طرف الي الطرف الاخر UDP: يعرف بال connection less protocol وده لان التواصل بين طرفين فيه غير متحكم فيه بشكل كامل ال packets المرسله فيه مش بيتم التحقق منها ومش شرط توصل بنفس الترتيب الي تم بعثها فيه واحد يسالني ويقلي طالما كده استخدمه ليه ؟

وده لانه اسرع من ال
TCP وبيتم استخدامه في ارسال الصور او ملفات ال video او ال streaming (زي ال youtube كده)وده لان الخطأ في نقل البيانات فيه محتمل ويمكن تجاوزه يعني لو نقص فرايم او اتنين من الفيديو مش هتحصل كارثه ومحدش حيحس بحاجه وهتعدي لكن لما تكون بتبعت binary file اي نقص في اي packet فيه معناه corruption في ال DATA وعدم وصول الملف بشكل صحيح ولكن اجدير بالذكر بردو انه يمكنك انت كمبرمج ان تقوم بعمل بروتوكول يعمل فوق ال UDP ويقوم بالتحقق من البيانات المرسله
امثلة البرامج الي بتقول بعمل كده هي برامج ال ftp مثلا !
جميل جدا دلوقتي احنا هنتكلم كمان عن حاجه تانيه عشان نوصل الكلام ببعضه : ال internet sockets فيها هي كمان اكتر من نوع

متنحش ايوه فيها اكتر من نوع ولكن احنا في درسنا هنتعلم منهم اتنين بس SOCK_STREAM / SOCK_DGRAM
وبما اننا شرحنا الفرق بين ال tcp وال udp اقدر اقلك بكل فخر ان ال
SOCK_STREAM بيستخدم ال TCPوال SOCK_DGRAM بيستخدم ال UDP وابقي متأكد كمان انك فهمت الفرق بينهم , في حالة اذا مفهمتش الفرق بينهم زي ماعلي طول بقول مش هيبقي فيه اي مشاكل بس لو سمحت اقفل البراوزر

جميل جدا بعد متكلمنا عن كل الكلام الكتير دوت الي هو
مقدمه هنتكلم عن مقدمة تانيه

هنا هنتكلم عن مقدمة عن ال IP Addresses واذاي نقدر نتحكم في ال socket وعن طريق ايه نقدر نخلي ال socket يربط نفسه بجهاز تاني او طرف تاني
اولا , كل جهاز علي الانترنت ليه IP address وديت اخر حاجه هتكلم فيها عن ال IP addresses
ثانيا كل جهاز علي النت ليه IP addresses ليه حاجه اسمها ports وديت بردوا اخر حاجه حتكلم فيها عن ال ports
في امور تانيه متعلقه بالشبكات مش هشرحها لان ولتاني مره الموضوع برمجي بحت , عاوز مقدمة في الشبكات هتلاقي لينك في اخر الموضوع لموضوع عربي كمان

Network byte ordering:يعني ايه ؟ عشان نتكلم بشكل صريح جهازك مش شرط يكون زي جهاز غيرك
انت البروسيسور بتاعك Intel انا بتاعي Bitingan ماركه جديده في السوق 500 كور ومساحه تيرا هيرتز

ايه بقي الي دخل ال processors في الموضوع ؟ هقلك
دلوقتي انت بتبعتلي سلسه ارقام 1 2 3 4 5
جهازي انا بيقرا ال bytes من اليمين للشمال هيقراها 1 2 3 4 5 جهازك انت بيقراها من الشمال لليمين 54 3 2 1 ولذلك هناك نوعان من ال network byte ordering
Little Endian
Big Endianلو جهاز بيستخدم الاول هيقرا الbytes بشكل عكسي من الشمال لليمين مثلا , انما جهازي انا هيقراها من اليمين للشمال بشكل صحيح . ولذلك عشان اقدر اتعامل معاك لازم يكون في نوع من انواع التحويل من جهازي لجهازك
بس فكرة اني احول من جهازي لجهازك ديت في حد ذاتها متعبه ومرهقه وغير مجديه !
وانا هعرف جهازك نوعه منين ؟ مش هتصل بيك بالتليفون اقلك انت intel ولا amd ?
ولذلك تم حل تلك المشكله باستخدام ال api
ال Big Endian هو ال Network byte order الي بتستخدمه ال network ولذلك التسميه
اما الي بيستخدمه جهازك فده بيكون معرف في ال API بتاعتك وانت كمستخدم تقدر تسميه Host byte order
وده بيختلف من جهاز للتاني لو Interl هيكون Little Endian لو بتنجان هيكون Big Endian وهكذا

طيب جميل عشان احول بقي الكلام دوت اعمل ايه ؟ استخدام الداول الاتيه
--man page snip
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
الداول ديت وظيفتها انها تحول short/long bytes من Host byte ordering الي Network byte ordering او العكس
ntohs = Network to host shot
ntohl = Network to host longخلينا نسيب الكلام دوت دلوقتي بعد موضحناه لان فايدته هتبان بعدين
كما سبق وقلنا من قبل ان ال socket بيحتاج مكان يخزن فيه معلومات عن الجهاز المتصل او المكان الي بيتصل منه
طيب هيخزنها فين ؟ network structures هنستخدم منها واحده دلوقتي
Socket addressing:اصعب حاجه هي الي هنتكلم عليها دلوقتي , بعد كده كله هيبقي عادي جدا !
ال socket addressing معناه انك تعطي عنوان لل socket الي انت شغال عليه
وال address دوت هيكون تابع لحاجه اسمها domain . الي domain دوت هو الي بيحدد طريقة ال addressing فاكر لما اتكلمنا عن ال socket types ? وقلنا ان في حاجه اسمها INTERNET_SOCKETS اهي ال INTERNET_SOCKETS ديت بتتبع domain معين اسمه AF_INET هو الي بيحدد طريقة ال addressing لل socket وبيعرفه انه هيقوم باتباع قوانين ال ip addressing
طبعا زي مقلت في انواع تانيه من ال sockets وبالتالي في انواع تانيه من ال domains بس احنا بنتكلم عن ال INTERNET SOCKETS
طيب ال addressing دوت بيتم تخزينه فين ؟
وده يدفعنا اننا نتكلم عن ال IPv4 addressing "ال tutorial ديت مجالها خارج تماما عن ال ipv6"
لتخزين ال addresses لل ipv4 بيتم استخدام structure اسمها sockaddr_in ومتحويتها كالاتي
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
struct in_addr {
unsigned long s_addr;
};
اولا sin_family ديت بيتم فيها تعريف ال addressing domain في حالتنا بيكون AF_INTER
ثانيا ال sin_port وده بيتم تحويله من host byte order ل network byte order عن طريق ايه ؟ ايوه بالظبت الدوال الي اتكلمنا عنها
ثالثا ديت structure تانيه بيتم فيها حفظ ال ip address وطبعا بيكون محول ل network byte order وده عن طريق استخدام داله تانيه غير الدوال الي اتكلمنا عنها
وهنشوفها كمان شوية في ال code الي هنستخدمه
رابع واخر حاجه ديت حاجه بتستخدم لل compatibility مع structures تانيه مفيش داعي تفهم معناها كل الي عليك تعمله انك تصفرها
ودلوقتي اسيبكم مع كود صغير تفهموه وحشرحه بعد متقروه
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(void)
{
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(80);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));
return 0;
}
كدا احنا قمنا بعمل form ل address لل socket
Domain = AF_INET
port = 80
address = INADDR_ANY --> ديت معناها اي address وقمنا بتحويلها من host ل network byte ordering
جميل طب وبعدين بقي ؟
نلم بقي الي عرفناه لحد دلوقتي عشان تفضل مركز معايا
عرفنا يعني ايه file descriprot
عرفنا يعني ايه socket
عرفنا يعني ايه Byte ordering
عرفنا يعني ايه Addessing
كل ده جميل بس بدون معرفة ال API الصحيحه لاستخدامه فهو لايساوي شيئ ودلوقتي انا هقوم باستخدام السورس كود الي فات وهضيف في اضافه اضافه مع كل API function بستخدمها واعرفك انا استخدمتها ليه
[/list]
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(void)
{
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(PF_INET , SOCK_STREAM , 0);
if(sockfd == -1)
return -1;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(80);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));
return 0;
}
اول داله
int socket(int domain, int type, int protocol);
domain = PF_INET
وده لسبب بسيط جدا وهو ان في بعض البروتوكولات ال domain الخاص ب AF_INET مش بيدعمها ولكن PF_INET بيدعمها ولذلك استخدام PF_INTET (PF = Protocol family) واستخدام AF_INET في ال sockaddr_in لل addressing domain
ثانيا ال type
SOCK_STREAM , SOCK_DGRAM ?
رابعا ال protocol عادة بتكون القيمه 0 والاختيار بيرجع للكيرنال ولكن كما قلنا من قبل ووضحنا الاختلاف بين الاتنين فال tcp الي بيتم اختياره في حالة ال streaming
قيمة الارجاع طبعا بتكون file descriptor ل socket مفتوح.
جميل بعد مابقي عندنا socket متفوح ايه الي حيحصل ؟ حاجه من الاتنين يانتصل يانستني اتصال
وده معناه اننا ممكن نستخدم bind او connect
bind بيتم استخدامها عشان تعمل bind لل socket مع ال ip وال port الخاص بالجهاز الي عاوز تستقبل عليه الاتصال او البيانات
انما connect بتحتاج ال socket file descriptor و structure تحتوي علي address للجهاز المتصل بيه
هنتكلم عن bind الاول
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(void)
{
int sockfd = 0;
int r = 0;
struct sockaddr_in my_addr;
sockfd = socket(PF_INET , SOCK_STREAM , 0);
if(sockfd == -1)
return -1;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(80);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));
r = bind(sockfd , (struct sockaddr*)&my_addr , sizeof(my_addr));
if(r == -1)
{
fprintf(stderr , "error at binding");
return -1;
}
close(sockfd);
return 0;
}
نفس الكلام في الكود الي فات مع اختلاف الداله الجديده
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
اول معطي هو ال socket file descriptor , تاني معطي هو ال address structure التالت هو حجم ال structure ديت
وبكده يبقي انت عامل bind علي البورت 80 تحت اي ip
بس يعني اي تحت اي ip ? يعني اي كونيكشن لل port 80 هيتوصل بيك . بس انت مش عاوز كده صح ؟
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(void)
{
int sockfd = 0;
int r = 0;
struct sockaddr_in my_addr;
const char *addr = "127.0.0.1";
sockfd = socket(PF_INET , SOCK_STREAM , 0);
if(sockfd == -1)
return -1;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(80);
if(inet_aton(addr , &my_addr.sin_addr) == 0)
{
fprintf(stderr , "Error at forming address\n");
return -1;
}
memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));
r = bind(sockfd , (struct sockaddr*)&my_addr , sizeof(my_addr));
if(r == -1)
{
fprintf(stderr , "error at binding\n");
return -1;
}
close(sockfd);
return 0;
}
int inet_aton(const char *cp, struct in_addr *inp);
بتاخد ip address في سلسله نصيه وبتقوم بتحويله بطريقتها ل ip address وتعمله تسجيل في ال sin_addr وبتقوم بتحويله كمان ل net work byte order
جميل قبل منعمل connect علي اي حاجه اي رايك في اننا نعمل سرفر بسيط ؟ وبعدين نعمل ليه ال client ونبقي خلصنا ال tutorial في سوري واحد ؟
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main(void)
{
int sockfd = 0;
int r = 0;
socklen_t len = 0;
struct sockaddr_in my_addr;
struct sockaddr_in his_addr;
const char *addr = "127.0.0.1";
sockfd = socket(PF_INET , SOCK_STREAM , 0);
if(sockfd == -1)
return -1;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(80);
if(inet_aton(addr , &my_addr.sin_addr) == 0)
{
fprintf(stderr , "Error at forming address\n");
return -1;
}
memset(my_addr.sin_zero , 0 , sizeof(my_addr.sin_zero));
r = bind(sockfd , (struct sockaddr*)&my_addr , sizeof(my_addr));
if(r == -1)
{
fprintf(stderr , "error at binding\n");
return -1;
}
printf("[+]Listening on %s at port %d\n"
,inet_ntoa(my_addr.sin_addr)
, ntohs(my_addr.sin_port));
if(listen(sockfd , 0) != 0)
{
fprintf(stderr , "Error while listening\n");
return -1;
}
if(accept(sockfd , (struct sockaddr*)&his_addr , &len) != 0)
{
fprintf(stderr , "Error in accepting connection\n");
return -1;
}
printf("Connection received from %s at %d\n",
inet_ntoa(his_addr.sin_addr)
,ntohs(his_addr.sin_port));
close(sockfd);
return 0;
}
الدوال الجديده
char *inet_ntoa(struct in_addr in);
الداله ديت بتقوم بتحويل address من network byte order ل ascii string بيتوصف فيها ال ip بصورته الطبيعيه
1.1.1.1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int listen(int sockfd, int backlog);
اول حاجه listen بتقوم بعمل listen علي ال socket الي انت عامله bind بالفعل وبتستني اي connection علي ال port الي انت محدده في ال address structure الي انت عاملها bind مع ال socket
تاني متغير هو طابور , يعني يقدر يعمل طابور لكام client عاوز يعمل اتصال بالسرفر ؟ احنا عملنا 0
لان بالفعل ملهاش اي لزمه او قيمه معانا
نيجي ل accept , القيمه التي تم ارجاعها من accept هي file descriptor ل socket بينك و بين ال client وهو ده الي من خلاله تقدر read/write مع ال client التاني
واحد يسالني ال socket الاول كده لزمته ايه ؟
accepting or connecting ! مكنتش هتقدر تفتح قناة اتصال وتعمل listen علي بورت او ip معين من غير ال socket دوت
وبكده يبقي خلصنا ال server
نيجي لل client
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
static void usage(const char *argv)
{
fprintf(stderr , "%s ip port\n",argv);
_exit(0);
}
int main(int argc , char **argv)
{
int sockfd = 0;
int r = 0;
struct sockaddr_in his_addr;
if(argc < 3)
usage(argv[0]);
sockfd = socket(PF_INET , SOCK_STREAM , 0);
if(sockfd == -1)
return -1;
his_addr.sin_family = AF_INET;
his_addr.sin_port = htons(atoi(argv[2]));
if(inet_aton(argv[1] , &his_addr.sin_addr) == 0)
{
fprintf(stderr , "Ip error\n");
usage(argv[0]);
}
memset(his_addr.sin_zero , 0 , sizeof(his_addr.sin_zero));
if(connect(sockfd , (struct sockaddr*)&his_addr , sizeof(his_addr)) == -1)
{
fprintf(stderr , "connecting error\n");
return -1;
}
close(sockfd);
return 0;
}
استخدمنا داله واحده جديده
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
اولا الخطوات المتبعه العاديه في forming an address وده هيكون للشخص المتصل بيه
فتح socket
استدعاء الداله واعطائها الsocket وال address والحجم الخاص بال address
الجدير بالذكر اننا لم نقم بعمل bind ! بالظبت لاننا مش محتاجينه وفي الحاله ديت ال address الخاص بال client يسمي wild address ال ip الداخلي هيكون 0.0.0.0 والبورت كمان هيكون 0 الا اذا اردت انك تقوم بعمل bind لل socket واعطائه address معين وبورت معين كمان ديت ترجعلك
طبعا الاتصال تقدر تعمل send/recv او read/write وديت مهمتك انت لان الموضوع مش مستحق الذكر هنا علاوه علي اني حاسس بوجع رهيب في ضهري دلوقتي يمكن اضيف الجزئيه البسيطه ديت بعدين