※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

以下を見て、TCPの順序保証とUDPのメッセージ境界保証のよいとこどりをした「SCTP」というプロトコルがあることを知った。
 http://www.ibm.com/developerworks/jp/linux/library/l-sctp/#resources

■インストール
 Fedora14にはパッケージがなかったので、以下からダウンロードした。
  http://lksctp.sourceforge.net/faqs.html
 rpmパッケージもあったが、32bit用しかないようなので、tarボールをダウンロードした。
 インストールは普通にconfigure、make、make installでokだった。

■echoサーバー
 上述のdeveloperworksにもサンプルがあったが、TCPのechoサーバーを改造して、echoサーバーを作ってみた。
 一応TCPとSCTP両方使えるよう「protocol_flg // 0:TCP,1:SCTP」変数の書き換えでどちらも使えるようにした。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <netinet/in.h>
#include <netinet/sctp.h>

#define PORT    8888    /* Listenポート */
#define MAX_BUFFER 1024    /* 受信バッファサイズ */

#define LOCALTIME_STREAM	0
#define GMT_STREAM		1

void reply(void *);
int protocol_flg = 1;               // 0:TCP,1:SCTP 

int main(int argc, char **argv)
{
   struct sockaddr_in saddr;
   struct sockaddr_in caddr;
   struct sctp_initmsg initmsg;
   int listen_fd;
   int conn_fd;
   int ret;
   int len = sizeof(struct sockaddr_in);
   pthread_t worker;

   /* ソケットの生成 */
   if(protocol_flg == 1){
       /* Create SCTP TCP-Style Socket */
       listen_fd = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );
   }else {
       listen_fd = socket( AF_INET, SOCK_STREAM, 0 );
   }
   if (listen_fd < 0) {
       perror("socket");
       exit(EXIT_FAILURE);
   }

   bzero((char *)&saddr, sizeof(saddr));

   /* ソケットのbind */
   saddr.sin_family        = AF_INET;
   saddr.sin_addr.s_addr   = INADDR_ANY;
   saddr.sin_port          = htons(PORT);
   if (bind(listen_fd, (struct sockaddr *)&saddr, len) < 0) {
       perror("bind");
       exit(EXIT_FAILURE);
   }

   if (protocol_flg == 1) { //SCTP
       /* Specify that a maximum of 5 streams will be available per socket */
       memset(&initmsg, 0, sizeof (initmsg));
       initmsg.sinit_num_ostreams = 5;
       initmsg.sinit_max_instreams = 5;
       initmsg.sinit_max_attempts = 4;
       /*
       if (setsockopt(listen_fd, IPPROTO_SCTP, SCTP_INITMSG,
               &initmsg, sizeof (initmsg))) {
           perror("setsockopt");
           exit(EXIT_FAILURE);
       }
       */
       if (setsockopt(listen_fd, SOL_SCTP, SCTP_INITMSG,
               &initmsg, sizeof (initmsg))) {
           perror("setsockopt");
           exit(EXIT_FAILURE);
       }
   }

   /* ポートをListen */
   if (listen(listen_fd, SOMAXCONN) < 0) {
       perror("listen");
       exit(EXIT_FAILURE);
   }
   printf("Start Listening Port : %d...\n", PORT);

   while (1) {
       /* 接続要求のaccept */
       if ((conn_fd = accept(listen_fd, (struct sockaddr *)&caddr, &len)) < 0) {
           perror("accept");
           exit(EXIT_FAILURE);
       }

       /* スレッド生成 */
       if (pthread_create( &worker, NULL, (void *)reply, (void *)conn_fd) != 0) {
           perror("pthread_create");
           exit(EXIT_FAILURE);
       }
       pthread_detach(worker);
   }
   /* Listenソケットのclose */
   close(listen_fd);

}

void reply(void *fd) {
   int ret;
   int conn_fd = (int)fd;
   struct sctp_sndrcvinfo sndrcvinfo;
   struct sctp_event_subscribe events;
   struct sockaddr strsockaddr;
   socklen_t addrlen = sizeof(strsockaddr);
   int rsize, ssize;
   int flags;
   char buf[MAX_BUFFER]; /* 受信バッファ */

   if (protocol_flg == 1) { //SCTP
       /* Enable receipt of SCTP Snd/Rcv Data via sctp_recvmsg */
       /* これがないとsndrcvinfoが設定されなかった。*/
       memset((void *) &events, 0, sizeof (events));
       events.sctp_data_io_event = 1;
       ret = setsockopt(conn_fd, SOL_SCTP, SCTP_EVENTS,
               (const void *) &events, sizeof (events));
   }

   /* 送信されたデータのread */
   do {
       if(protocol_flg == 1){
           printf("sctp_recvmsg\n");
           rsize = sctp_recvmsg( conn_fd, (void *)buf, sizeof(buf),
                       &strsockaddr, &addrlen, &sndrcvinfo, &flags );
       } else {
           rsize = recvfrom(conn_fd, buf, sizeof(buf), 0,
                &strsockaddr, &addrlen);
       }

       if (rsize == 0) { /* TCPはクラアイントが接続を切ったとき sctpではありえない? */
               break;
       } else if (rsize == -1) {
               perror("recv");
               exit(EXIT_FAILURE);
       }
       printf("### Recv Buf ###\n");
       hexdump(buf, rsize);
       printf("\n### Recv Buf ### sinfo_stream :%d\n",sndrcvinfo.sinfo_stream);
       hexdump(&sndrcvinfo, sizeof(sndrcvinfo));
   
       if(protocol_flg == 1){
           printf("sctp_sendmsg\n");
           hexdump(buf, rsize);
           /*
           ssize = sctp_sendmsg( conn_fd, buf, rsize,
                        NULL, 0, 0, 0, sndrcvinfo.sinfo_stream, 0, 0 );
            */
           ssize = sctp_sendmsg( conn_fd, buf, rsize,
                        NULL, 0, 0, 0, sndrcvinfo.sinfo_stream, 0, 0 );
       } else {
           ssize = write(conn_fd, buf, rsize);
       }

   } while (1);

   if ( close(conn_fd) < 0) {
       perror("close");
       exit(EXIT_FAILURE);
   }

   printf("Connection closed.\n");
}

■SCTPクライアント
 TCPのechoサーバーならtelnetなどでためすことができるが、SCTPだとtelnetではだめなので、現在時刻を取得してサーバと送受信するクライアントを書いてみた。(developerworksのサンプルの改造ですが・・・)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <time.h>

#define MAX_BUFFER	1024
#define MY_PORT_NUM	8888
#define LOCALTIME_STREAM	0
#define GMT_STREAM		1

int main() {
   int connSock, in, k, ret, flags;
   struct sockaddr_in servaddr;
   struct sctp_status status;
   struct sctp_sndrcvinfo sndrcvinfo;
   struct sctp_event_subscribe events;
   struct sctp_initmsg initmsg;
   char buffer[MAX_BUFFER + 1];
   time_t currentTime;

   /* Create an SCTP TCP-Style Socket */
   connSock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);

   /* Specify that a maximum of 5 streams will be available per socket */
   memset(&initmsg, 0, sizeof (initmsg));
   initmsg.sinit_num_ostreams = 5;
   initmsg.sinit_max_instreams = 5;
   initmsg.sinit_max_attempts = 4;
   ret = setsockopt(connSock, IPPROTO_SCTP, SCTP_INITMSG,
           &initmsg, sizeof (initmsg));

   /* Specify the peer endpoint to which we'll connect */
   bzero((void *) &servaddr, sizeof (servaddr));
   servaddr.sin_family = AF_INET;
   servaddr.sin_port = htons(MY_PORT_NUM);
   servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

   /* Connect to the server */
   ret = connect(connSock, (struct sockaddr *) &servaddr, sizeof (servaddr));

   /* Enable receipt of SCTP Snd/Rcv Data via sctp_recvmsg */
   memset((void *) &events, 0, sizeof (events));
   events.sctp_data_io_event = 1;
   ret = setsockopt(connSock, SOL_SCTP, SCTP_EVENTS,
           (const void *) &events, sizeof (events));

   /* Read and emit the status of the Socket (optional step) */
   in = sizeof (status);
   ret = getsockopt(connSock, SOL_SCTP, SCTP_STATUS,
           (void *) &status, (socklen_t *) & in);

   printf("assoc id  = %d\n", status.sstat_assoc_id);
   printf("state     = %d\n", status.sstat_state);
   printf("instrms   = %d\n", status.sstat_instrms);
   printf("outstrms  = %d\n", status.sstat_outstrms);

   for (k=0; k<100; k++) {
       /* Grab the current time */
       currentTime = time(NULL);

       /* Send local time on stream 0 (local time stream) */
       //printf("%s\n", ctime(&currentTime));
       ret = snprintf(buffer, MAX_BUFFER, "%s", ctime((const time_t *) &currentTime));
       buffer[ret-1]='\0';    // ctimeは末尾にLFを返すので、それをNULLに上書き。
       ret = sctp_sendmsg(connSock, (void *) buffer, (size_t) strlen(buffer),
               NULL, 0, 0, 0, LOCALTIME_STREAM, 0, 0);
       if (ret == -1) {
           perror("recv");
           exit(EXIT_FAILURE);
       }

       in = sctp_recvmsg(connSock, (void *) buffer, sizeof (buffer),
               (struct sockaddr *) NULL, 0, &sndrcvinfo, &flags);
       if (in == -1) {
           perror("recv");
           exit(EXIT_FAILURE);
       }

       buffer[in] = 0;
       if (sndrcvinfo.sinfo_stream == LOCALTIME_STREAM) {
           printf("PID:%.6d (Local) %s\n", getpid(),buffer);
       } else if (sndrcvinfo.sinfo_stream == GMT_STREAM) {
           printf("PID:%.6d (GMT  ) %s\n", getpid(),buffer);
       }

       /* Send GMT on stream 1 (GMT stream) */
       //printf("%s\n", asctime(gmtime(&currentTime)));
       ret = snprintf(buffer, MAX_BUFFER, "%s", asctime(gmtime(&currentTime)));
       buffer[ret-1]='\0';    // asctimeは末尾にLFを返すので、それをNULLに上書き。
       ret = sctp_sendmsg(connSock, (void *) buffer, (size_t) strlen(buffer),
               NULL, 0, 0, 0, GMT_STREAM, 0, 0);
       if (ret == -1) {
           perror("recv");
           exit(EXIT_FAILURE);
       }

       in = sctp_recvmsg(connSock, (void *) buffer, sizeof (buffer),
               (struct sockaddr *) NULL, 0, &sndrcvinfo, &flags);
       if (in == -1) {
           perror("recv");
           exit(EXIT_FAILURE);
       }
       buffer[in] = 0;
       if (sndrcvinfo.sinfo_stream == LOCALTIME_STREAM) {
           printf("PID:%.6d (Local) %s\n", getpid(),buffer);
       } else if (sndrcvinfo.sinfo_stream == GMT_STREAM) {
           printf("PID:%.6d (GMT  ) %s\n", getpid(),buffer);
       }
   }

   /* Close our socket and exit */
   close(connSock);

   return 0;
}