Logo Search packages:      
Sourcecode: yaskkserv version File versions  Download package

skk_server.hpp

/*
  Copyright (C) 2005, 2006, 2007, 2008 Tadashi Watanabe <wac@umiushi.org>

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#ifndef SKK_SERVER_H
#define SKK_SERVER_H




#include "skk_architecture.hpp"
#include "skk_socket.hpp"
#include "skk_utility.hpp"
#include "skk_dictionary.hpp"
#include "skk_syslog.hpp"




#ifdef YASKKSERV_DEBUG
#define SKK_MEMORY_DEBUG
#endif  // YASKKSERV_DEBUG

#ifdef SKK_MEMORY_DEBUG
//
// SKK_MEMORY_DEBUG が定義されている場合、バッファを
// SKK_MEMORY_DEBUG_MARGIN_SIZE * 2 だけ多く確保します。
//
// SKK_MEMORY_DEBUG_MARGIN_SIZE の大きさを持つ領域はバッファの前後に付加さ
// れます。マージンは memory_debug_set() で初期設定され、
// memory_debug_check() でチェックします。
//
// SKK_MEMORY_DEBUG が定義されない場合のバッファ
//    +----------+
//    | バッファ |
//    +----------+
//
// SKK_MEMORY_DEBUG が定義される場合のバッファ
//    +----------+----------+----------+
//    | マージン | バッファ | マージン |
//    +----------+----------+----------+
//
#define SKK_MEMORY_DEBUG_MARGIN_SIZE 4
inline void skk_memory_debug_set_(char *p)
{
        *(p + 0) = 0xde;
        *(p + 1) = 0xad;
        *(p + 2) = 0xbe;
        *(p + 3) = 0xef;
        DEBUG_PRINTF("skk_memory_debug_set_() %p\n"
                     ,
                     p);
}




inline void skk_memory_debug_check_(const char *p)
{
        if (((*(p + 0) & 0xff) != 0xde) ||
            ((*(p + 1) & 0xff) != 0xad) ||
            ((*(p + 2) & 0xff) != 0xbe) ||
            ((*(p + 3) & 0xff) != 0xef))
        {
                DEBUG_PRINTF("skk_memory_debug_check_() %p failed\n"
                             "0x %02x,%02x,%02x,%02x\n"
                             ,
                             p,
                             *(p + 0) & 0xff,
                             *(p + 1) & 0xff,
                             *(p + 2) & 0xff,
                             *(p + 3) & 0xff);
                DEBUG_ASSERT(0);
        }
}
#endif  // SKK_MEMORY_DEBUG




/// SKK サーバです。
00095 class SkkServer
{
        SkkServer(SkkServer &source);
        SkkServer& operator=(SkkServer &source);




public:
        virtual ~SkkServer()
        {
                for (int i = 0; i != max_connection_; ++i)
                {
#ifdef SKK_MEMORY_DEBUG
                        delete[] ((work_ + i)->read_buffer - SKK_MEMORY_DEBUG_MARGIN_SIZE);
#else  // SKK_MEMORY_DEBUG
                        delete[] (work_ + i)->read_buffer;
#endif  // SKK_MEMORY_DEBUG
                }

                delete[] work_;

                syslog_.printf(1,
                               SkkSyslog::LEVEL_INFO,
                               "terminated");
        }


        SkkServer(const char *identifier,
                  int port,
                  int log_level) :
                socket_(),
                syslog_(identifier,
                        log_level),
                work_(0),
                port_(port),
                max_connection_(0),
                listen_queue_(0),
                file_descriptor_(0)
        {
        }


        void printFirstSyslog(void)
        {
                syslog_.printf(1,
                               SkkSyslog::LEVEL_INFO,
                               "version " YASKKSERV_VERSION " (port=%d)",
                               port_);
        }


        virtual bool mainLoop() = 0;


protected:
/// send() に成功すれば真を返します。偽を返した場合 Work::closeAndReset() すべきです。
00152         bool send(int file_descriptor,
                  const void *data,
                  int data_size)
        {
                bool result = false;

                int send_size = 0;
                for (;;)
                {
                        int send_result = socket_.send(file_descriptor,
                                                       data,
                                                       data_size);
                        if (send_result == -1)
                        {
                                break;
                        }
                        else
                        {
                                send_size += send_result;
                                if (send_size == data_size)
                                {
                                        result = true;
                                        break;
                                }
                        }
                }

                return result;
        }


/// MainLoop() のイニシャライズ処理です。
00184         bool main_loop_initialize(int max_connection,
                                  int listen_queue)
        {
                max_connection_ = max_connection;
                listen_queue_ = listen_queue;


// delete[] はデストラクタで。
                work_ = new Work[max_connection_];

                for (int i = 0; i != max_connection_; ++i)
                {
#ifdef SKK_MEMORY_DEBUG
                        (work_ + i)->read_buffer = new char[SKK_MEMORY_DEBUG_MARGIN_SIZE + READ_BUFFER_SIZE + SKK_MEMORY_DEBUG_MARGIN_SIZE];
                        (work_ + i)->read_buffer += SKK_MEMORY_DEBUG_MARGIN_SIZE;
                        skk_memory_debug_set_((work_ + i)->read_buffer - SKK_MEMORY_DEBUG_MARGIN_SIZE);
                        skk_memory_debug_set_((work_ + i)->read_buffer - SKK_MEMORY_DEBUG_MARGIN_SIZE + SKK_MEMORY_DEBUG_MARGIN_SIZE + READ_BUFFER_SIZE);
#else  // SKK_MEMORY_DEBUG
                        (work_ + i)->read_buffer = new char[READ_BUFFER_SIZE];
#endif  // SKK_MEMORY_DEBUG
                        *((work_ + i)->read_buffer + 0) = '\0';
                }

                file_descriptor_ = socket(AF_INET,
                                          SOCK_STREAM,
                                          0);
                if (file_descriptor_ == -1)
                {
                        return false;
                }

                {
                        fcntl(file_descriptor_,
                              F_SETFL,
                              O_NONBLOCK);
                }

                {
                        int optval;
                        int result;

                        optval = 1;
                        result = setsockopt(file_descriptor_,
                                            SOL_SOCKET,
                                            SO_REUSEADDR,
                                            &optval,
                                            sizeof(optval));
                }


                struct sockaddr_in socket_connect;
                SkkUtility::clearMemory(&socket_connect,
                                        sizeof(socket_connect));
                socket_connect.sin_family = AF_INET;
                socket_connect.sin_addr.s_addr = INADDR_ANY;
                socket_connect.sin_port = htons(port_);
                int retry = 3;
                while (bind(file_descriptor_,
                            reinterpret_cast<struct sockaddr *>(&socket_connect),
                            sizeof(socket_connect)) == -1)
                {
                        if (--retry <= 0)
                        {
                                close(file_descriptor_);
                                SkkUtility::printf("socket bind failed\n");
                                return false;
                        }
                        SkkUtility::sleep(1);
                }

                if ((listen(file_descriptor_,
                            listen_queue_)) == -1)
                {
                        close(file_descriptor_);
                        return false;
                }

                return true;
        }


/// 複数辞書を扱うために必要な情報を取得します。 found_times は「見出し」が見付かった辞書の個数、 candidate_length は見付かった「見出し」に対する「変換文字列」中の candidate の個数、 total_henkanmojiretsu_size は全ての「変換文字列」の合計サイズです。 found_times, candidate_length と total_henkanmojiretsu_size は必ず設定されます。
00266         void main_loop_get_plural_dictionary_information(int work_index,
                                                         SkkDictionary *skk_dictionary,
                                                         int skk_dictionary_length,
                                                         int &found_times,
                                                         int &candidate_length,
                                                         int &total_henkanmojiretsu_size)
        {
                found_times = 0;
                candidate_length = 0;
                total_henkanmojiretsu_size = 0;
                for (int h = 0; h != skk_dictionary_length; ++h)
                {
                        if ((skk_dictionary + h)->search((work_ + work_index)->read_buffer + 1))
                        {
                                const int cr_size = 1;
                                total_henkanmojiretsu_size += (skk_dictionary + h)->getHenkanmojiretsuSize() + cr_size;
                                candidate_length += SkkUtility::getCandidateLength((skk_dictionary + h)->getHenkanmojiretsuPointer());
                                ++found_times;
                        }
                }
        }


/// 「見出し」の探索に成功したものとして "1" を付加した現在のバッファの「変換文字列」を send() します。
00290         void main_loop_send_found(int work_index,
                                  SkkDictionary *skk_dictionary)
        {
//
// mmap を使わない場合、メモリコピーを避けるためブロックリードバッファ
// を書き換え、 send() 後に戻しています。一見危なっかしいですが、 '1'
// を書き込む p - 1 は RAM であることが保証され、かつ必ず存在します。具
// 体的にはブロックバッファ中の以下の位置にあたります。
//
// mmap を使う場合、または ROM 上のデータを扱う場合には send() を複数回
// 呼ぶなどの処理が必要になることに注意が必要です。
//
//     p - 1
//       |
//       v
// みだしβ /変換文字列0/変換文字列1/
//        ^
//        |
//        p == getHenkanmojiretsuPointer()
//
                char *p = const_cast<char*>(skk_dictionary->getHenkanmojiretsuPointer());
                const int protocol_size = 1;
                const int cr_size = 1;
                char backup = *(p - 1);

                *(p - 1) = '1';

                if (!send((work_ + work_index)->file_descriptor,
                          p - 1,
                          protocol_size + skk_dictionary->getHenkanmojiretsuSize() + cr_size))
                {
                        (work_ + work_index)->closeAndReset();
                }

                *(p - 1) = backup;
        }


/// 「見出し」の探索に失敗したものとして "4" を付加した現在のバッファの「見出し」を send() します。
/**
 * 現在のバッファの末尾が \\n でない場合は \\n を追加して send() します。
 */
00332         void main_loop_send_not_found(int work_index,
                                      int recv_result)
        {
// 2 回に分けて send() すれば素直になりますが、メモリをいじりまわして
// 1 回の send() にした方が高速なため、以下のようなコードになっています。
                DEBUG_ASSERT(recv_result > 1);


// read_buffer は recv() するサイズより大き目に確保されているので +
// recv_result にアクセスしても問題ありません。
                char backup_2 = *((work_ + work_index)->read_buffer + recv_result);
                int send_size;
                if (*((work_ + work_index)->read_buffer + recv_result - 1) != '\n')
                {
// 終端が  '\n' でなければ '\n' を追加します。
                        *((work_ + work_index)->read_buffer + recv_result) = '\n';
                        send_size = recv_result + 1;
                }
                else
                {
                        send_size = recv_result;
                }


                char backup = *((work_ + work_index)->read_buffer);
                *((work_ + work_index)->read_buffer) = '4';
                if (!send((work_ + work_index)->file_descriptor,
                          (work_ + work_index)->read_buffer,
                          send_size))
                {
                        (work_ + work_index)->closeAndReset();
                }
                *((work_ + work_index)->read_buffer) = backup;


                *((work_ + work_index)->read_buffer + recv_result) = backup_2;
        }


/// mainLoop() で select() します。
00372         int main_loop_select(fd_set &fd_set_read)
        {
                int file_descriptor_maximum = file_descriptor_;

                FD_ZERO(&fd_set_read);
                FD_SET(file_descriptor_,
                       &fd_set_read);

                for (int i = 0; i != max_connection_; ++i)
                {
                        if ((work_ + i)->flag)
                        {
                                FD_SET((work_ + i)->file_descriptor,
                                       &fd_set_read);
                                if ((work_ + i)->file_descriptor > file_descriptor_maximum)
                                {
                                        file_descriptor_maximum = (work_ + i)->file_descriptor;
                                }
                        }
#ifdef SKK_MEMORY_DEBUG
                        skk_memory_debug_check_((work_ + i)->read_buffer - SKK_MEMORY_DEBUG_MARGIN_SIZE);
                        skk_memory_debug_check_((work_ + i)->read_buffer - SKK_MEMORY_DEBUG_MARGIN_SIZE + SKK_MEMORY_DEBUG_MARGIN_SIZE + READ_BUFFER_SIZE);
#endif  // SKK_MEMORY_DEBUG
                }

                int n = select(file_descriptor_maximum + 1,
                               &fd_set_read,
                               0,
                               0,
                               0);

                if ((n == -1) &&
                    (errno == EINTR))
                {
                        syslog_.printf(1,
                                       SkkSyslog::LEVEL_INFO,
                                       "caught signal");
                }

                return n;
        }


/// mainLoop() で辞書の更新チェックをします。
00416         int main_loop_check_reload_dictionary(SkkDictionary *skk_dictionary,
                                              int skk_dictionary_length,
                                              const char * const *dictionary_filename_table,
                                              bool dictionary_check_update_flag)
        {
                int result = 0;

                if (dictionary_check_update_flag &&
                    dictionary_filename_table)
                {
                        int total_read_size = 0;
                        for (int i = 0; i != max_connection_; ++i)
                        {
                                if ((work_ + i)->flag)
                                {
                                        total_read_size += (work_ + i)->read_process_index;
                                }
                        }
                        if (total_read_size == 0)
                        {
                                for (int i = 0; i != skk_dictionary_length; ++i)
                                {
                                        if (*(dictionary_filename_table + i))
                                        {
                                                bool update_flag;
                                                if ((skk_dictionary + i)->isUpdateDictionary(update_flag,
                                                                                             *(dictionary_filename_table + i)))
                                                {
                                                        if (update_flag)
                                                        {
                                                                ++result;
                                                                (skk_dictionary + i)->close();
                                                                (skk_dictionary + i)->open(*(dictionary_filename_table + i));

                                                                syslog_.printf(1,
                                                                               SkkSyslog::LEVEL_INFO,
                                                                               "dictionary \"%s\" (index = %d) updated",
                                                                               *(dictionary_filename_table + i),
                                                                               i);
                                                        }
                                                }
                                                else
                                                {
                                                        syslog_.printf(1,
                                                                       SkkSyslog::LEVEL_WARNING,
                                                                       "dictionary \"%s\" (index = %d) check failed",
                                                                       *(dictionary_filename_table + i),
                                                                       i);
                                                }
                                        }
                                }
                        }
                }

                return result;
        }


/// mainLoop() で accept() します。
00475         bool main_loop_accept(fd_set &fd_set_read,
                              int select_result)
        {
                socklen_t length = sizeof(struct sockaddr_in);
                if (FD_ISSET(file_descriptor_,
                             &fd_set_read))
                {
                        int counter = 0;
                        for (int i = 0; i != max_connection_; ++i)
                        {
                                if ((work_ + i)->flag == false)
                                {
                                        int fd = accept(file_descriptor_,
                                                        reinterpret_cast<struct sockaddr*>(&(work_ + i)->socket),
                                                        reinterpret_cast<socklen_t*>(&length));
                                        if (fd == -1)
                                        {
//                                         goto ERROR_BREAK;
//                                         return false;
                                        }
                                        else
                                        {
                                                syslog_.printf(2,
                                                               SkkSyslog::LEVEL_INFO,
                                                               "connected from %s",
                                                               inet_ntoa((work_ + i)->socket.sin_addr));
                                                (work_ + i)->flag = true;
                                                (work_ + i)->file_descriptor = fd;
                                                ++counter;
                                                if (counter >= select_result)
                                                {
                                                        break;
                                                }
                                        }
                                }
                        }
                        if (counter == 0)
                        {
                                struct sockaddr_in dummy_socket;
                                int dummy_fd = accept(file_descriptor_,
                                                      reinterpret_cast<struct sockaddr*>(&dummy_socket),
                                                      reinterpret_cast<socklen_t*>(&length));
                                if (dummy_fd == -1)
                                {
                                        return false;
                                }
                                close(dummy_fd);
                        }
                }

                return true;
        }


/// MainLoop() で recv() すべきかどうかを返します。
00530         bool main_loop_is_recv(int work_index,
                               fd_set &fd_set_read)
        {
                if ((work_ + work_index)->flag &&
                    FD_ISSET((work_ + work_index)->file_descriptor,
                             &fd_set_read))
                {
                        return true;
                }
                else
                {
                        return false;
                }
        }


/// MainLoop() で recv() して read_buffer へ読み込みます。 read_buffer を参照してはならないならば真を返します。
00547         bool main_loop_recv(int work_index,
                            int &recv_result,
                            bool &error_break_flag)
        {
                bool result = true;

                recv_result = socket_.receive((work_ + work_index)->file_descriptor,
                                              (work_ + work_index)->read_buffer + (work_ + work_index)->read_process_index,
                                              MIDASI_SIZE + MIDASI_TERMINATOR_SIZE - (work_ + work_index)->read_process_index);

                error_break_flag = false;

                if (recv_result == -1)
                {
                        switch (errno)
                        {
                        case EAGAIN:
                        case EINTR:
                        case ECONNABORTED:
                        case ECONNREFUSED:
                        case ECONNRESET:
                                break;

                        default:
                                error_break_flag = true;
                                goto ERROR_BREAK;
                        }
                        (work_ + work_index)->reset();
                }
                else if (recv_result == 0)
                {
                        close((work_ + work_index)->file_descriptor);
                        (work_ + work_index)->reset();
                        (work_ + work_index)->file_descriptor = 0;
                        (work_ + work_index)->flag = false;
                }
                else if (recv_result > MIDASI_SIZE + MIDASI_TERMINATOR_SIZE)
                {
// recv() で指定した読み込みサイズは、ここで比較する値より小さいので、
// ここに来ることはありません。
                        DEBUG_ASSERT(0);
                        (work_ + work_index)->reset();
                }
                else
                {
                        result = false;
                }

        ERROR_BREAK:

                return result;
        }


/// mainLoop() で "0" 相当の処理をします。
00602         void main_loop_0(int work_index)
        {
                close((work_ + work_index)->file_descriptor);
                (work_ + work_index)->file_descriptor = 0;
                (work_ + work_index)->flag = false;
        }


/// mainLoop() で "2" 相当の処理をします。
00611         void main_loop_2(int work_index,
                         const char *version_string,
                         int version_string_size)
        {
                if (!send((work_ + work_index)->file_descriptor,
                          version_string,
                          version_string_size - 1))
                {
                        (work_ + work_index)->closeAndReset();
                }
        }


/// mainLoop() で "3" 相当の処理をします。
00625         void main_loop_3(int work_index)
        {
                const char hostname[] = "hostname:addr:...: ";

                if (!send((work_ + work_index)->file_descriptor,
                          hostname,
                          sizeof(hostname) - 1))
                {
                        (work_ + work_index)->closeAndReset();
                }
        }


/// mainLoop() で不正なコマンドの処理をします。
00639         void main_loop_illegal_command(int work_index)
        {
                const char result[] = "0\n";

                if (!send((work_ + work_index)->file_descriptor,
                          result,
                          sizeof(result) - 1))
                {
                        (work_ + work_index)->closeAndReset();
                }
        }


/// バッファをリセットすべきならば真を返します。
/**
 * 偽を返すのは 1 度で recv() しきれなかった場合だけです。
 *
 * その他不正なプロトコルなどでもバッファはリセットすべきなので、真を返
 * します。
 *
 * recv 文字列に '\n' を含むにもかかわらず ' ' が存在しない場合は不正な
 * プロトコルとして illegal_protocol_flag を真にします。このとき呼び出
 * し側では "4" を返すべきです。
 */
00663         bool main_loop_check_buffer(int work_index,
                                    int recv_result,
                                    bool &illegal_protocol_flag)
        {
                bool result = true;
                illegal_protocol_flag = false;

                DEBUG_ASSERT(recv_result > 0);
                DEBUG_ASSERT(recv_result <= MIDASI_SIZE + MIDASI_TERMINATOR_SIZE);

                bool cr_found_flag = false;
                bool space_found_flag = false;
                for (int h = recv_result - 1; h >= 0; --h)
                {
                        int tmp = (work_ + work_index)->read_process_index + h;
                        if (*((work_ + work_index)->read_buffer + tmp) == '\n')
                        {
                                cr_found_flag = true;
                        }
                        if (*((work_ + work_index)->read_buffer + tmp) == ' ')
                        {
                                space_found_flag = true;
                                break;
                        }
                }

                if (!space_found_flag)
                {
                        if (cr_found_flag)
                        {
// '\n' を含むにもかかわらず ' ' が存在しないので不正なプロトコルです。
                                illegal_protocol_flag = true;
                        }
                        else
                        {
// 終端に '\n' も ' ' も存在しないということは recv() で読み切れていな
// いため、バッファをリセットしません。
                                result = false;
                        }
                }

                return result;
        }


/// MainLoop() で必要ならばバッファリセットをおこないます。
00709         void main_loop_check_buffer_reset(int work_index,
                                          int recv_result,
                                          bool buffer_reset_flag)
        {
                if (buffer_reset_flag)
                {
                        (work_ + work_index)->reset();
                }
                else
                {
                        (work_ + work_index)->read_process_index += recv_result;
                        if ((work_ + work_index)->read_process_index > MIDASI_SIZE + MIDASI_TERMINATOR_SIZE)
                        {
// 不正なリクエストです。
                                (work_ + work_index)->reset();
                        }
                }
        }


/// MainLoop() のファイナライズ処理です。
00730         bool main_loop_finalize()
        {
                for (int i = 0; i != max_connection_; ++i)
                {
                        if ((work_ + i)->flag)
                        {
                                close((work_ + i)->file_descriptor);
                                (work_ + i)->file_descriptor = 0;
                                (work_ + i)->flag = false;
                        }
                }

                close(file_descriptor_);

                return true;
        }




protected:
        enum
        {
// でエンコード時は先頭に \\1 が付くため 1 バイト大きくなる可能性があり
// ます。
                MIDASI_SIZE = 1 + 510,

// 見出しのターミネータとなるスペース (0x20) のサイズです。
                MIDASI_TERMINATOR_SIZE = 1,

// main_loop_send_not_found() などでマージン領域を利用するため、絶対に
// 必要です。
                MIDASI_MARGIN_SIZE = 4,

// struct Work の read_buffer のサイズです。
                READ_BUFFER_SIZE = MIDASI_SIZE + MIDASI_TERMINATOR_SIZE + MIDASI_MARGIN_SIZE
        };


        struct Work
        {
        private:
                Work(Work &source);
                Work& operator=(Work &source);




        public:
                Work() :
                        read_buffer(0),
                        file_descriptor(0),
                        read_process_index(0),
                        socket(),
                        flag(false)
                {
                        SkkUtility::clearMemory(&socket,
                                                sizeof(socket));
                }


                void reset()
                {
                        read_process_index = 0;
                        *(read_buffer + 0) = '\0';
                }


                void closeAndReset()
                {
                        close(file_descriptor);
                        reset();
                        file_descriptor = 0;
                        flag = false;
                }




                char *read_buffer;
                int file_descriptor;
                int read_process_index;
                struct sockaddr_in socket;
                bool flag;
        };




        SkkSocket socket_;
        SkkSyslog syslog_;
        Work *work_;
        int port_;
        int max_connection_;
        int listen_queue_;
        int file_descriptor_;
};




#endif  // SKK_SERVER_H

Generated by  Doxygen 1.6.0   Back to index