7.2 مكتبة سي القياسية ونظام يونكس | كتاب لينكس الشامل | >> |
مكتبة سي القياسية التي تأتي مع أنظمة GNU/Linux هي من جنو GNU C Library
وللاختصار glibc اكتب info glibc
لتقرأ معلومات تفصيلية (تفصيلاً مملاً)
وهي مكتبة مطابقة لمواصفات ANSI C و ISO/IEC 9899/1990
و ISO/IEC 9945-1/1996 POSIX و ANSI/IEEE Std 1003
وهي تطبق بعض متطلبات BSD 4.4
وبعض XSI أي X/open system interface
هذا التبعيض جاء لأسباب تتعلق بالأمان والموثوقية
(لأن هناك بعضٌ لم يطبق لأنه قد يسبب ثغيرة أو ماشابه)
أو لأسباب تتعلق بالأداء . تضمن لك هذه المعايير
أن يعمل برنامج على أكبر عدد من الأنظمة لأطول فترة ممكنة
بأعلى أداء ممكن. بإمكانك القول أن مكتبة سي القياسية من جنو
تعمل على أي نظام تقريباً في هذا العالم المتغير.
عند استدعاء أي وظيفة من مكتبة سي القياسية فإنها في
الغالب تعيد ما يدل على أنها نجحت أو فشلت
مثلاً malloc
تعيد مؤشر على الذاكرة المحجوزة
و NULL أي صفر في حال الفشل.
وبعض الوظائف تعيد رقم سالب في حالة الفشل ،
نستخدم هذا الرقم لنعرف أن الخطأ قد حدث
ولكن حتى نحصل على تفاصيل أكبر عن الخلل الذي سبب هذا الخطأ
بطريقة مفهومة أكثر نستخدم رقم يوضح سبب الفشل اسمه
errno
وهو معرف في ملف errno.h
يفضل قبل أن تستدعي الوظيفة التي تسبب أخطاء أن تصفر هذا
المتغير وتفحصه بعد استدعائها ، ولترجمة هذا الرقم الأصم إلى
نص مفهوم بشرياً نستخدم strerror(errno)
التي تعيد سلسلة نصية تفسر الخطأ
هذه الوظيفة معرفة في stdio.h
يمكنك أن تطبع ناتجها على الخرج القياسي باستعمال printf
أو على جهاز الخطأ القياسي بوضع stderr على أنه الملف في
fprintf
أو بمجرد استدعاء
perror
أي print error
التي تأخذ معاملها مؤشر للنص المراد طباعته
على جهاز الخطأ وأسهل طريقة هي بتمرير NULL
لها فتطبع
هي تلقاياً الخطأ الذي يعبر عنه strerror(errno)
مثلاً
#include<stdio.h> int main() { int *a; /* try to allocate 1000 integers */ if ( !(a=malloc(sizeof(int)*1000)) ) { /* if fail print error and exit*/ perror(NULL); return 1; } return 0; }
if ( !(a=malloc(sizeof(int)*1000)) )
تعني ضع malloc(sizeof(int)*1000)
كقيمة ل a
الآن انفي الناتج منطقياً "!" أي إذا كانت صفراً NULL
(منطقياً تسمى FALSE)
تصبح TRUE الآن افحص هل هي متحققة TRUE
يمكن كتابة البرنامج السابق كما يلي
#include<stdio.h> #include<stdlib.h> int main() { int *a; /* try to allocate 1000 integers */ a=malloc(sizeof(int)*1000); if ( !a ) { /* if fail print error and exit*/ perror(NULL); return 1; } return 0; }
#include<stdio.h> int main() { int *a; /* try to allocate 1000 integers */ a=malloc(sizeof(int)*1000); if ( a==NULL ) { /* if fail print error and exit*/ perror(NULL); return 1; } return 0; }
يجب أن تلاحظ أن strerror
تعيد سلسة نصية محجوزة مسبقاً بطريقة static
بكلمات أخرى أنه صالح حتى أول استدعاء آخر لهذه
الوظيفة مرة أخرى لأنه حينها سيتم الكتابة فوقه
أي أن البرنامج التالي
#include<errno.h> #include<stdio.h> int main() { char *str1,*str2; str1=strerror(EPERM); str2=strerror(ENOENT); fprintf(stderr,"\n%s\n",str1); fprintf(stderr,"\n%s\n",str2); return 1; }
No such file or directory.
مرتين
جرت تقاليد UNIX أن يطبع البرنامج عد حدوث خطأ اسمه ثم نقطتين ثم الخطأ كما يتوقعه المبرمج ثم فاصلة منقوطة ثم ما تخبرنا به مكتبة سي من تفاصيل يمكن عمل ذلك كما في المثال (المثال مأخوذ من GNU C library manual مع تعديل بسيط)
/* errtest.c: a program to tell error */ #include<errno.h> #include<stdio.h> int main() { char name[80]; FILE *file1; printf("Enter file name: "); fgets(name,80,stdin); printf("trying to open %s for reading.",name); errno = 0; if (!file1=fopen(name,"r")) { fprintf(stderr,"%s: Can't open file '%s'; %s\n", program_invocation_short_name, name,strerror(errno) ); return errno; } printf("The file '%s' opened successfully",name\n); fclose(file1); return 0; }
errtest: Can't open file 'foo.bar'; No such file or directory.
program_invocation_short_name
لإعادة اسم البرنامج مع حذف الأدلة
وهو تعبير غير قياسي من إضافات GNU وهناك أيضا
program_invocation_name
الذي يحتوي اسم البرنامج الكامل مع الأدلة
الطرقة القياسية لعمل ذلك هي استعمال المتغير argv[0]
وحذف الأدلة منه ب basename
الموجود في string.h
/* errtest.c: a program to tell error */ #include<errno.h> #include<stdio.h> #include<string.h> int main(int argc,char *argv[]) { char name[80]; FILE *file1; printf("Enter file name: "); fgets(name,80,stdin); printf("trying to open %s for reading.",name); errno = 0; if (!file1=fopen(name,"r")) { fprintf(stderr,"%s: Can't open file '%s'; %s\n", basename(argv[0]), name,strerror(errno) ); return errno; } printf("The file '%s' opened successfully",name\n); fclose(file1); return 0; }
وعندما يصبح البرنامج في حالة لا يمكنه المتابعة بعدها
بسبب حدوث خطأ عندها يمكن الخروج (من البرنامج وليس من الوظيفة) باستعمال
exit
التي تأخذ معامل رقم يمثل سبب الخروج(أي الناتج)
ويكون غير صفري لبيان أنه ناتج عن خطأ مثلاً exit(errno);
وهو معرف في stdlib.h
ولأنه سيكثر استخدامه مع الشرط أي إذا لم تنجح في كذا اخرج
هناك اختصار هو assert
التي تأخذ معاملاً هو الشرط
الذي يجب التأكد منه (شرط عدم الخروج) فإذا لم يحدث انتهى البرنامج
وإذا حدث تابع البرنامج سيره هذا الأخير معرف في assert.h
ما تعلمنا قبلاً نحجز ذاكرة في أي وقت باستعمال
malloc
التي تأخذ معاملاً يدل على حجم الذاكرة بالبايت
وتعيد مؤشر من نوع void *
يمكن تحويله
لأي نوع آخر صراحةً بكتابته بين قوسين أو تركه ضمني
(هذا الأخير مسموح به في المعايير الحديثة ولكن هناك معايير قديمة تشترط التحويل الصريح)
هذا في حال النجاح أما في حال فشل العملية تعيد NULL التي تكافئ صفر
وعندها طبعاً يجب أن لا تتابع البرنامج بل عليك
المحاولة برقم أقل أو الخروج مع كتابة أن السبب هو عدم وجود ذاكرة كافية..
وعمر هذه الذاكرة من عمر البرنامج أي عند إغلاق البرنامج
يتم تحريرها وإذا أردت تحريرها قبل هذا عليك استعمال free
التي تأخذ معامل وحيد هو مؤشر على الذاكرة المراد تحريرها
ويجب أن يكون هذا المؤشر محجوز ب malloc
أو ما يكافئها
ويجب أن لا تحرر مؤشر محرر! وبعد تحرير ذاكرة يجب أن لا تقرأ أو تكتب فيها.
لتغير حجم ذاكرة محجوزة نستخدم realloc
التي تغير الحجم مع الإحتفاظ
بالبيانات الموجودة فيها ، تأخذ realloc
معاملين الأول هو المؤشر القديم الذي تريد
تحجيمه والثاني هو الحجم الجديد لذي قد يكون أكبر أو أصغر من القديم.
في حالة أن يكون الجديد أكبر يتم محاولة توسعة المؤشر الحالي نفسه
فإذا فشلت حاول حجز ذاكرة جديدة ونقل البيانات لها
وفي حالة أن يكون الجديد أقل سيحاول تقليص حجمها فإن لم ينجح
حجز مكان آخر ونقلها إليه ، بمعنى أنه في الحالتين قد تحصل على
مؤشر جديد وفي حالة الفشل سيكون هذا الجديد NULL
دون المساس بالمؤشر القديم.
لاحظنا أنه في حال فشل malloc
أو realloc
لا يستطيع البرنامج المتابعة إذ عليه معالجة
الوضع فإذا كان البرنمج تفاعلي يفترض أن يطبع رسالة
تفيد أنه العملية تحتاج ذاكرة أكثر مما هو متوفر
وتظهر زر لإعادة المحاولة(ربما بقيم أقل) وآخر للخرج، ولكن
في أغلب البرامج النصية التي تسير على تقاليد يونكس
جرت العادة أن يطبع رسالة تتفيد الخطأ ويخرج من البرنامج
ولأن مسألة حجز الذاكرة مسألة متكررة في البرامج
ومن غير العملي متابعة كل حالة تقترح كتيبات GNU عمل وظيفة وتسميتها
xmalloc
وأخرى xrealloc
تأخذان نفس معاملات malloc
و realloc
على الترتيب وتعيدان مؤشر على الذاكرة المحجوزة
ولكن في حالة الفشل لا تعيدان NULL
بل تطبع رسالة خطأ وتخرج
هذه نسخة معدلة عنهما
void *xmalloc(size_t bytes) { void *r; errno=0; if ( !(r=malloc(bytes)) ) { fprintf(stderr,"%s: Couldn't allocate memory; %s\n\a", program_invocation_short_name, strerror(errno) ); exit(errno); /* this is exit NOT return */ } return r; } void *xrealloc(void *ptr,size_t bytes) { void *r; errno=0; if ( !(r=realloc(ptr,bytes)) ) { fprintf(stderr,"%s: Couldn't allocate memory; %s\n\a", program_invocation_short_name, strerror(errno) ); exit(errno); /* this is exit NOT return */ } return r; }
main
أو حتى في ملف منفصل
عندها يكون البرنامج مشابه لما يلي
#include<stdio.h> #include<stdlib.h> #include<errno.h> /* ... */ /* you may put xmalloc & xrealloc here */ /* ... */ int main() { int n; int *a; printf("Enter number of integers: "); scanf("%d",&n); a=xmalloc(sizeof(int)*n); printf("%i integer allocated successfully\n",n); return 0; }
تكون البانات المحجوزة باستعمال * غير مستهلة بأي قيمة وهذا
لا يعني أنها تساوي صفر بل تكون قيم لا يمكن ضمان قيمتها (شبه عشوائية) ،
لحجز ذاكرة وتصفيرها نستخدم calloc
التي تأخذ معاملين الأول هو عدد العناصر والثاني
هو حجم العنصر الواحد بالبايت مثلاً ما يقابل
malloc(sizeof(int)*n);
هو calloc(n,sizeof(int));
عند حجز ذاكرة باستعمال malloc
داخل وظيفة فإنها تظل صالحة حتى بعد الخروج منها (الوظيفة) إلى أن تخرج من البرنامج
بأكمله وفي الغالب فإن الكثير من الوظائف يتم تصميمها بأن
تحرر الذاكرة بعد الخروج منها عندها عليك القيام بذلك يدوياً
باستدعاء free
وللتيسير عليك وإراحتك من ذلك
يمكنك أن تحجز ذاكرة من مكدس الوظيفة باستعمال alloca
التي يتم تحريرها تلقائياً عند الخروج من الوظيفة.
انظر هذا المثال:
/* ... */ int stack_alloc(int n) { int i,*a; printf("Allocating %d integer(s): ...",n); if ( !(a=alloca(sizeof(int)*n)) ) return errno; printf("%i integer allocated successfully\n",n); for (i=0;i<n;++i) {a[i]=i; printf("a[%d]=%d\n",i,a[i]);} printf("it will be automaticlly freed\n"); /* we need not call `free(a)' */ return 0; }
هذا نموذج الوظائف التي تحدثنا عنها بخصوص الذاكرة وهي معرفة في stdlib.h
void *malloc(size_t bytes); void *alloca(size_t bytes); void *calloc(size_t items_count,size_t item_size); void *realloc(void *ptr,size_t bytes); void free(void *ptr);
size_t
هو مجرد عدد صحيح int
موضوع السلاسل النصية على علاقة وثيقة مع المنظومات(مناطق من الذاكرة)
لأن السلاسل عبارة عن منظومة عناصرها char
وتنهي ب '\0'
لهذا توفر مكتبة string.h
الوظائف اللازمة للتعامل مع المنظومات والسلاسل النصية
والفرق بينها أن الوظائف التي تتعامل مع المنظومات نعطيها
الحجم بالبايت الذي يجب أن تطبق عليه بينهما الأخرى تتحسس نهاية السلسلة
ب '\0'
وإذا أعطينها العدد فإنها تأخذ أيهما يأتي أولاً
،ويجب الإنتباه هنا أن طول السلسلة لا يشمل علامة النهاية '\0'
بمعنى آخر أن حجم الذاكرة المخصصة له تكون طول السلسة + 1 ويمكن معرفة طول السلسلة باستعمال الوظيفة.
strlen
بتمرير السلسة لها كمعامل.
لنسخ محتويات منطقة في الذاكرة إلى منطقة أخرى
نستخدم memcpy
التي تأخذ
مؤشر على المنطقة الهدف(المراد النسخ إليها) ثم مؤشر إلى المنطقة المصدر(النسخ منها)
ثم حجم المنطقة ، في المقابل لنسخ سلسلة نصية
نستخدم strcpy
التي تأخذ مؤشر على السلسلة الهدف
ثم السلسلة المصدر وأما الوظيفة strncpy
تأخذ معامل إضافي هو الحد الأعلى لطول
السلسلة.
/* strcpy-test.c: copying strings */ #include<stdio.h> #include<string.h> int main() { char s1[]="Hello world"; char s2[]="good bye ....... world"; strcpy(s2,s1); printf("new s2=%s",s2); return 0; }
s2
لتصبح قيمته
"Hello world\0..... world"
بحيث يطبع البرنامج على الشاشة عند تنفيذه
Hello world
لأنه يتوقف عند علامة النهاية
'\0'
في كل الحالات السابقة يجب أن تكون المنطقتين غير متقاطعتين
لأن الخوارزمية التي تقوم بذلك تفترض ذلك فإذا كانتا متقاطعتين فإن
الناتج سيكون يحتوي على منطقة مشوهة مليئة بتكرار للتعامل مع التقاطع نستخدم
memmove
انظر هذا المثال
/* memcpy-test.c: */ #include<stdio.h> #include<string.h> int main() { char s1[]="good bye ........ world"; char s2[]="good bye ........ world"; char s3[]="good bye ........ world"; memcpy(s1+9,s1,8); /* non overlaping */ memcpy(s2+5,s2,8); /* overlaping */ memmove(s3+5,s3,8); /* overlaping */ printf("new s1=%s",s1); printf("new s2=%s",s2); printf("new s3=%s",s3); return 0; }
s1
وهي good bye
إلى الموقع المشار إليه ب s1+9
أي فوق النقاط وهنا لا يوجد منطقة مشتركة لذا سيظهر على الشاشة
good bye good bye world
في الثانية نقل ثمناية بايت من s2
وهي good bye
إلى الموقع المشار له بs2+5
أي فوق بداية bye
وفي هذه الحالة نلاحظ وجود
منطقة تقاطع من 3 بايت ، ولانستطيع أن نتوقع ما سيظهر على الشاشة
فقد يكون المصنف ذكي بما فيه الكفاية ولكن لا تعتمد على ذلك.
في الحالة الثالثة استعملنا الطريقة الصحيحة للتعامل مع مناطق التقاطع
فسينتج good good bye.... world"
لملئ منطقة من الذاكرة بقيمة محددة
نستخدم memset
التي تأخذ
مؤشر على المنطقة ثم القيمة (على شكل unsigned char
) ثم
حجم المنطقة .
-------------- const -------------- M_E : e=e1=exp(1) about 2.718281828459 M_LOG2E : log2e M_LOG10E : log10e M_LN2 : loge2 M_LN10 : loge10 M_PI : acos(-1)=3.14159265358979323846264338327 M_PI_2 : M_PI/2 M_PI_4 : M_PI/4 M_1_PI : 1/M_PI M_2_PI : 2/M_PI M_2_SQRTPI : 2/sqrt(M_PI) M_SQRT2 : sqrt(2) M_1_SQRT2 : 1/sqrt(2)=sqrt(1/2) and add l like M_El M_PIl to have long double -------------- Trigonometric -------------- sin sinf sinl cos,tan *sincos(x,&sinx,&cosx) csin : complex sin=1/(2i) * ( exp(zi)-exp(-zi) ) ccos,ctan -------------- Inverse Trigonometric -------------- asin asinf asinl acos,atan atan2(y,x) : atan(y/x) casin : complex sin inv cacos,catan -------------- Exp & log -------------- exp expf expl : ex exp2 exp2f exp2l : 2x exp10 exp10f exp10l pow10 pow10f pow10l: 10x expm1 expm1f expm1l: (exp(x)-1) but more accurate when x near zero cexp cexpf cexpl : complex exp(z) = exp(creal(z))*( cos(cimg(z))+Isin(cimg(z)) ) pow powf powl : bx cpow: compilx power sqrt sqrtf sqrtl: square root cbrt cbrtf cbrtl: cube root csqrt csqrtf csqrtl: complix square root hypot hypotf hypotl: hypot(a,b)=sqrt(a2+b2) log logf logl : logex called ln(x) in math log10 log10f log10l : log10x log2 log2f log2l : log2x logp1 logp1f logp1l: loge(x+1) more accurate when x near zero clog clogf clogl: complex log = log(cabs(z)+Icarg(z)) *clog10 logb logbf logbl : the exponenet part ie floor (log2x) -------------- Hyperbolic -------------- sinh sinhf sinhl: (exp(x)-exp(-x))/2 cosh coshf coshl: (exp(x)+exp(-x))/2 tansh tanshf tanshl: sinh/cosh csinh : (cexp(x)-cexp(-x))/2 ccosh : (cexp(x)+cexp(-x))/2 ctansh: csinh/ccosh -------------- Inverse Hyperbolic -------------- asinh asinhf asinhl acosh acoshf acoshl atansh atanshf atanshl acsinh accosh actansh -------------- Special Functions (only real part) -------------- erf erff erfl: (2/sqrt(M_PI))*intgeral(e-t2.dt,0,x) erfc erfcf erfcl: 1-erf but more accurate for larg x tgamma tgammaf tgammal: gamma(x)=integral(t(x-1).e-t.dt,0,inf) lgamma lgammaf lgammal: loge(tgamma(x)) and sign(tgamma(x)) saved in signgam j0 j0f j0l: 1st kind Bessel function of order 0 j1 j0f j0l: 1st kind Bessel function of order 1 jn jnf jnl: 1st kind Bessel function of order n y0 y0f y0l: 2nd kind Bessel function of order 0 y1 y0f y0l: 2nd kind Bessel function of order 1 yn ynf ynl: 2nd kind Bessel function of order n
عندما نقول "وقت" أو "زمن" فإننا نقصد هنا الوقت والتاريخ معاً
أو ما يسمى التقويم أو الوقت المطلق ويتم التعبير عنه بطريقة نصية
Wed Feb 18 16:02:05 2004
ولكن كما نعلم التعامل مع النصوص يقلل من أداء البرامج
لذا تستخدم مكتبة سي القياسية النوع time_t
لتمثيل الوقت وهو في جنو رقم يمثل الثواني منذ
تاريخ محدد (منتصف ليل 1/1/1970 حسب التوقيت الدولي) بحيث أنك إذا أردت أن تعرف كم
ثانية بين زمنين يمكنك طرحمها (ولكن هذا قد لا يكون مضموناً في المصنفات الأخرى) وعليك
استخدام الوظيفة difftime
التي تأخذ معاملين هما الزمن التالي ثم الزمن الأول وتعيد الثواني بينهما
لمعرفة الوقت الحالي استدعي time(NULL);
التي تعيد الوقت الحالي على شكل time_t
ويمكن أن تمرر لها مؤشر (تحصل عليه ب عملية "&") لمتغير من نوع time_t
ليضع الوقت الحالي فيه كما يلي
time_t t1,t2; t1=time(NULL); /* method 1 */ time(&t2); /* method 2 */
tm
ويسمى هذا النوع بالوقت المحلل broken-down time
وهو تمثيل للوقت
على شكل عناصر هي الثواني tm_sec
و الدقائق tm_min
والساعات tm_hour
واليوم tm_mday
والشهر tm_mon
والسنة منذ 1900 tm_year
يوم الأسبوع(صفر:الأحد و 2:الإثنين وهكذا) tm_wday
وكم يوم من بداية السنة tm_yday
وكلها تبدأ العد من صفر يعني اليوم العاشر من الشهر الأول
نخزنهما تسمعة و صفر
وتحتوي أيضا علامة تحدد Daylight Saving Time tm_isdst
وتعتبر فعالة إذا كانت موجبة وغير فعالة إذا كانت صفراً وغير معروفة إذا كانت سالبة
(أظن أنها تعني التوقيت الصيفي)
وهناك إضافات مثلاً في جنو حقل يحدد عدد الثواني إلى الشرق من
UTC
أي التوقيت الدولي Universal Time Coordenaite
أو كما يسميه العامة توقيت غرينتش GMT
مثلاً الأردن +2*60*60
يسمى هذا الحقل tm_gmtoff
وآخر يعطي اسم المنطقة الزمنية tm_zone
لتحويل الوقت من time_t
إلى tm
نستخدم gmtime
أو localtime
نمرر لهما مؤشر ل time_t
الذي نريد نحويله
فتعيد مؤشر (لذاكرة داخل المكتبة) من نوع tm
الأولى حسب توقيت غرينتش والثانية حسب التوقيت المحلي للجهاز
(في ويندوز تتحكم بها من لوحة التحكم وملفات ini وأيضا عبارة COUNTRY
في ملف CONFIG.SYS
وفي لينكس
تفعل ذلك متغيرات البيئة ونصوص الإقلاع وطبعاً مركز التحكم )
انظر هذا المثال
/* time-test.c: tells GMT/UTC and local time */ #include<stdio.h> #include<time.h> int main() { tm *tm1; time_t t1=time(NULL); tm1=gmtime(&t1); printf("%02d:%02d:%02d@UTC\n", tm1->tm_hour,tm1->tm_min,tm1->tm_sec); tm1=localtime(&t1); printf("%02d:%02d:%02d@localtime\n", tm1->tm_hour,tm1->tm_min,tm1->tm_sec); return 0; }
tm
إلى نص نستخدم
asctime
التي تأخذ مؤشر إلى الوقت المحلل tm
وتعيد مؤشر لسلسة نصية (تنتهي بسطر جديد) وهي ضمن ذاكرة مكتبة سي
ولتحويل الوقت من time_t
إلى نص نستخدم
ctime
التي تأخذ مؤشر إلى time_t
وتعيد مؤشر لسلسلة نصية وكأن هذه الوظيفة
عبارة عن استدعاء ل localtime
ثم asctime
مثلاً لطباعة الوقت الحالي printf("%s\n",ctime(time(NULL)));
الوظيفتان asctime
و ctime
تنتجان سلسلة نصية بالطريقة القياسية التي تشبه
Wed Feb 18 16:02:05 2004
إذا أردت طريقة أخرى لتنسيق النص استعمل
strftime
التي تأخذ سلسلة نصية لكتابة النص ورقم يمثل الحد الأقصى لطولها
ثم سلسلة تمثل الهيئة التي تريد تنسيق النص بها
بطريقة تشبه طريقة sprintf
ثم أخيراً متغير الوقت المحلل من نوع tm
السلسلة التي تنسق النص تكون الأشياء فيها كما هي عدا علامة
'%'
التي تفسر وفقاً لما بعدها
%a | اسم اليوم من الأسبوع مختصراً (باللغة المحلية) |
%A | اسم اليوم من الأسبوع كاملاً (باللغة المحلية) |
%b | اسم الشهر مختصراً (باللغة المحلية) |
%B | اسم الشهر كاملاً (باللغة المحلية) |
%c | التاريخ والوقت بالتنسيق الذي تحدده الإعدادات المحلية |
%C | القرن أي رقم السنة مع حدف الآحاد والعشرات |
%d | اليوم من الشهر(العد من واحد) |
%D | الطريقة الأمريكية مثل 12/31/04 بكلمات أخرى%m/%d/%y |
%e | اليوم من الشهر(العد من واحد) كما في %d مع وضع مسافة بدل الصفر على اليسار |
%F | طريقة ISO مثل 2004-12-31 بكلمات أخرى%Y-%m-%d |
%H | الساعة بنظام ال24 ساعة |
%I | الساعة بنظام ال12 ساعة |
%m | رقم الشهر (العد من واحد) |
%M | الدقائق |
%p | صباحاً/مساءً مختصرة مثلاً am/pm |
%P | صباحاً/مساءً مختصرة مثلاً AM/PM |
%r | الوقت والتاريخ بنظام 12 ساعة وفقاً للإعدادات المحلية |
%R | الساعة والدقيقية %H:%M |
%S | الثواني |
%T | الوقت دون التاريخ بطريقة %H:%M:%S |
%x | التاريخ وفقاً للإعدادات المحلية |
%X | الوقت من اليوم(دون التاريخ) وفقاً للإعدادات المحلية |
%z | المنطقة الزمنية كرقم |
%Z | المنطقة الزمنية كنص مختصر |
%% | % |
tm
نستعمل strptime
التي تأخذ سلسلة نصية ليتم تحليلها ثم أخرى للتنسيق ثم مؤشر على
وقت محلل من نوع tm
لتغيير الوقت نستخدم stime
التي تأخذ
مؤشر إلى time_t
وتعيد صفر في حال النجاح في ذلك
كما في الأداة kill
من سطر الأوامر
بحيث نستطيع إرسال إشارة معينة لبرنامج معين (في الغالب لإنهاء البرنامج) ،
توفر مكتبة سي الوظيفة kill
التي تأخذ معاملين أولمها
هو معرف العملية pid والآخر رقم الإشارة وهي واحدة من
SIGABRT SIGALRM SIGBUS SIGCHLD SIGCLD SIGCONT SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGLOST SIGPIPE SIGPOLL SIGPROF SIGQUIT SIGSEGV SIGSTKSZ SIGSTOP SIGSYS SIGTERM SIGTRAP SIGTSTP SIGTTIN SIGTTOU SIGURG SIGUSR1 SIGUSR2 SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ
signal.h
يمكن أن يرسل البرنامج إشارة لنفسه ب raise
التي تأخذ معامل واحد هو الإشارة.
بعض هذه الإشارات مثل SIGKILL
تنهي البرنامج بحث لا يملك البرنامج
منعها أو إهمالها أو القيام بأي شيء ولكن بعض الإشارات مثل SIGTERM
يمكن تغيير السلوك التلقائي لها (مثلاً شطب ملف مؤقت قبل لخروج) ،
بعض هذه الإشارات يمكن ضبطها على موعد معين
بالوظيفة alarm
التي تأخذ معامل واحد هو عدد الثواني
فهذه الوظيفة ترسل إشارة SIGALRM
بعد إنقضاء
تلك الثواني.
بعض هذه الإشارات تحديداً SIGUSR1
و SIGUSR2
وضعت لتستخدمها أنت كيفما شئت للتخاطب بين برامجك.
ليتمكن البرنامج من التعامل مع الإشارات التي يستلمها بطريقة مختلفة عن السلوك التلقائي بحيث تعرف وظيفة تأخذ معامل هو رقم الإشارة ولا تعيد شيء مثلاً
void my_handler(int signum) { printf ("Someone send me signal %d\n",signum); }
signal
المعامل الأول هو الإشارة
والثاني هو الوظيفة. تعيد هذه الوظيفة في حال النجاح مؤشر على
الوظيفة التي كانت سابقاً. يمكنك استعمال الوظيفتين التاليتين
SIG_DFL
و SIG_IGN
للسلوك التلقائي و لإهمال الإشارة على الترتيب.
أشهر الوظائف التي قد تفكر في تغيير سلوكها
SIGALRM
تنبيه ناتج عن توقيت
SIGTERM
الإشارة الشهيرة لإنهاء برنامج
SIGINT
المقاطعة الناتجة من ضغط CTRL+C
SIGTSTP
التوقيف الناتجة من ضغط CTRL+Z
SIGHUP
فصل الإتصال
CTRL+C
يمكنك عمل
signal(SIGINT,SIG_IGN);
نقصد بالعشوائية تلك التي تظهر كذلك ولكنها في الحقيقة سلسلة رياضية؛
لهذا تسمى في جنو pseudo random numbers ، توفر مكتبة سي القياسية
وظائف خاصة بذلك وهي معرفة في stdlib.h
كلما تريد رقم عشوائي استدع rand
لكي يعطيك رقم غير سالب بين الصفر و RAND_MAX
الذي هو 2147483647 أي الجزء غير السالب من العدد الصحيح int ولكنه في
الأنظمة من طراز 16 بت يكون 32767 ، بتنفيذ هذه الوظيفة
تحصل على سلسلة من الأرقام تبدو عشوائية
وعند تنفيذ البرنامج مرة أخرى تحصل على نفس السلسلة
دائماً ولكن هذا في الغالب ليس هو المطلوب بل المطلوب
هو الحصول على سلسلة مختلفة في كل مرة
لهذا نستخدم ما يسمى بذور seeds
وهي رقم يحدد
الرقم العشوائي التالي ونحدده باستدعاء srand
في بداية البرنامج
وتمرير رقم لها وبتمرير رقم مختلف في كل مرة
نحص على سلسلة مختلفة من الأرقام ولكن من أين نحصل على رقم مختلففي كل مرة؟
الجواب هو باستدعاء time(NULL)
التي تعيد "الوقت" الحالي (عدد صحيح يمثل الثواني منذ 1970) وطبعا
يكون استدعاء srand(time(NULL))
مرة واحدة في بداية
البرنامج ، ثم استدعائات متتالية ل rand
ولكي تحصل على رقم يتراوح بين a و b
يمكنك استعمال عملية باقي القسمة التي تعيد رقم
أكبر أو يساوي الصفر وأقل من الرقم المقسوم عليه ثم جمع رقم البداية أي
int r=(rand()%(b-a+1))+a
مثلاً للحصول على رقم 1-6
استدع (rand()%6)+1
هناك طريقة أخرى للحصول على الأرقام العشوائية
وهي باستدعاء drand48()
التي تعيد عدد نسبي بين الصفر
و الواحد (الفترة مفتوحة) وللحصول على فترة أخرى اضرب
في طول الفترة واجمع بدايتها ونستعمل srand48
وتمرير عدد
صحيح له لعمل لسلة مختلفة في كل مرة كما في srand
هناك طريقتان للتعامل مع الملفات هما سيال البيانات Streams
وواصف الملف
File Descriptor
الأولى هي طريقة أسهل وأكثر مرونة
والأخرى طريقة ذات مستوى منخفض ومباشرة وفي الغالب
تكون مفضلة على الأولى عند التعامل مع ملفات غير عادية (مثل الأنابيب والأجهزة)
ولكن الأولى يمكنها التعامل مع الملفات
بطريقة منسقة أكثر. للتعامل مع الملف في الطريقتين
نقوم أولاً بإخبار المكتبة وبالتالي نظام التشغيل بأننا نريد
التعامل مع الملف الفلاني وبالطريقة الفلانية(قراءة فقط أو قراءة وكتابة .. إلخ)
فيرد بالرفض لأن الملف غير موجود أو لأننا لا نملك الحق في الملف الفلاني
،فنقوم بمعاجة هذه الحالة (بالخروج أو بإعادة المحاولة ) ،
أو يرد بأن ذلك ممكن ويعيد لنا متغير من نوع معين
ليمثل الملف وحالته بدل التعامل من خلال اسمه على شكل سلسلة فيما بعد،
تسمى هذه العملية فتح الملف .
وثم نستخدم هذا المتغير للكتابة في الملف أو القراءة منه
وعند الإنتهاء نقوم بإغلاق الملف ومعنى ذلك
هو انهاء العمليات المطلوبة التي كانت مؤجلة وتحرير كل المصادر التي حجزت
للقيام بها فهناك حد معين من الملفات التي يمكن
فتحها في وقت واحد ونعمل ذلك لتجنب الوصول لهذا الحد.
في الطريقة الأولى وهي سيال البيانات Streams
حيث جاء المصطلح من تخيل الملف وكأنه
تيار من البيانات تستطيع أن تأخذ منه أي تقرأ Read
أو تطرح فيه أي تكتب Write
ويمكن لهذه الكتابة أن تكون منسقة أي اكتب رقم بعرض كذا وبه كذا منزلة عشرية
ويمكن أن تكون مباشرة مثل انقل كذا بايت مما هو موجود في العنوان
التالي من الذاكرة وكذلك القراءة
لفتح ملف بهذا الأسلوب نستخدم الوظائف المعرفة في stdio.h
وأولها fopen
التي تأخذ معاملين أولهما سلسلة نصية تمثل اسم الملف
وثانيهما سلسلة نصية تمثل طور فتح الملف(للقراءة/للكتابة)
كما يلي
السلسلة التي تمثل الطور | معناها |
---|---|
r | فتح ملف موجود أصلاً للقراءة |
w | إنشاء ملف جديد أو مسح القديم والكتابة فوقه |
a | فتح ملف للإضافة append إلى آخره أو انشاؤه إن لم يكن موجود |
r+ | فتح ملف موجود أصلاً للقراءة والكتابة |
w+ | فتح للقراءة والكتابة وإنشائه إن لم يكن موجود |
a+ | فتح للقراءة والإضافة وإنشائه إن لم يكن موجود القراءة من أي مكان والإضافة إلى النهاية |
هناك علامة أخرى للطور هي إما t
أو b
الأولى هي للملفات النصية text والثانية للثنائية binary
وفي الحقيقة في أنظمة يونكس لا معنى لها وتهمل
فكلها ملفات، ولكنها مهمة بالنسبة لنظام دوس ووريث أخطاؤه ويندوز
فهناك خطأ تاريخي في طريقة تعاملهما مع السطر الجديد
حيث يكون السطر الجديد في الملف النصي "\n\r"
ولكنه يحمل في الذاكرة "\n"
تخيل الآلة الكاتبة(وليس الطابعة) عندما تضغط إدخال
فإن عربة الكتابة تنزل سطر جديد line feed(LF)
أي "\n"
ثم تعود العربة لبداية السطر courage return(CR)
اي "\r"
ففيهما لا معنى لسطر جديد دون عودة
لهذا إذا فتحت ملف ثنائي(ملف صورة أو تنفيذي) في الطور النصي
فإن كل رموز "\n\r"
تصبح "\n"
ولكن هذه الرموز في هكذا ملفات لا تمثل سطر جديد ولا عودة
بل ربما تعليمة للمعالج أو نقطة أو لون مما يسبب مشاكل.
في يونكس نحن نتجنب هذا الصداع فالملف ليس آلة كاتبة
السطر الجديد "\n"
هو سطر جديد
وعند عرضه على طابعة أو شاشة ينزل سطر ويعود للبداية
أما "\r"
فهي تعيد المؤشر
لبداية السطر الحالي من أجل غايات المسح والكتابة فوقه.
بكلمات أخرى أنه في الحالتين لا يقوم بإضاعة وقته في عمليات تحويل
على أي حال يمكنك أن تضع العلامة t
أو b
في أي مكان
من نص الطور مثلاً wt+
أو r+b
FILE
بحروف كبيرة
هذا مثال
FILE *myfile; myfile=fopen("readme.txt","r");
fclose
وتمرير متغير الملف مثلاً fclose(myfile)
لقراءة بايت من الملف fgetc
ولكتابة بايت
fputc
في الملف ولكتابة سلسلة نصية fputs
ولقراءة سلسلة نصية(سطر كامل) fgets
وهذه الأخيرة تأخذ مؤشر على السلسلة والحد الأقصى لطولها والملف
ولكن هذه تعمل من الموقع الحالي وهي غير منسقة
لاستعمال التنسيق نستعمل fprintf
وربما تكون قد خمنت
أنها تأخذ معاملاً أضافيا(يكون المعامل الأول هو الملف) والباقي نفس
printf
التقليدية ولكن بدل من الظهور على الشاشة
تكتب في الملف
مثلاً fprintf(myfile,"You have $%.2f in your credit.\n",c);
وللقراءة نستعمل fscanf
التي طبعا ولا داعي للذكر
لا تختلف عن scanf
إلا أن الأولى تقرأ من الموقع الحالي
من الملف بدلاً من جهاز الدخل القياسي
وهي تأخذ كما هو متوقع الملف والتنسيق ومؤشرات على المتغيرات
مثلاً fscanf(myfile,"%d",&i);
من الشائع استخدام fflush
مع تمرير متغير الملف
إليها لتقوم باهمال بقية الدخل مثلاً لنفرض أنك
استعملت scanf("%d",&i);
لقراءة قيمة i (من لوحة المفاتيح)
فقام المستخدم بإدخال رقمين عندها سيمرر الرقم الآخر
إلى الاستدعاء التالي لوظائف القراءة مثل scanf
وهذا جيد حيث يمكنك سؤال المستخدم ليدخل رقمين
ب scanf("%d %d",&i,&j);
أو
scanf("%d",&i); scanf("%d",&j);
ولكن أحياناً تريد اهمال كل باقي الدخل مثلاً في برنامج حساس يسأل "هل أنت متأكد؟" فضغط المستخدم y مرتين
بالخطأ هنا لأن المسألة حساسة لا نريد من "y" الثانية أن تحسب لجواب السؤال التالي
فنستعمل fflush
في حالتنا الملف هو جهاز الدخل القياسي
نستدع fflush(stdin);
هناك ثلاث ملفات مفتوحة دائماً هي stdin
و stdout
و stderr
أي الدخل القياسي والخرج القياسي والخطأ القياسي
وهي متغيرات من نوع مؤشرات إلى FILE
مثلاً يمكنك كتابة fgetc(stdin)
و fprintf(stderr,"Error")
هذه نماذج الوظائف المذكورة
FILE* fopen (const char* FileName, const char* Mode); int fflush (FILE* file); int fclose (FILE* file); FILE* tmpfile (); int fprintf (FILE* file, const char* Format, ...); int printf (const char* Format, ...); int sprintf (char* str, const char* Format, ...); int snprintf (char* caBuffer, size_t n, const char* Format, ...); int vprintf (const char* Format, va_list varg); int vsprintf (char* str, const char* Format, va_list varg); int vsnprintf (char* str, size_t n, const char* Format, va_list varg); int fscanf (FILE* file, const char* Format, ...); int scanf (const char* Format, ...); int sscanf (const char* str, const char* Format, ...); int fgetc (FILE* file); char* fgets (char* str, int Size, FILE* file); int fputc (int c, FILE* file); int fputs (const char* str, FILE* file); int ungetc (int c, FILE* file);
أحياناً كما قلنا تريد حمل كذا بايت ووضعها في العنوان الفلاني عندها نستعمل
fread
التي تأخذ مؤشر على الذاكرة
حجم الوحدة بالبايت وعدد الوحدات ثم متغير الملف
وتعيد الوظيفة عدد الوحدات التي تم قرأتها فعلاً
فإذا طلبت منه أن يقرأ عشر وحدات وأعاد خمسة هذا قد يعني
أنه وصل إلى نهاية الملف ولم يعد هناك أكثر من 5 وحدات أو
حدوث خطأ (عطب) في وسيط التخزين
يمكنك أن تفحص الخطأ باستعمال ferror
وتمرير متغير الملف له
و تعلمه أنك عملت الخطأ ب clearerr
.
عند قراءة جزء من الملف بأي طريقة فإن مؤشر الموقع يتحرك للأمام
بمقدار القراءة فتكون القراءة التالية للوحدة التي تليه
ولمعرفة أنك وصلت إلى آخر الملف وقرأة
كل ما به استدع feof
مع تمرير متغير الملف لها وتعيد قيمة تكون صحيحة عند الوصول إلى نهاية الملف
يمكن استعمالها لقراءة ومعالجة الملف بأكمله
#define DO_AT_ONCE 0xFFFFul FILE *f1=fopen("my_file","rb"); char *block=(char *)malloc(0xFFFFul); size_t n; if (!f1||!block) exit(errno); printf("reading 1st block ...\n"); while( !feof(f1) ) { n=fread(block,1,DO_AT_ONCE,f1) printf("\tprocessing it (%d bytes)\n",n); my_process(n,block); printf("reading next block ...\n"); } fclose(f1); printf("done\n");
fread
#define DO_AT_ONCE 0xFFFFul FILE *f1=fopen("my_file","rb"); char *block=(char *)malloc(0xFFFFul); size_t n; if (!f1||!block) exit(errno); printf("reading 1st block ...\n"); while( (n=fread(block,1,DO_AT_ONCE,f1))!=0 ) { printf("\tprocessing it (%d bytes)\n",n); my_process(n,block); printf("reading next block ...\n"); } fclose(f1); printf("done\n");
fwrite
التي لا تختلف صيغتها عن fread
ويمكن القراءة أو الكتابة من أي موقع فليس بالضرورة أن يكون الموقع
التالية لأنه من الممكن القفز إلى أي موقع من الملف والعمل فيه
باستدعاء fseek
أو fseeko
الأخيرة ليست جزء من معايير ANSI ولكنها من معايير POSIX
والفرق أن الأخيرة تستعمل متغير من نوع off_t
وليس int
لتمثيل الإزاحة لمزيد من الموثوقية لأن الأنظمة تختلف فيما بينها
في قدرتها. نمرر لهما متغير الملف ثم الإزاحة ثم نحدد من
أين تكون هذه الإزاحة: بداية الملف SEEK_SET
أو
الموقع الحالي SEEK_CUR
أو
الإزاحة عن ما قبل نهاية الملف SEEK_END
ولمعرفة الموقع الحالي أي الإزاحة عن بداية الملف
نستدع ftell
أو ftello
مع تمرير الملف لها. ويجب أن أذكر بأنه يفترض عدم
استدعاء هذه الوظائف في الملفات التي تفتح على أنها نصية
في بعض أنظمة التشغيل (تعرفون ..) انظر التحذير السابق
ولكنها ستنجح في أنظمة جنو. إذا كنت تريد من برنامج العمل
في أنظمة غير جنو وتصر على فتح الملف نصياً هناك
وظائف تصلح وهي fsetpos
و fgetpos
ولكن الأسهل هو فتح الملف في الطور الثنائي.
نماذج الوظائف المذكورة هي
size_t fread (void* Array, size_t ObjectSize, size_t Count,FILE* file); size_t fwrite (const void* Array, size_t ObjectSize, size_t Count,FILE* file); int fseek (FILE* file, long Offset, int from); long ftell (FILE* file); void rewind (FILE* file); int feof (FILE* file); int ferror (FILE* file); void clearerr (FILE* file);
إن الوظائف التي شرحناها سابقاً تصلح لملفات لا يزيد حجمها عن 2 أو 4 غيغا بايت
وللعمل بالملفات الكبيرة LFS
أي Larg File Systems
هناك طريقتين الأولى هي استعمال الوظائف الخاصة بالملفات الكبيرة
وهي نفس أسماء الوظائف السابقة عدا أنها تنتهي ب 64
مثلاً بديل fopen
هو fopen64
وهكذا . أما الطريقة الثانية وهي المفضلة
استعمال الوظائف العادية مع تحديد
_FILE_OFFSET_BITS
لتصبح قيمتها 64
عند تصنيف البرنامج بمصنفات جنو كما يلي
gcc -d_FILE_OFFSET_BITS=64 myfile.c -o myfile
قد يصل الحد الأقصى لحجم الملف 2^63=9x1018
خصوصاً عند استعمال الطريقة الأولية اللاحقة
تحت الطرق السابقة المرنة ذات المستوى الراقي
توجد طريقة أولية تسمى File Descriptor
وهو عبارة عن رقم فريد تخصصه النواة ليصف الملف معين
الوظائف الخاصة به معرفة في ملف unistd.h
و fnctl.h
وإذا لم يتوفرا ربما تجد io.h
لفتح ملف نستدع open
ونمرر لها اسم
الملف وبعض العلامات لتحديد طور فتح الملف ويمكن إضافة
معامل ثالث اختياري هو التراخيص في حالة إنشاء ملف جديد
مثل 0755
الصفر للثماني
وتعيد الوظيفة رقم صحيح غير سالب في حال النجاح يسمى واصف الملف
File Descriptor
وفي حال الفشل سالب
هو في الغالب سالب واحد ويتم تحديد errno
أما علامات الطور فهي واحدة أو أكثر من التالية ( ضع | بينها)
O_RDONLY | السماح بالقراءة |
O_WRONLY | السماح بالكتابة |
O_RDWR | السماح بالقراءة والكتابة |
O_CREATE | إنشاء الملف إن لم يكن موجوداً |
O_EXCL | فتح ملف موجود مسبقاً |
O_TRUNC | حذف كل بيانات الملف ليصبح حجمه صفر يفضل استعمال ftruncate |
O_APPEND | مؤشر الكتابة يشير إلى نهاية الملف |
O_TEMPORARY | يتم حذف الملف عند إغلاقه |
O_NONBLOCK | عدم الإنتظار حتى تتم العملية (مثلاً عند فتح ملف جهاز) |
O_FSYNC | علامة التزامن؛ عن وظائف الكتابة لا تعود حتى تكتب البيانات على القرص |
O_NOLINK | فتح الوصلة وليس ما تشير إليه |
O_NOTRANS | فتح الملف مع تخطي مترجم الملف أي كما يراه المترجم |
O_NONBLOCK
عمليات الكتابة والقراءة
تعتبر فاشلة إذا لم تتم مباشرة ،
يمكن تغيير بعض هذه العلامات باستدعاء fcntl
(وهي تشبه ioctl
الخاصة بنواة النظام ولكن fcntl
جزء من مكتبة سي القياسية)
مثلاً fcntl(fd,F_SETFL,my_new_flag);
ويمكن معرفة العلامات الحالية ب
fcntl(fd,F_GETFL,0);
وتعيد الوظيفة قيمة غير سالبة
في حال النجاح حيث fd هو الرقم الذي يصف الملف،
هاتان الوظيفتان واحدة تضع العلامة O_NONBLOCK
وأخرى تزيلها
int set_nonblock_flag (int desc) { int oldflags = fcntl (desc, F_GETFL, 0); /* If reading the flags failed, return error indication now. */ if (oldflags == -1) return -1; oldflags |= O_NONBLOCK; return fcntl (desc, F_SETFL, oldflags); } int unset_nonblock_flag (int desc) { int oldflags = fcntl (desc, F_GETFL, 0); /* If reading the flags failed, return error indication now. */ if (oldflags == -1) return -1; oldflags &= ~O_NONBLOCK; /* Store modified flag word in the descriptor. */ return fcntl (desc, F_SETFL, oldflags); }
fcntl
لوضع أقفال على قراءة أو كتابة الملف
مثلاً قفل الكتابة تحدد فيه أنك تريد الكتابة
ويجب أن لا تقرأ البرامج من الملف لأنه سيتغير
أما قفل القراءة فهو يحدد بأن البرامج الأخرى يفترض
أن لا تكتب في الملف حتى تتمكن الأولى من قراءته ولكن
هذه أقفال غير إلزامية والبرامج ليست مجبرة على
احترمها (مثل قوانين ال UN) لهذا لن أتحدث عنها أكثر.
بعد فتح الملف نستعمل read
وتمرير رقم وصف الملف
ومؤشر على منطقة من الذاكرة والحجم
وتعيد read
رقم يمثل المقدار الفعلي الذي
تم قراءته . ونستعمل write
وتمرير رقم وصف الملف ومؤشر
على منطقة من الذاكرة والحجم وتعيد المقدار الفعلي الذي تم كتابته
ونستعمل lseek
التي لها نفس صيغة
fseek
غير أن معاملها الأول هو رقم وصف الملف
لتحويل ملف مفتوح بطريقة stream
إلى File Descriptor نستدع
fileno
ونمرر لها مؤشر لمتغير الملف
فتعيد رقم وصف الملف
وللقيام بالعملية العكسية نستعمل fdopen
التي تأخذ معاملين هما رقم وصف الملف و سلسلة نصية تمثل الطور بنفس طريقة fopen
وتعيد مؤشر لمتغير الملف من نوع FILE
من الوظائف الشائعة الإستخدام هي نسخ واصف ملف بواصف جديد
في عملية تسمى مضاعفة/نسخ duplicate
حيث
يحل يمكن استعمال أي منها للقيام بعمليات القراءة والكتابة
باستدعاء dub(oldfd)
حيث تعيد رقم واصف ملف جديد
وإذا كنت تريد تحديد الواصف الجديد أيضاً
dub2(oldfd,newfd)
حيث تغلق الجديد ويحل مكانه القديم
وقد تبدو هذه العملية بلا فائدة
ولكن فائدتها تكم في استبدال الدخل القياسي (الرقم الواصف له
STDIN_FILENO
) بأي ملف آخر
/* redirect.c: 'echo "hello" > readme.txt' */ #include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() { int fd; fd=open("readme.txt",O_WRONLY|O_CREATE); if (fd==-1) exit(1); dup2(fd,STDOUT_FILENO); printf("hello\n"); close(fd); return 0; }
في الحالات العادية عند القراءة من ملفات مفتوحة دون العلامة
O_NONBLOCK
فإن عملية القراءة تنتظر توفر المدخلات من الأجهزة
(أو توفر علامة بالفشل)
مثلاً عند القراءة من الدخل القياسي فإن البرنامج يتوقف
حتى يدخل المستخدم عبر لوحة المفاتيح الكمية المطلوبة
ولكن في حالة O_NONBLOCK
فإن عمليات
القراءة يجب أن تكون بعد توفر المدخلات وإلا فإنها تفشل.
حالة أخرى؛ لنفرض أنك فتحت أكثر من ملف دون O_NONBLOCK
وتريد
القراءة من أي واحد منها تتوفر منه مدخلات فإن محاولة
القراءة من ملف لم تتوفر مدخلات منه قبل آخر متوفرة
فإن المحاولة ستظل معلقة حتى يتوفر مدخلات من الأول
وتظل المدخلات القادمة من الثاني تنتظر.
لحل هاتان المشكلتان نستعمل الوظيفة select
(وأيضاً لها فوائد أخرى في الشبكات كما سنرى)
هذه الوظيفة تأخذ عدد من الواصفات fd
على شكل مجموعة التي تريد إنتظار جاهزيتها
خلال فترة من الزمن.
هذه المجموعة من نوع fd_set
مرر مؤشر على المجموعة إلى FD_ZERO
لجعلها مجموعة خالية استهلالاً.
ثم أضف الواصفات fd
التي تريد إليها باستدعاء
FD_SET
المعامل الأول هو الواصف fd
والثاني هو مؤشر على المجموعة.
نموذج الوظيفة select
هو كما يلي:
int select (int N, fd_set *r-fds, fd_set *w-fds, fd_set *except-fds, struct timeval *timeout);
FD_SETSIZE
وهو أكبر عدد مسموح به.
أما r-fds
و w-fds
هو مجموعة الواصفات
التي تريد إنتظار جاهزيتها للقراءة والكتابة (على الترتيب) ،
أما except-fds
فهي مجموعة الواصفات التي تريد فحص حالات استثائية.
يمكنك وضع NULL
مكان أي منها لإهماله.
و timeout
هو الفترة الزمنية التي يفترض أن ينتظرها كحد أقصى لحدوث الجاهزية
يمكنك وضع NULL
لزمن غير محدود.
تعيد الوظيفة عدد الواصفات الجاهزة. تقوم الوظيفة بتعديل
المجموعة لتحتوي الواصفات الجاهزة فإذا كنت تريد الأصلية
اعمل منها نسخة ومرر النسخة.
يمكنك أن تعرف إن كان الواصف الفلاني في المجموعة
أم لا نستعمل FD_ISSET
أول معامل لها هو الواصف والثاني
مؤشر على مجموعة.
هذا مثال يوضح استخدام تقليدي لهذه الوظيفة.
حيث fd1
و fd2
واصفات الملفات التي نريد انتظارها للقراءة
fd_set orig_set, temp_set; FD_ZERO (orig_set); FD_SET (fd1, orig_set); FD_SET (fd2, orig_set); temp_set=orig_set; if (select (FD_SETSIZE, temp_set, NULL, NULL, NULL)<0) exit(1); for (i = 0; i < FD_SETSIZE; ++i) { if (FD_ISSET (i, &temp_set)) { printf("we have some thing on fd=%d\n",i); read(i,&c,1); } }
التعابير العادية هي من أقوى الطرق المستخدمة للتعامل مع النصوص
بحثاً وإبدالاً ومبدأ عمل المصنفات تقوم عليها
لهذا ليس غريباً أن تسمى عملية توليدها من النصوص تصنيفاً compiling
،
نعم فالتعابير العادية لا تخزن كنصوص يتم إعرابها وتحليلها في كل مرة
بل تخزن على شكل تركيب من نوع regex_t
الوظيفة التي تحول النص إلى هذا النوع هي regcomp
ونمرر لها مؤشر على تركيب التعبير regex_t
ثم سلسلة نصية ثم علامات تحدد بعض الخيارات وتعيد الوظيفة صفر في حال النجاح
وهي موجودة في regex.h
العلامات هي (استعمل "|" للجمع بين أكثر من واحدة)
REG_EXTENDED | قبول التعابير الإضافية |
REG_ICASE | إهمال حالة الحروف (الإنجليزية) إن كبيرة أو صغيرة |
REG_NOSUB | عدم توليد السلاسل الفرعية(تلك المحصورة بين أقواس هلالية) |
REG_NEWLINE | معاملة السطر الجديد"\n" كحالة خاصة |
regex_t rg1; regcomp(&rg1,"^hd[a-d][0-9]\\{0,3\\}",REG_EXTENDED);
regexec
أبسط المعاملات هي بأن تأخذ مؤشر على التعبير المصنف أي regex_t *
ومؤشر على النص المراد مطابقته ثم 0 ثم NULL
ثم بعض
العلامات هي REG_NOTBOL
أي لا تفترض أن بداية النص هي بداية سطر
أي أن هذا النص قد يكون مجتزأ أو العلامة REG_NOTEOL
أي لا تفترض أن نهاية النص هي نهاية سطر لأنه قد يكون مجتزأ
أو ضع صفر
regex_t rg1; char *pat1="^hd[a-d][0-9]\\{0,3\\}",*str1="hdc12"; regcomp(&rg1,pat1,REG_EXTENDED); if (regexec(&rg1,str1,0,NULL,0)==0) { printf("%s Matches %s",str1,pat1); } else { printf("No Match"); } regfree(&rg1);
regmatch_t
حيث يمكننا أن نعرف أي حصلت المطابقة
الذي عنصره الأول وهو rm_so
يمثل إزاحة بداية
المطابفة و rm_eo
الذي هو إزاحة نهاية المطابقة
ولمعرفة تلك المعلومات احجز منظومة من نوع regmatch_t
وعدد عناصرها يزيد بواحد عن الحد الأعلى للتعابير الفرعية التي تريد
وتمريرها مكان NULL وتمرير ذلك العدد مكان الصفر في regexec
كما يلي
عندما تعود يكون العنصر الأول من المنظومة (رقم صفر) يمثل
المطابقة الكلية أما التاليات فتكون للفرعايات
regex_t rg1; char *pat1="c(o\\{2,\\})l",*str1="This is a coooler!"; regmatch_t rm[2]; regcomp(&rg1,pat1,REG_EXTENDED|REG_ICASE); if (regexec(&rg1,str1,2,rm1,0)==0) { printf("'%s' Matches %s",str1,pat1); printf("match happen from %d to %d",rm[0].rm_so,rm[0].rm_eo); printf("the submatch from %d to %d",rm[1].rm_so,rm[1].rm_eo); } else { printf("No Match"); } regfree(&rg1);
regfree
لتحرير الذاكرة التي تحجزها regcomp
الكثير من الوظائف أدناه موجودة حصراً على أنظمة يونكس ، وربما لن تجدها في المصنفات غير القياسية مثل (...) تعرفون من أقصد. هذا على الرغم من أنها ووظائف قياسية ضمن معايير POSIX.
تتم ترجمة البرامج المفتوحة المصدر
باستعمال gettext
وهي أداة ووظيفة ومكتبة من جنو
حتى تستفيد منها يجب أن تستعمل #include<libintl.h>
عليك تحويل كل النصوص في برنامج باستدعاءات ل gettext
وتمرير النص المراد ترجمته سيكون هذا النص هو مفتاح الترجمة
وتستخدم جنو طرق تحسين تجعل من هذا المفتاح النصي
بسرعة المفتاح الرقمي إذ أن ملف الترجمة يكون مع بطريقة
توفر البحث فيه . هذا مثال لبرنامج بسيط
/* hello-i18n.c: International "Hello, world!" */ #include<stdio.h> #include<libintl.h> int main() { setlocale (LC_ALL,""); /* change from LC_ALL=C to default locale*/ textdomain("hello-i18n"); /* load file hello-i18n.mo */ /* you may also use /usr/local/share/locale */ bindtextdomain ("test-package","/usr/share/locale"); printf("%s\n",gettext("Hello, world!")); return 0; }
/usr/share/locale/XY/LC_MESSAGES/hello-i18n.mo
حيث XY
هي اللغة التي سيترجم لها
وهذا الملف يتم توليده من ملف hello-i18n.po
وهذا الأخير ملف نصي يحتوي الأصل والترجمة كما يلي
# hello-i18n.po: translation for hello-i18n.c # this is a comment msgid "Hello, world!" msgstr "مرحباً، يا عالم!"
.mo
نستعمل الأداة
msgfmt
كما يلي
msgfmt hello-i18n.po -o hello-i18n.mo
ثم انقل هذا الملف إلى المكان المناسب
ويمكن كتابة ملف po يحتوي النص المراد ترجمته
بأي محرر نصوص يدوياً أو توليده تلقائياً من ملف السي
بواسطة الأداة xgettext
لمعرفة الدليل الحالي نستدع getcwd
مع تلرير مؤشر لموقع من الذاكرة
والحد الأقصى لطوله إذا لم تكن تريد التحديد استدع getcwd(NULL,0)
التي تحجز ذاكرة ب malloc
وتخزن فيها الدليل الحالي
وعليك تحرير الذاكرة بنفسك.
تعيد هذه الوظيفة مؤشر على اسم الدليل الحالي أو NULL
.
يقابل هذه الوظيفة chdir
التي تقوم بتغير الدليل الحالي
إلى الدليل الذي تمرره له تعيد هذه الوظيفة صفر في حال
النجاح وسالب في حال الفشل.
لمعرفة محتويات دليل ربما تفضل استعمال glib لأنها توفر طيف أكبر من الأنظمة.
على أي حال، توفر مكتبة سي القياسية هذه الوظائف
باستعمال opendir
مع تمرير اسم الدليل الذي تريد معرفة
محتوياته تعيد هذه الوظيفة مؤشر الدليل DIR *
أو NULL
من الطرق الشائعة لاستدعاء هذه الوظيفة
opendir("./")
لمعرفة محتويات الدليل الحالي.
لمعرفة الملفات واحداً تلو واحد نستدع
readdir
مرة بعد أخرة،نمرر مؤشر الدليل له
تعيد هذه الوظيفة مؤشر من نوع عنصر من الدليل dirent
أو NULL إذا تم قراءة كل شيء ، المؤشر الذي تعيده
تحجزه الوظيفة وهي مسؤولة عن تحريره استدعاؤها مرة
أخرى لنفس مؤشر الدليل قد يكتب فوق المؤشر السابق.
يتحوي التركيب dirent
الذي تعيده هذه
الوظيفة المعلومات الأساسية عن الملفات مثل
d_name
التي هي اسم الملف ومنها أيضاً
d_type
التي تحدد نوع الملف (عادي أو دليل أو جهاز)
لإعادة قراءة الملفات من البداية نستعمل rewinddir
مع تمرير مؤشر الدليل. هذه الوظيفة لا تعيد شيء.
عند الإنتهاء تقوم بإغلاق مؤشر الدليل ب closedir
مع
تمرير المؤشر له. هذا مثال يقوم بسرد محتويات الدليل الحالي
#include<stdio.h> #include<dirent.h> #incltde<unistd.h> int main() { DIR *dp; dirent *entry; if (!dp=opendir("./")) { perror("Could not open director."); return 1; } while(entry=readdir(dp)) { printf("./%s%s\n",entry->d_name, (entry->d_type==D_DIR)?"/":NULL); } closedir(dp); return 0; }
يمكن للبرامج استدعاء برامج أخرى
في هذه الحالة نسمي البرنامج الذي تم استدعاؤه بالبرنامج الإبن
والذي استدعاه الأب مكونين شجرة كبيرة مثلاً
عند تشغيل الجهاز تعمل النواة على استدعاء برنامج واحد هو init
وباقي البرامج تكون أبناء وأحفاد له
يمكن للبرنامج الأب أن ينتظر انتهاء البرنامج
الإبن أو متابعة العمل ويمكنه كذلك التواصل معه.
قبل استدعاء برنامج عليك أولاً التفريع fork
وذلك باستدعاء وظيفة بهذا الاسم دون معاملات
تعيد قيمة سالبة (سالب واحد) في حالة الفشل،
وفي حالة النجاح تقوم هذه الوظيفة بعمل نسخة من البرنامج
الحالي على أنها إبنة البرنامج الذي طلب التفريع
وتعيد الرقم المعرف للإبن PID
في النسخة الأب من البرنامج
وتعيد صفر في النسخة البنت من البرنامج
وتتابعا (أي النسختين) العمل من المنطقة التي تلي fork
.
/* self-fork.c: a dummy fork */ #include<stdio.h> #include<unistd.h> int main() { pid_t p=fork(); switch(p) { case 0: printf("I'm the child process\n"); printf("My PID is %d, My parent PID is %d\n",getpid(),getppid()); break; case -1: perror("Can't fork"); exit(0); default: printf("I'm the parent process\n"); printf("My child PID is %d \n",p); printf("wait it to end\n",p); waitpid(p,NULL,0); printf("the child ended\n",p); } }
exec
وأخواتها
تختلف في ما بينها في طريقة تمرير المعاملات
أو البحث عن البرنامج المراد تنفيذه
execv | تمرر المعاملات على شكل منظومة |
execl | تكون معاملات الوظيفة الإضافية هي معاملات البرنامج |
execve | تمرير متغيرات البيئة إضافة إلى execv |
execle | تمرير متغيرات البيئة قبل المعاملات كما في execl |
execvp | كما فيexecv ولكن يتم البحث عن البرنامج في المسارات PATH |
execlp | كما فيexecl ولكن يتم البحث عن البرنامج في المسارات PATH |
execl
وأخواتها التي تأخذ المعاملات من معاملات الوظيفة
يجب لأن تنتهي ب NULL
/* run-ls.c: a program calling 'ls' */ #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<unistd.h> int main() { pid_t p=fork(); int status; switch(p) { case 0: printf("I'm the child process\nI'm going to call 'ls'\n"); execlp("ls","ls","-F",NULL); /* running 'ls -F' */ perror("Couldn't run 'ls -F'"); exit(errno); case -1: perror("Can't fork"); exit(0); default: printf("I'm the parent process\n"); printf("My child PID is %d \n",p); printf("wait it to end\n"); waitpid(p,&status,0); printf("the child ended with value %d\n",status); } }
system
المعرفة في stdlib.h
والتي يمكن استعمالها في معظم أنظمة التشغيل
تأخذ هذه الوظيفة سلسلة نصية(البرنامج ومعاملاته) وتنفذها وكأنها طبعت في محث الأوامر sh
(في ويندوز و دوس وكأنها كتبت في command.com محث دوس)
وتقوم هذه الوظيفة بالتشعيب والبحث عن البرامج المطلوب وتنفيذه وانتظار انتهاؤه
ولكن الوظائف السابقة تعطيك تحكم أكبر فليس بالضرورة أن تنتظر انتهاؤه
ولكتابة برنامج للإستعملات الحساسة والآمنة
يجب تجنب استعمال system
وتجنب execvp
و execlp
لأنها تبحث عن البرنامج
في متغير البيئة PATH
الذي يمكن تغيره بحيث ينفذ برنامج
آخر وعند الحاجة استعمال execv
أو execl
مع تحديد موقع البرنامج المطلق مثلاً /bin/ls
أو نسبة لمكان وجود البرنامج مثلاً برنامج /usr/bin/myprog
يريد تنفيذ البرنامج /usr/lib/myprog/mysub
يمكن معرفة المسار باستدعاء
sprintf(filename,"%s/../lib/myprog/mysub",dirname(arg[0]));
التي تنتج /usr/bin/../lib/myprog/mysub
التي تكافئ المطلوب فإذا كانت توزيعة قد غيرت سابقة البرنامج
من /usr
إلى /usr/X11R6
فأصبح البرنامج الأب /usr/X11R6/bin/myprog
وفرعه /usr/X11R6/lib/myprog/mysub
فإن الحيلة السابقة تنجح أيضا في معرفة ذلك.
هذا مثال يوضح استعمال system
/* run-sh-ls.c: a program calling a shell to run'ls' */ #include<stdio.h> #include<stdlib.h> int main() { int s; printf("I'm the parent process\n"); s=system("ls -F"); printf("the child ended with value %d\n",s); }
هناك عدة طرق للتخاطب بين العمليات IPC
أي Inter-Process Communication
منها
fork/pipe
- التفريع فالأنبوب
FIFO
الخاصة وتسمى named pipe
IPC
المعيارية الخاصة بالنظام الخامس SysV
message queues
semaphores
shared memory objects
socket
سواء المحلية أم عبر شبكات
ls | tr 'A-Z' 'a-z'
الطريقة الثانية FIFO هي أسهل طريقة لعمل علاقة server/client محلية. الثالثة تستفيد من نواة النظام المتوافق مع sysV. أما الأخيرة فهي الطريقة المفضلة لعمل برامج الشبكات.
أسهل طريقة لتنفيذ برنامج وارسال بيانات عبر أنبوب في أحد الإتجاهات
هي باستعمال popen
وتمرير الأمر الذي تريد تنفيذه
عبر محث الأوامر sh
ثم معاملاً آخر هو نص يحدد اتجاه
الأنبوب (أي هل تريد أن تقرأ منه"r" أم تكتب "w")
وتعيد الوظيفة مؤشر لمتغير ملف FILE *
وكأنه فتح ب
fopen
/* run-sort.c: a program calling a shell to sort */ #include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() { FILE *f1; printf("using '/bin/sort' to sort {camel,apple,zoo,foobar}\n"); f1=popen("/bin/sort","w"); if (!f1) exit(1); printf("sending ... "); fprintf(f1,"camel\napple\nzoo\nfoobar\n"); printf("OK.\nthe sorted list:\n"); pclose(f1); return 0; }
pipe
لعمل أنبوب بطرفين قراءة/كتابة
fork
لتفريع البرنامج الإبن
exec
pipe
تأخذ مؤشر على منظومة من عددين صحيحين
هما رقما وصف الملف الخاص بالقراءة والكتابة العائد على الأنبوب
int fd[2]; /* fd[0] for input and fd[1] for output */ if (pipe(fd)) perror("Can't create pipe");
fdopen
للتحويل من واصف الملف إلى متغير الملف
بحيث نستعمل وظائف مثل fprintf
و fgets
.
هذا برنامج يخاطب نفسه عبر أنبوب
/* self-pipe.c: a program talking to it self */ #include<stdio.h> #include<stdlib.h> #include<unistd.h> int main() { FILE *f1,*f2; char str[80]; int fd[2]; if (pipe(fd)) { perror("Can't create pipe"); exit(errno);} pid_t p=fork(); switch(p) { case 0: printf("sun: I'm the child process\n"); close(fd[0]); /* close the reading so I can write*/ f1=fdopen(fd[1],"w"); printf("sun: Hello dad. can you hear me!!\n"); fprintf(f1,"Hello dad. can you hear me!!\n"); break; case -1: perror("Can't fork"); exit(0); default: printf("parent: I'm the parent process\n"); close(fd[1]); /* close the writting so I can read */ f2=fdopen(fd[0],"r"); printf("parent: I'm listening sun\n"); fgets(str,80,f2); printf("parent: did you say '%s'?\n",str); printf("wait it to end\n"); waitpid(p,&status,0); printf("the child ended with value %d\n",status); } }
شرحنا طريقة الأنبوب ولكن هذا الأنبوب كان بإتجاه واحد.
يمكننا جنو/لينكس (وأنظمة يونكس) من عمل ملف خاص نميزه
من خلال حرف p على يسار الأذونات وعلامة ‘|‘ على يمين الاسم
عند استعمال ls -lF
نعمل هكذا ملف بواسطة mkfifo
أو mknod
bash# mknod /tmp/myfifo p
/* myfifo-serv.c - sample fifo testing server * * run it in the background to have a server */ #include <stddef.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #define MYFIFO "/tmp/myfifo" #define MY_MAX 180 int main() { FILE *f; char str[MY_MAX]; remove(MYFIFO); if (mknod(MYFIFO,S_IFIFO|0666,0)) { perror("Could not create file"); exit(1); } if (!f=fopen(MYFIFO,"r")) { perror("Could not open file"); exit(1); } printf("myfifo-serv: waiting to recive data from clients\n"); printf("myfifo-serv: use myfifo-client to send data\n"); while(1) { fgets(str,MY_MAX,f); printf("myfifo-serv: string received [%s]\n",str); } }
/* myfifo-client.c - sample fifo testing client * * run it as 'myfifo-client string' */ #include <stddef.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #define MYFIFO "/tmp/myfifo" #define MY_MAX 180 int main(int argc,char argv[]) { FILE *f; stat mystat; if (argc!=2) { printf("myfifo-client: sample fifo testing client syntax: myfifo-client STRGING where STRING is the string to send to server. example: myfifo-client 'Hello every body' "); exit(0); } if (stat(MYFIFO,&mystat)||!S_ISFIFO(mystat.st_mode)) { perror("Server is not running"); exit(1); } printf("myfifo-client: sending [%s] to server\n",argv[1]); if (!f=fopen(MYFIFO,"w")) { perror("Could not open file"); exit(1); } fputs(argv[1],f); fclose(f); }
bash# myfifo-serv & myfifo-serv: waiting to recive data from clients myfifo-serv: use myfifo-client to send data bash# myfifo-client "Hello, world!" myfifo-client: sending [Hello, world!] to server myfifo-serv: string received [Hello, world!]
chmod 2755 myfifo-serv
وتعمل مجموعة myfifo وتغيّر مجموعة المالك للخادم.
يمكن عمل ملفين واحد بإتجاه (من الخادم) وآخر بإتجاه آخر(إلى الخادم).
يمكن تغيير السلوك التلقائي بأن ينتظر
البرنامج في غفوة حتى يتوفر قراءة/كتابة على الطرف الآخر بواسطة
الخيار O_NONBLOCK
عند فتح الملف ب open
أو بتغيرها فيما بعد ب fcntl.
ونستعمل الوظيفة select من أجل الانتظار.
قنواة socket
نوعان: النوع الأول محلية تسمى local/file/unix socket
والثانية عبر الشبة وفق بروتوكولات الإنترنت IPv4
أو IPv6
هذا التصنيف وفق عائلة البرتوكول PF ففي
الأول يتم التخاطب بين برنامجين ويعتمد ملف على أنه العنوان
(كما في FIFO
ولكن كل برنامج له ملف)
أما الثانية تعتمد عنوان IP مثل 172.12.0.1
الأمر الآخر هو طريقة الإرسال فهي إما سيال stream (طلب-موافقة-فتح قناة ) فالأول يتصل الثاني يستمع ثم يقرر فتح اتصال وتخصيص fd بعدها يتم الإتصال بين الإثنين عبره وكأنه ملف تصل البيانات بنفس ترتيب الإرسال. أما الثانية فهي إبراق البيانات Datagram حيث في كل عملية من إرسال/استقبال يجب أن يحدد العنوان في هذا الأخير قد تصل البرقية datagram عدة مرات أو قد تصل بترتيب غير الذي أرسلت به. كما وعليك أن تحدد البرتوكول حسب طريقة الإتصال و عائلة البرتوكولات والأفضل أن تضع 0 لاختيار المناسب
الصيغة العامة لعمل socket هي
socket (PF, STYLE, PROTOCOL)
حيث PF هي
PF_LOCAL
أو PF_INET
أو PF_INET6
و STYLE هي
SOCK_STREAM
أو SOCK_DGRAM
و PROTOCOL هي صفر.
تعيد هذه الوظيفة رقم صحيح موجب هو fd.
PF_LOCAL
له أسماء أخرى هي PF_UNIX
و PF_FILE
هي فقط للتواصل عبر جهاز واحد. هذا لا يعني
أنه لا يمكن عمل اتصال بين برنامجين على جهاز واحد لا يحتوي جهاز اتصال
باستعمال PF_INET إذ يمكن استعمال العنوان المحلي127.0.0.1
لعمل برنامج وكيل/مخدوم client فإننا نقم بالخطوات بالشكل التالي:
socket
بالمعاملات المناسبة
وفق نوع الإتصال للحصول على رقم موجب هو واصف الملف fd.
PF_LOCAL
نحدد ملف ليمثل
عنواناً لبرنامجنا المخدوم client
ونربطه مع الواصف باستدعاء bind
أما النوع الآخر (نوع IP) يتم تحديد عنوانك تلقائياً من عنوان الجهاز.
SOCK_DGRAM
sendto
و recvfrom
التي تأخذ عنوان المرسل إليه والمستقبل منه في كل رسالة
SOCK_STREAM
connect
على العنوان
الذي نريد أن تتصل به فإن وافق ونجحت يمكنك القراءة والكتابة
عبر الواصف بالوظائف read
و write
و recv
و send
socket
بالمعاملات المناسبة
وفق نوع الإتصال للحصول على رقم موجب هو واصف الملف fd.
PF_LOCAL
نحدد ملف ليمثل
عنواناً لبرنامجنا الخادم ونربطه مع الواصف باستدعاء bind
.
أما النوع الآخر (نوع IP) نربطه ليستقبل من كل العناوين
INADDR_ANY
أو من المحلية INADDR_LOOPBACK
وحدد منفذ port
واربطها باستدعاء bind
SOCK_DGRAM
sendto
و recvfrom
التي تأخذ عنوان المرسل إليه والمستقبل منه في كل رسالة
SOCK_STREAM
listen
(تستطيع تحديد حجم الطابور لكن 1 تصلح)
accept
ويمكن استدعاء getpeername
لمعرفة من أين هذا الإتصال.
listen
fd_set
وأضف إليها fd الأصلي
select
قدوم بيانات
إلى أي عنصر في المجموعة السابقة
accept
ثم أضف الواصف الناتج للمجموعة fd_set
لكي نتواصل ونتظر منه.
read
و write
و recv
و send
يجب تحويل الأرقام (رقم المنفذ port وعنوان IP) من هيئة الجهاز
الحالي لهيئة الشبكة (حيث تختلف الأجهزة في ترتيب البايتات
بحيث يكون الأكثر/الأقل أهمية أولاً big-endian/little-endian على الترتيب)
بواسطة مثلاً htons (port)
htonl (INADDR_LOOPBACK)
.
لا يجوز وضع accept
داخل thread
في برامج multi-threads
.
sys/socket.h
ومن أجل العناوين المحلية sys/un.h
وعناوين IP الملف netinet/in.h
الملف arpa/inet.h
يحتوي الوظيفة inet_aton
لتحويل سلسلة نصية تحتوي العنوان على صورة "a.b.c.d"
ومن أجل حل العناوين بالاسم مثل gnu.org
بواسطة خادم DNS نستعمل
ملف netdb.h
لتسهيل العمل يقترح كتيب مكتبة glibc الوظائف التالية المستعملة بكثرة. لهذا أجمعها في ملف واحد.
/* socket-common.c - add it to your project for common function */ #include <stddef.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/un.h> #include <netinet/in.h> #include <netdb.h> int make_named_socket (const char *filename) { struct sockaddr_un name; int sock; size_t size; /* Create the socket. */ sock = socket (PF_LOCAL, SOCK_DGRAM, 0); if (sock < 0) { perror ("socket"); exit (1); } /* Bind a name to the socket. */ name.sun_family = AF_LOCAL; strncpy (name.sun_path, filename, sizeof (name.sun_path)); size = SUN_LEN (&name); if (bind (sock, (struct sockaddr *) &name, size) < 0) { perror ("bind"); exit (1); } return sock; } int make_socket (uint16_t port) { int sock; struct sockaddr_in name; /* Create the socket. */ sock = socket (PF_INET, SOCK_STREAM, 0); if (sock < 0) { perror ("socket"); exit (1); } /* Give the socket a name(any_address:port). */ name.sin_family = AF_INET; name.sin_port = htons (port); name.sin_addr.s_addr = htonl (INADDR_ANY); if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { perror ("bind"); exit (1); } return sock; } void init_sockaddr (struct sockaddr_in *name,const char *hostname,uint16_t port){ struct hostent *hostinfo; name->sin_family = AF_INET; name->sin_port = htons (port); hostinfo = gethostbyname (hostname); if (hostinfo == NULL) { fprintf (stderr, "Unknown host %s.\n", hostname); exit (1); } name->sin_addr = *(struct in_addr *) hostinfo->h_addr; }
PF_LOCAL
تكون من نوع sockaddr_un
وتحدد بوضع العنصر sun_family
ليكون AF_LOCAL
ونسخ مسار الملف إلى العنوان المشار له ب sun_path
بواسطة strcpy
ونعرف الحجم الحقيقي(لأن sun_path
محجوزة ل108 محرف مسبقاً) لها ب SUN_LEN
(نحتاجه ل bind
).
أما PF_INET
فهي من نوع sockaddr_in
العنصر sin_family
يجب أن يساوي AF_INET
و sin_port
رقم المنفذ و s_addr
إلى عنوان IP
محول إلى رقم (بواسطة gethostbyname
مثلاً) أو من الجاهزة
مثل htonl (INADDR_LOOPBACK)
و
htonl (INADDR_ANY)
ونعرف حجمها ب sideof
.
هذه الأمثلة معدل من كتيب مكتبة سي من جنو أولها مخدوم وخادم توضيحي محلي بطريقة الإبراق datagram يشبه الزوج السابق FIFO:
/* mysock-lo-dgram-client: local data gram socket sample client */ #include <stdio.h> #include <errno.h> #include <unistd.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/un.h> #define SERVER "/tmp/serversocket" #define CLIENT "/tmp/mysocket" #define MAXMSG 512 /* This function is defiend in our 'socket-common.c' */ extern int make_named_socket (const char *name); int main (int argc,char *argv[]) { int sock; struct sockaddr_un name; size_t size; int nbytes; /* Process arguments and show usage */ if (argc!=2) { printf("mysock-lo-dgram-client: sample socket testing client syntax: mysock-lo-dgram-client STRGING where STRING is the string to send to server. example: mysock-lo-dgram-client 'Hello every body' "); exit(0); } /* Make the socket. */ sock = make_named_socket (CLIENT); /* Initialize the server socket address. */ name.sun_family = AF_LOCAL; strcpy (name.sun_path, SERVER); size=SUN_LEN (&name); /* it was strlen(name.sun_path) + sizeof(name.sun_family)*/ /* Send the datagram. */ nbytes = sendto (sock, argv[1], strlen (argv[1]) + 1, 0, (struct sockaddr *) & name, size); if (nbytes < 0) { perror ("sendto (client)"); exit (-1); } /* Clean up. */ remove (CLIENT); close (sock); return 0; }
/* mysock-lo-dgram-server: local data gram socket sample server*/ /* run it in the background using '&' */ #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/un.h> #define SERVER "/tmp/serversocket" #define MAXMSG 512 /* This function is defiend in our 'socket-common.c' */ extern int make_named_socket (const char *name); int main (void) { int sock; char message[MAXMSG]; struct sockaddr_un name; size_t size; int nbytes; /* Remove the filename first, it's ok if the call fails */ unlink (SERVER); /* Make the socket, then loop endlessly. */ sock = make_named_socket (SERVER); while (1) { /* Wait for a datagram. */ size = sizeof (name); nbytes = recvfrom (sock, message, MAXMSG, 0, (struct sockaddr *) & name, &size); if (nbytes < 0) { perror ("recfrom (server)"); exit (1); } /* Give a diagnostic message. */ fprintf (stderr, "Server: got message: [%s]\n", message); } }
/* mysock-ip-client: internet sample client */ #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define PORT 5555 /* This function is defiend in our 'socket-common.c' */ extern void init_sockaddr (struct sockaddr_in *name, const char *hostname,uint16_t port); char *SERVERHOST,*MESSAGE; void write_to_server (int filedes) { int nbytes; nbytes = write (filedes, MESSAGE, strlen (MESSAGE) + 1); if (nbytes < 0) { perror ("write"); exit (1); } } int main (int argc,char *argv[]) { int sock; struct sockaddr_in servername; /* Process arguments and show usage */ if (argc!=3) { printf("mysock-ip-client: sample inet socket testing client syntax: mysock-ip-client HOSTNAME STRGING where: STRING is the string to send to server. HOSTNAME is the host of the server like 'mynet.com' example: mysoc-ip-client 'localhost' 'Hello every body' "); exit(0); } SERVERHOST=argv[1]; MESSAGE=argv[2]; /* Create the socket. */ sock = socket (PF_INET, SOCK_STREAM, 0); if (sock < 0) { perror ("socket (client)"); exit (1); } /* Connect to the server. */ init_sockaddr (&servername, SERVERHOST, PORT); if (0 > connect (sock, (struct sockaddr *) &servername, sizeof (servername))) { perror ("connect (client)"); exit (1); } /* Send data to the server. */ write_to_server (sock); close (sock); return 0; }
/* mysock-ip-serv: internet sample server */ /* run it in the background */ #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define PORT 5555 #define MAXMSG 512 int read_from_client (int filedes) { char buffer[MAXMSG]; int nbytes; nbytes = read (filedes, buffer, MAXMSG); if (nbytes < 0) { /* Read error. */ perror ("read"); exit (1); } else if (nbytes == 0) { /* End-of-file. */ return -1; } else { /* Data read. */ fprintf (stderr, "Server: got message: `%s'\n", buffer); return 0; } } int main (void) { extern int make_socket (uint16_t port); int sock; fd_set active_fd_set, read_fd_set; int i; struct sockaddr_in clientname; size_t size; /* Create the socket and set it up to accept connections. */ sock = make_socket (PORT); if (listen (sock, 1) < 0) { perror ("listen"); exit (1); } /* Initialize the set of active sockets. */ FD_ZERO (&active_fd_set); FD_SET (sock, &active_fd_set); while (1) { /* Block until input arrives on one or more active sockets. */ read_fd_set = active_fd_set; if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0) { perror ("select"); exit (1); } /* Service all the sockets with input pending. */ for (i = 0; i < FD_SETSIZE; ++i) if (FD_ISSET (i, &read_fd_set)) { if (i == sock) { /* Connection request on original socket. */ int new; size = sizeof (clientname); new = accept (sock, (struct sockaddr *) &clientname, &size); if (new < 0) { perror ("accept"); exit (1); } fprintf (stderr, "Server: connect from host %s, port %hd.\n", inet_ntoa (clientname.sin_addr), ntohs (clientname.sin_port)); FD_SET (new, &active_fd_set); } else { /* Data arriving on an already-connected socket. */ if (read_from_client (i) < 0) { close (i); FD_CLR (i, &active_fd_set); } } /* enf of if-else */ } /* end of for */ } /* end of while */ }
<< السابق | كتاب لينكس الشامل | التالي >> |