11. حساب الضرائب: تمرين باستخدام خدمة ويب وبنية ثلاثية المستويات
سنعيد النظر في تمرين IMPOTS (انظر الأقسام 4.2 و4.3 و6) ونحوله إلى تطبيق عميل/خادم. سيتم تقسيم البرنامج النصي للخادم إلى ثلاثة مكونات:
- طبقة تسمى [DAO] (كائنات الوصول إلى البيانات) ستتولى التعامل مع قاعدة بيانات MySQL
- طبقة تسمى [business] ستقوم بحساب الضريبة
- طبقة [web] ستتولى الاتصال مع عملاء الويب.
![]() |
يُرسل البرنامج النصي للعميل [1]:
- يرسل المعلومات الثلاث ($married، $children، $salary) اللازمة لحساب الضريبة إلى البرنامج النصي للخادم
- يعرض استجابة الخادم على وحدة التحكم
يتكون البرنامج النصي للخادم [2] من طبقة [الويب] الخاصة بالخادم.
- في بداية جلسة عميل جديدة، سيملأ المصفوفات بالبيانات من قاعدة بيانات MySQL [dbimpots]. للقيام بذلك، سيستدعي طبقة [DAO]. سيتم وضع المصفوفات التي تم إنشاؤها بهذه الطريقة في جلسة العميل بحيث يمكن استخدامها في طلبات العميل اللاحقة.
- عندما يقوم العميل بتقديم طلب، فإنه يمرر المعلومات الثلاث ($married، $children، $salary) إلى طبقة [منطق الأعمال]، والتي ستحسب الضريبة $tax.
- سيُرجع البرنامج النصي للخادم الضريبة المحسوبة $tax.
11.1. نص العميل (clients_impots_05_web)
سيكون البرنامج النصي للعميل عميلاً لخدمة الويب لحساب الضريبة. وسيقوم بإرسال (POST) المعلمات إلى الخادم بالصيغة التالية:
params=$married,$children,$salary حيث
- $married ستكون السلسلة "yes" أو "no"،
- $children سيكون عدد الأطفال،
- $salary هو راتب دافع الضرائب
يسترد المعلمات الثلاثة المذكورة أعلاه من ملف نصي [data.txt] بالتنسيق (married, children, salary):
نص البرنامج النصي للعميل
- سيقوم بقراءة الملف النصي [data.txt] سطراً سطراً
- سيرسل السلسلة params=$married,$children,$salary إلى خدمة الويب لحساب الضرائب
- سيسترد الرد من الخدمة. يمكن أن يتخذ هذا الرد شكلين:
- سيحفظ استجابة الخادم في ملف نصي [results.txt] بأحد التنسيقين التاليين:
فيما يلي كود البرنامج النصي من جانب العميل:
<?php
// tax client
// error management
ini_set("display_errors", "off");
// ---------------------------------------------------------------------------------
// a class of utility functions
class Utilitaires {
function cutNewLinechar($ligne) {
...
}
}
// main -----------------------------------------------------
// definition of constants
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
// server data
$HOTE = "localhost";
$PORT = 80;
$urlServeur = "/exemples-web/impots_05_web.php";
// taxable person parameters (marital status, number of children, annual salary)
// were placed in the $DATA text file, one line for each taxpayer
// results (marital status, number of children, annual salary, tax payable)
// or (marital status, number of children, annual salary, error msg) are placed in
// the $RESULTATS text file, with one result per line
// utility class
$u = new Utilitaires();
// opening taxpayer data files
$data = fopen($DATA, "r");
if (!$data) {
print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
exit;
}
// open results file
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
exit;
}
// the current line of the taxpayer data file is used
while ($ligne = fgets($data, 100)) {
// remove any end-of-line marker
$ligne = $u->cutNewLineChar($ligne);
// we retrieve the 3 fields married:children:salary which form $ligne
list($marié, $enfants, $salaire) = explode(",", $ligne);
// tax calculation
list($erreur, $impôt) = calculerImpot($HOTE, $PORT, $urlServeur, $cookie, array($marié, $enfants, $salaire));
// enter the result
$résultat = $erreur ? "$marié:$enfants:$salaire:$erreur" : "$marié:$enfants:$salaire:$impôt";
fputs($résultats, "$résultat\n");
// following data
}
// close files
fclose($data);
fclose($résultats);
// end
print "Terminé...\n";
exit;
function calculerImpot($HOTE, $PORT, $urlServeur, &$cookie, $params) {
// connects client to ($HOTE,$PORT,$urlServeur)
// sends the $cookie cookie if it is non-empty. $cookie is passed by reference
// sends $params to the server
// exploits the single line returned by the server
// open a connection on port 80 of $HOTE
$connexion = fsockopen($HOTE, $PORT);
// mistake?
if (!$connexion)
return array("erreur lors de la connexion au serveur ($HOTE, $PORT)");
// protocol HTTP headers must end with an empty line
// POST
fputs($connexion, "POST $urlServeur HTTP/1.1\n");
// Host
fputs($connexion, "Host: localhost\n");
// Connection
fputs($connexion, "Connection: close\n");
// send cookie if non-empty
if ($cookie) {
fputs($connexion, "Cookie: $cookie\n");
}////if
// now we send the client instruction after encoding it
$infos = "params=" . urlencode(implode(",", $params));
// on indique quel type d'informations on va envoyer
fputs($connexion, "Content-type: application/x-www-form-urlencoded\n");
// send the size (number of characters) of the information to be sent
fputs($connexion, "Content-length: " . strlen($infos) . "\n");
// send an empty line
fputs($connexion, "\n");
// we send the news
fputs($connexion, $infos);
// the web server response is displayed
// and we take care to recover any cookie
while ($ligne = fgets($connexion, 1000)) {
// cookie - only on 1st response
if (!$cookie) {
if (preg_match("/^Set-Cookie: (.*?)\s*$/", $ligne, $champs)) {
$cookie = $champs[1];
}//if
}
// as soon as there is an empty line, the HTTP response is terminated
if (trim($ligne) == "") {
break;
}
}////while
// read line result
$ligne = fgets($connexion, 1000);
// close the connection
fclose($connexion);
// result calculation
$erreur="";
$impôt="";
if (preg_match("/^<erreur>(.*?)<\/erreur>\s*$/", $ligne, $champs)) {
$erreur = $champs[1];
} else {
if (preg_match("/^<impot>(.*?)<\/impot>\s*$/", $ligne, $champs)) {
$impôt = $champs[1];
}else{
$erreur="résultat du serveur non exploitable";
}
}
// return
return array($erreur, $impôt);
}
تعليقات
يتضمن كود البرنامج النصي من جانب العميل عناصر سبق أن رأيناها من قبل:
- الأسطر 9–15: تم تقديم فئة [Utilities] في الإصدار 3، القسم 6
- الأسطر 17–68: يشبه البرنامج الرئيسي ذلك الموجود في الإصدار 1، القسم 4.2. ويختلف عنه فقط في حساب الضريبة، السطر 56.
- السطر 56: تقبل دالة حساب الضريبة المعلمات التالية:
- $HOST، $PORT، $serverURL: تُستخدم للاتصال بخدمة الويب
- $cookie: هي ملف تعريف الارتباط للجلسة. يتم تمرير هذه المعلمة بالرجوع إليها. يتم تعيين قيمتها بواسطة وظيفة حساب الضريبة. عند الاستدعاء الأول، لا تحتوي على أي قيمة. بعد ذلك، تحتوي على قيمة.
- array($married, $children, $salary): تمثل صفًا من ملف [data.txt]
تُرجع دالة حساب الضريبة مصفوفة من نتيجتين ($error، $tax) حيث $error هي رسالة خطأ محتملة و$tax هي مبلغ الضريبة.
- الأسطر 70–134: هذا عميل HTTP كلاسيكي، من النوع الذي صادفناه مرات عديدة. لاحظ النقاط التالية:
- السطر 83: يتم إرسال المعلمات ($married، $children، $salary) إلى الخادم عبر طلب POST
- الأسطر 89–91: إذا كان لدى العميل معرف جلسة، فإنه يرسله إلى الخادم
- السطر 93: إنشاء المعلمة params
- السطر 101: يتم إرسال المعلمة `params`
- الأسطر 104–115: يقرأ العميل جميع رؤوس HTTP المرسلة من الخادم حتى يصادف السطر الفارغ الذي يشير إلى نهاية الرؤوس. ويستغل هذه الفرصة لاسترداد معرف الجلسة من الاستجابة لطلبه الأول.
- الأسطر 123–125: معالجة أي سطر بالصيغة <error>message</error>
- الأسطر 126–128: نفعل الشيء نفسه مع أي سطر بالصيغة <tax>amount</tax>
- السطر 133: يتم إرجاع النتيجة
11.2. خدمة الويب لحساب الضريبة
نحن مهتمون هنا بالبرامج النصية الثلاثة التي يتكون منها الخادم:
![]() |
مشروع NetBeans المقابل هو كما يلي:
![]() |
في [1]، يتكون الخادم من البرامج النصية PHP التالية:
- [impots_05_entites] يحتوي على الفئات التي يستخدمها الخادم
- [impots_05_dao] يحتوي على الفئات والواجهات الخاصة بطبقة [dao]
- [impots_05_metier] يحتوي على الفئات والواجهات الخاصة بطبقة [business]
- [impots_05_web] يحتوي على الفئات والواجهات الخاصة بطبقة [dao]
نبدأ بعرض فئتين تستخدمهما الطبقات المختلفة لخدمة الويب.
11.2.1. كيانات خدمة الويب (impots_05_entities)
تحتوي قاعدة بيانات MySQL [dbimpots] على جدول [impots] يحتوي على البيانات اللازمة لحساب الضريبة [1]:
![]() |
سنقوم بتخزين البيانات من جدول MySQL [impots] في مصفوفة من كائنات Tranche، حيث Tranche هي الفئة التالية:
<?php
// a tax bracket
class Tranche {
// private fields
private $limite;
private $coeffR;
private $coeffN;
// getters and setters
public function getLimite() {
return $this->limite;
}
public function setLimite($limite) {
$this->limite = $limite;
}
public function getCoeffR() {
return $this->coeffR;
}
public function setCoeffR($coeffR) {
$this->coeffR = $coeffR;
}
public function getCoeffN() {
return $this->coeffN;
}
public function setCoeffN($coeffN) {
$this->coeffN = $coeffN;
}
// manufacturer
public function __construct($limite, $coeffR, $coeffN) {
$this->setLimite($limite);
$this->setCoeffR($coeffR);
$this->setCoeffN($coeffN);
}
// toString
public function __toString(){
return "[$this->limite,$this->coeffR,$this->coeffN]";
}
}
سيتم استخدام الحقول الخاصة [$limite, $coeffR, $coeffN] لتخزين الأعمدة [limites, coeffR, coeffN] لصف في جدول MySQL [impots].
بالإضافة إلى ذلك، سيستخدم كود الخادم استثناءً مخصصًا، وهو فئة ImpotsException:
- السطر 1: تمتد فئة [ImpotsException] من فئة [Exception] المحددة مسبقًا في PHP 5
- السطر 3: يقبل منشئ فئة [ImpotsException] معلمتين:
- $message: رسالة خطأ
- $code: رمز الخطأ
11.2.2. طبقة [dao] (impots_05_dao)
توفر طبقة [dao] الوصول إلى بيانات قاعدة البيانات:
![]() |
تحتوي طبقة [dao] على الواجهة التالية:
تُظهر واجهة IImpotsDao الدالة getData فقط. تضع هذه الدالة الصفوف المختلفة لجدول MySQL [dbimpots.impots] في مصفوفة من كائنات Tranche.
فئة التنفيذ هي كما يلي:
<?php
// dao layer
// dependencies
require_once "impots_05_entites.php";
// constants
define("TABLE", "impots");
// -----------------------------------------------------------------
// abstract implementation
abstract class ImpotsDaoWithPdo implements IImpotsDao {
// private fields
private $dsn;
private $user;
private $passwd;
private $tranches;
// getters and setters
public function getDsn() {
return $this->dsn;
}
public function setDsn($dsn) {
$this->dsn = $dsn;
}
public function getUser() {
return $this->user;
}
public function setUser($user) {
$this->user = $user;
}
public function getPasswd() {
return $this->passwd;
}
public function setPasswd($passwd) {
$this->passwd = $passwd;
}
// manufacturer
public function __construct($dsn, $user, $passwd) {
// save parameters
$this->setDsn($dsn);
$this->setUser($user);
$this->setPasswd($passwd);
// retrieve data from SGBD
// connects ($user,$pwd) to base $dsn
try {
// connection
$connexion = new PDO($dsn, $user, $passwd, array(PDO::ATTR_PERSISTENT => true));
// read table $TABLE
$requête = "select limites,coeffR,coeffN from " . TABLE;
// executes the $requête request on the $connexion connection
$statement = $connexion->prepare($requête);
$statement->execute();
// query result evaluation
while ($colonnes = $statement->fetch()) {
$this->tranches[] = new Tranche($colonnes[0], $colonnes[1], $colonnes[2]);
}
// disconnect
$connexion=NULL;
} catch (PDOException $e) {
// return with error
throw new ImpotsException($e->getMessage(), 1);
}
}
public function getData(){
return $this->tranches;
}
}
- السطر 5: يتطلب تنفيذ واجهة [IImpotsDao] الفئات المحددة في البرنامج النصي [impots_05_entities].
- السطر 11: تعريف فئة مجردة. الفئة المجردة هي فئة لا يمكن إنشاء مثيل لها. يجب أن تكون الفئة المجردة مشتقة من فئة أخرى حتى يمكن إنشاء مثيل لها. يمكن إعلان فئة ما على أنها مجردة لأنها لا يمكن إنشاء مثيل لها (بعض أساليبها غير محددة) أو لأننا لا نريد إنشاء مثيل لها. هنا، لا نريد إنشاء مثيل لفئة [ImpotsDaoWithPdo]. سنقوم بإنشاء مثيلات للفئات المشتقة.
- السطر 11: تنفذ فئة [ImpotsDaoWithPdo] واجهة [IImpotsDao]. لذلك يجب أن تحدد طريقة getData. توجد هذه الطريقة في الأسطر 72-74.
- السطر 14: $dsn (اسم مصدر البيانات) هو سلسلة تحدد بشكل فريد نظام إدارة قواعد البيانات (DBMS) وقاعدة البيانات المستخدمة.
- السطر 15: $user يحدد المستخدم المتصل بقاعدة البيانات
- السطر 16: $passwd هي كلمة مرور المستخدم السابق
- السطر 17: $tranches هو مصفوفة كائنات Tranche التي سيتم تخزين جدول MySQL [dbimpots.impots] فيها.
- الأسطر 45–70: منشئ الفئة. تم تناول هذا الرمز بالفعل في الإصدار 4، القسم 8.2. لاحظ أن إنشاء كائن [ImpotsDaoWithPdo] قد يفشل. عندئذ يتم إلقاء استثناء [ImpotsException].
- الأسطر 72–74: طريقة [getData] لواجهة [IImpotsDao].
تعد فئة [ImpotsDaoWithPdo] مناسبة لأي نظام إدارة قواعد البيانات (DBMS). يتطلب منشئ الفئة، في السطر 45، معرفة اسم مصدر البيانات الخاص بقاعدة البيانات. تعتمد هذه السلسلة على نظام إدارة قواعد البيانات المستخدم. لقد اخترنا ألا نطلب من مستخدم الفئة معرفة اسم مصدر البيانات هذا. لكل نظام إدارة قواعد بيانات (DBMS)، ستكون هناك فئة محددة مشتقة من [ImpotsDaoWithPdo]. بالنسبة لنظام إدارة قواعد البيانات MySQL، ستكون هذه الفئة كما يلي:
class ImpotsDaoWithMySQL extends ImpotsDaoWithPdo {
public function __construct($host, $port, $base, $user, $passwd) {
parent::__construct("mysql:host=$host;dbname=$base;port=$port", $user, $passwd);
}
}
- في السطر 3، لا يطلب المنشئ اسم مصدر البيانات، بل يطلب فقط اسم جهاز مضيف نظام إدارة قواعد البيانات ($host)، ومنفذ الاستماع الخاص به ($port)، واسم قاعدة البيانات ($base).
- السطر 4: يتم إنشاء اسم مصدر البيانات لقاعدة بيانات MySQL واستخدامه لاستدعاء منشئ الفئة الأصلية.
لاحظ أنه للتكيف مع نظام إدارة قواعد بيانات آخر، ما عليك سوى كتابة الفئة المناسبة المشتقة من [ImpotsDaoWithPdo]. في كل حالة، يجب إنشاء اسم مصدر البيانات الخاص بنظام إدارة قواعد البيانات المستخدم.
11.2.3. طبقة [business] (impots_05_metier)
تحتوي طبقة [business] على منطق حساب الضرائب:
![]() |
تحتوي طبقة [الأعمال] على الواجهة التالية:
<?php
// business interface
interface IImpotsMetier {
public function calculerImpot($marié, $enfants, $salaire);
}
تُظهر واجهة [IImpotsMetier] طريقة واحدة فقط، وهي طريقة [calculateTax]، التي تحسب ضريبة دافع الضرائب بناءً على المعلمات التالية:
- $married: سلسلة من "yes" أو "no" اعتمادًا على ما إذا كان دافع الضرائب متزوجًا أم لا
- $children: عدد أطفال المكلف
- $salary: راتب دافع الضرائب
ستوفر طبقة [web] هذه المعلمات.
يتم تنفيذ واجهة [IImpotsMetier] على النحو التالي:
// dependencies
require_once "impots_05_dao.php";
// ------------------------------------------------------------------
// implementation class
class ImpotsMetier implements IImpotsMetier {
// dao layer
private $dao;
// object array [Slice]
private $data;
// getter and setter
public function getDao() {
return $this->dao;
}
public function setDao($dao) {
$this->dao = $dao;
}
public function setData($data){
$this->data=$data;
}
public function __construct($dao) {
// retrieve the data needed to calculate taxes
$this->setDao($dao);
$this->setData($this->dao->getData());
}
public function calculerImpot($marié, $enfants, $salaire) {
// $marié : yes, no
// $enfants : number of children
// $salaire: annual salary
// number of shares
$marié = strtolower($marié);
if ($marié == "oui")
$nbParts = $enfants / 2 + 2;
else
$nbParts=$enfants / 2 + 1;
// an additional 1/2 share if at least 3 children
if ($enfants >= 3)
$nbParts+=0.5;
// taxable income
$revenuImposable = 0.72 * $salaire;
// family quotient
$quotient = $revenuImposable / $nbParts;
// is set at the end of the limit table to stop the following loop
$N = count($this->data);
$this->data[$N - 1]->setLimite($quotient);
// tAX CALCULATION
$i = 0;
while ($i < $N and $quotient > $this->data[$i]->getLimite()) {
$i++;
}
// because $quotient has been placed at the end of the $limites array, the previous loop
// cannot exceed the table $limites
// now we can calculate the tax
return floor($revenuImposable * $this->data[$i]->getCoeffR() - $nbParts * $this->data[$i]->getCoeffN());
}
}
- السطر 2: تتطلب طبقة [business] فئات من طبقة [DAO] وكيانات (Tranche، ImpotsException).
- السطر 6: تنفذ فئة [ImpotsMetier] واجهة [IimpotsMetier].
- الأسطر 9-11: الحقول الخاصة بالفئة:
- $dao: مرجع إلى طبقة [dao]
- $data: مصفوفة من الكائنات من النوع [Tranche] مقدمة من طبقة [dao]
- الأسطر 26-30: يقوم منشئ الفئة بتهيئة الحقلين السابقين. ويتلقى إشارة إلى طبقة [dao] كمعلمة.
- الأسطر 32-61: تنفيذ طريقة [calculerImpot] لواجهة [IimpotsMetier]. تم تقديم هذه الطريقة في الإصدار 1 (القسم 4.2).
11.2.4. طبقة [web] (impots_05_web)
تحتوي طبقة [business] على منطق حساب الضرائب:
![]() |
تتكون طبقة [الويب] من خدمة الويب التي تستجيب لعملاء الويب. تذكر أن هؤلاء العملاء يوجهون طلبًا إلى خدمة الويب عن طريق إرسال المعلمة التالية: params=married,children,salary. هذه خدمة ويب مشابهة لتلك التي أنشأناها في الأقسام السابقة. وفيما يلي شفرة البرمجة الخاصة بها:
<?php
// business layer
require_once "impots_05_metier.php";
// error management
ini_set("display_errors", "off");
// uTF-8 header
header("Content-Type: text/plain; charset=utf-8");
// ------------------------------------------------------------------------------
// tax web service
// definition of constants
$HOTE = "localhost";
$PORT = 3306;
$BASE = "dbimpots";
$USER = "root";
$PWD = "";
// the data required to calculate the tax has been placed in the mysql table IMPOTS
// belonging to the $BASE database. The table has the following structure
// limits decimal(10,2), coeffR decimal(6,2), coeffN decimal(10,2)
// taxable person parameters (marital status, number of children, annual salary)
// are sent by the customer in the form params=marital status, number of children, annual salary
// results (marital status, number of children, annual salary, tax payable) are returned to the customer
// in the form <impot>impot</impot>
// or as <error>error</error>, if parameters are invalid
// retrieve the [business] layer in the session
session_start();
if (!isset($_SESSION['metier'])) {
// instantiation of the [dao] layer and the [business] layer
try {
$_SESSION['metier'] = new ImpotsMetier(new ImpotsDaoWithMySQL($HOTE, $PORT, $BASE, $USER, $PWD));
} catch (ImpotsException $ie) {
print "<erreur>Erreur : " . utf8_encode($ie->getMessage() . "</erreur>");
exit;
}
}
$metier = $_SESSION['metier'];
// retrieve the line sent by the client
$params = utf8_encode(htmlspecialchars(strtolower(trim($_POST['params']))));
$items = explode(",", $params);
// there must be only 3 parameters
if (count($items) != 3) {
print "<erreur>[$params] : nombre de paramètres invalides</erreur>\n";
exit;
}//if
// first parameter (marital status) must be yes/no
$marié = trim($items[0]);
if ($marié != "oui" and $marié != "non") {
print "<erreur>[$params] : 1er paramètre invalide</erreur>\n";
exit;
}//if
// the second parameter (number of children) must be an integer
if (!preg_match("/^\s*(\d+)\s*$/", $items[1], $champs)) {
print "<erreur>[$params] : 2ième paramètre invalide</erreur>\n";
exit;
}//if
$enfants = $champs[1];
// the third parameter (salary) must be an integer
if (!preg_match("/^\s*(\d+)\s*$/", $items[2], $champs)) {
print "<erreur>[$params] : 3ième paramètre invalide</erreur>\n";
exit;
}//if
$salaire = $champs[1];
// tax calculation
$impôt = $metier->calculerImpot($marié, $enfants, $salaire);
// return the result
print "<impot>$impôt</impot>\n";
// end
exit;
- السطر 4: تحتاج طبقة [الويب] إلى الفئات من طبقة [الأعمال]
- الأسطر 30–40: الإشارة إلى طبقة [business] محددة بنطاق الجلسة. إذا تذكرنا أن طبقة [business] هذه تحتوي على إشارة إلى طبقة [DAO] وأن الأخيرة تخزن بيانات نظام إدارة قواعد البيانات (DBMS)، يمكننا أن نرى أن:
- أن الطلب الأول للعميل سيؤدي إلى الوصول إلى نظام إدارة قواعد البيانات
- أن الطلبات اللاحقة من نفس العميل ستستخدم البيانات المخزنة بواسطة طبقة [DAO]. وبالتالي، لا يوجد وصول إلى نظام إدارة قواعد البيانات (DBMS).
- السطر 34: إنشاء طبقة [الأعمال] التي تعمل مع طبقة [DAO] التي تم تنفيذها لنظام إدارة قواعد البيانات MySQL
- الأسطر 35-37: معالجة خطأ محتمل في العملية السابقة. في هذه الحالة، يتم إرسال سطر <error>message</error> إلى العميل.
- السطر 43: استرداد المعلمة 'params' التي أرسلها العميل.
- الأسطر 46-49: التحقق من عدد العناصر الموجودة في "params"
- الأسطر 51–55: التحقق من صحة المعلومة الأولى
- الأسطر 56–60: نفس الشيء بالنسبة للمعلومة الثانية
- الأسطر 62-66: نفس الشيء بالنسبة للمعلومة الثالثة
- السطر 69: تحسب طبقة [الأعمال] الضريبة.
- السطر 71: إرسال النتيجة إلى العميل
النتائج
تذكر أن العميل [client_impots_web_05] يستخدم الملف التالي [data.txt]:
بناءً على هذه الأسطر (متزوج، أطفال، راتب)، يستعلم العميل عن خادم حساب الضرائب ويكتب النتائج في ملف نصي [results.txt]. بعد تشغيل العميل، يكون محتوى هذا الملف كما يلي:
oui:2:200000:22504
non:2:200000:33388
oui:3:200000:16400
non:3:200000:22504
oui:5:50000:0
non:0:3000000:1354938
حيث يكون كل سطر بالصيغة (متزوج، أطفال، راتب، ضريبة محسوبة).






