پشتیبانی از چندریسه در کلاینت‌های ایمیل

کلاینت‌های ایمیل مانند ImapClient و Pop3Client به کاربران اجازه می‌دهد تا در محیط چندریسه از آن‌ها استفاده کنند. کلاینت‌های ایمیل امکان داشتن یک یا چند اتصال با سرور را دارند. برای مدیریت مجموعه‌ای از اتصالات درون کلاینت‌ها از استخر اتصال استفاده می‌شود. تعداد اتصالاتی که می‌توانند همزمان ایجاد و استفاده شوند، توسط ویژگی CredentialsByHostClient.MaxConnectionsPerServer محدود می‌شود. این ویژگی می‌تواند به 1 یا مقدار بیشتری تنظیم شود. به طور پیش‌فرض این ویژگی برابر 10 است. یک صف دستورات برای اتصال پیاده‌سازی شده است تا از عملیات‌های چندریسه پشتیبانی کند. دستورات شامل ساده‌ترین عملیات‌های تعریف‌شده در پروتکل هستند، مانند Nooop، Authenticate و غیره. کاربر ممکن است تعداد دستورات بیشتری نسبت به تعداد اتصالات موجود شروع به اجرا کند، اما آن‌ها تنها زمانی اجرا می‌شوند که کلاینت بتواند برای عملیات اتصال ایجاد کند.

روش‌های استفاده از کلاینت‌های ایمیل در محیط چندریسه

کلاینت‌های ایمیل رفتارهای زیر را دارند:

1. در صورتی که MaxConnectionsPerServer = 1 باشد، کلاینت یک اتصال ایجاد می‌کند، احراز هویت و مجوزدهی را انجام می‌دهد. این اتصال تا زمان از بین رفتن کلاینت در حالت کاری حفظ می‌شود. تمام عملیات‌ها از ریسمان‌های مختلف به یک صف دستورات که در اتصال اصلی قرار دارد هدایت می‌شوند.

2. در صورتی که MaxConnectionsPerServer > 1 باشد، کلاینت تعداد مورد نیاز اتصالات را ایجاد می‌کند و برای هر اتصال احراز هویت و مجوزدهی را انجام می‌دهد. یک اتصال به عنوان اتصال اصلی رزرو می‌شود. این اتصال تا زمانی که کلاینت از بین برود در حالت کاری حفظ می‌شود. سایر اتصالات بر حسب نیاز ایجاد و از بین می‌روند. حداکثر تعداد این اتصالات توسط ویژگی MaxConnectionsPerServer تعریف می‌شود، به طور مثال اگر MaxConnectionsPerServer = 2 باشد، یک اتصال به عنوان اصلی رزرو می‌شود و اتصال دوم به عنوان اضافه برای عملیات‌هایی که در ریسمان‌های دیگر اجرا می‌شوند استفاده می‌شود. به همین ترتیب اگر MaxConnectionsPerServer = 3 باشد، اولین اتصال به عنوان اصلی رزرو شده و دو اتصال دیگر به عنوان اضافه برای عملیات‌های اجرا شده در ریسمان‌های دیگر استفاده می‌شوند. در صورتی که درخواست بعدی برای اتصال از ریسمان جدیدی بیاید و تمام اتصالات در حال استفاده باشند، کلاینت تا کاهش تعداد اتصالات استفاده‌شده صبر می‌کند….

مثال‌ها

کاربر می‌تواند عملیات‌ها را در ریسمان‌های مختلف به چند روش اجرا کند. این روش‌ها به دو نوع تقسیم می‌شوند:

1. کاربر از متدهای ناهمزمان (Begin/End) تعریف شده در کلاینت استفاده می‌کند. در این حالت کلاینت ایمیل وقتی نیاز باشد ریسمان‌های جدیدی راه‌اندازی می‌کند. در کلاینت یک صف وظایف پیاده‌سازی شده است (لطفاً آن را با صف دستورات در اتصال اشتباه نگیرید). یک وظیفه می‌تواند اجرا شود اگر اتصال در دسترس باشد. زمانی که تعداد اتصالات استفاده‌شده کمتر از مقدار محدودیت می‌شود، کلاینت یک اتصال جدید ایجاد می‌کند، ریسمان برای وظیفه فعلی می‌سازد و این وظیفه را اجرا می‌کند. مثال استفاده از عملیات‌های ناهمزمان:

// Create an imapclient with host, user and password
ImapClient client = new ImapClient();
client.setHost("domain.com");
client.setUsername("username");
client.setPassword("password");
client.selectFolder("InBox");

ImapMessageInfoCollection messages = client.listMessages();
IAsyncResult res1 = client.beginFetchMessage(messages.get_Item(0).getUniqueId());
IAsyncResult res2 = client.beginFetchMessage(messages.get_Item(1).getUniqueId());
MailMessage msg1 = client.endFetchMessage(res1);
MailMessage msg2 = client.endFetchMessage(res2);

2. کاربر می‌تواند ریسمان‌ها را با استفاده از اشیائی مانند Thread، ExecutorService یا هر شیء دیگری که برای این منظور در نظر گرفته شده است، ایجاد کند. همچنین کاربر می‌تواند از ریسمان‌های ایجاد شده در کدهای شخص ثالث استفاده کند. در این حالت کلاینت دو مدل رفتار دارد

a. اگر کاربر ایجاد اتصالات اضافه برای عملیات در ریسمان را در نظر نگرفته باشد، تمام عملیات‌های این ریسمان به صف دستورات برای اتصال اصلی ارسال می‌شوند. یک مثال از عملیات‌ها در یک ریسمان اضافه بدون ایجاد اتصال جدید، تمام تراکنش‌ها از طریق اتصال اصلی انجام می‌شود:

/**
 * Creates an executor service with a fixed pool size, that will time 
 * out after a certain period of inactivity.
 * 
 * @param poolSize The core- and maximum pool size
 * @param keepAliveTime The keep alive time
 * @param timeUnit The time unit
 * @return The executor service
 */
private static ExecutorService createFixedTimeoutExecutorService(
    int poolSize, long keepAliveTime, TimeUnit timeUnit)
{
    ThreadPoolExecutor e = 
        new ThreadPoolExecutor(poolSize, poolSize,
            keepAliveTime, timeUnit, new LinkedBlockingQueue<Runnable>());
    e.allowCoreThreadTimeOut(true);
    return e;
}

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        client.selectFolder("folderName");
        ImapMessageInfoCollection messageInfoCol = client.listMessages();
        for (ImapMessageInfo messageInfo : messageInfoCol) {
            list.add(client.fetchMessage(messageInfo.getUniqueId()));
        }
    }
});

b. وقتی کاربر متدی را اجرا می‌کند تا اتصال جدیدی برای ریسمان اضافه ایجاد کند، این ریسمان تا زمانی که مقدار سهمیه برای اتصالات جدید تغییر کند مسدود می‌شود تا اتصال جدید مجاز شود. سپس اتصال جدید ایجاد می‌شود. این اتصال به عنوان اتصال پیش‌فرض برای تمام عملیات‌ها در این ریسمان تنظیم می‌گردد. پس از اتمام تمام عملیات‌ها در این ریسمان، باید اتصال را از بین برد. برای ایجاد اتصالات جدید از متد CredentialsByHostClient.CreateConnection استفاده کنید. این متد شی‌ای را برمی‌گرداند که اینترفیس IDisposable را پیاده‌سازی می‌کند. برای آزادسازی اتصال باید متد Dispose فراخوانی شود. ایجاد و از بین بردن اتصال باید در داخل ریسمانی که عملیات‌های ایمیل در آن اجرا می‌شود انجام شود. تلاش برای ایجاد اتصال جدید در ریسمانی که کلاینت ایمیل در آن ساخته شده است منجر به خطا می‌شود، زیرا این ریسمان در این لحظه نمی‌تواند برای ایجاد اتصال جدید استفاده شود. همچنین ایجاد اتصال جدید زمانی ممکن نیست که MaxConnectionsPerServer = 1 باشد. یک مثال کد برای ایجاد اتصال جدید در ریسمان اضافه:

final List<MailMessage> list = new ArrayList<MailMessage>();

ExecutorService executor = createFixedTimeoutExecutorService(1, 1000, TimeUnit.MILLISECONDS);

executor.execute(new Runnable() {
    public void run() {
        IConnection connection = client.createConnection();
        try {
            client.selectFolder(connection, "FolderName");
            ImapMessageInfoCollection messageInfoCol = client.listMessages(connection);
            for (ImapMessageInfo messageInfo : messageInfoCol)
                list.add(client.fetchMessage(connection, messageInfo.getUniqueId()));
        } finally {
            connection.dispose();
        }
    }
});

پیشنهادات

شایان ذکر است که اگر کاربر تمام دستورات را به اتصال اصلی ارسال کند، ممکن است وضعیتی پیش آید که دستورات از ریسمان‌های متفاوت با هم مخلوط شوند. کاربر باید بفهمد که کدام دستورات به ترتیب خود وابسته هستند و برای همگام‌سازی این دستورات اقدامات لازم را انجام دهد. همچنین لازم است امکان اجرای دستورات در جلسات متفاوت (IMAP/POP3) در نظر گرفته شود. عملیات‌های پرهزینه زمانی، مانند FetchMessage، AppendMessage و Send. احتمالاً منطقی است که این عملیات‌ها را با ریسمان و اتصال جدید انجام داد. اما عملیات‌های سریع مانند Delete منطقی است که با اتصال اصلی انجام شوند. لطفاً توجه داشته باشید که مقداردهی اولیه اتصال جدید یک عملیات زمان‌بر است.