WeMosを使ってみる

パケット分割を考慮したSocket通信


Socket通信を行うときに、パケット分割を考慮しないと思わぬトラブルになります。
パケット分割とは、例えば100文字のデータをsend()で送信すると、最大100個のパケットに分割されて
相手に届きます。
これを避けるためには、最初に全体のデータ長を送信するのが常道です。
以下にパケット分割を考慮したSocket通信のコードを示します。

【クライアント側】

read()とwrite()を_read()と_write()に置き換えています。
_write()では最初に全体の文字長を送ります。
_read()では最初に全体の長さを読んで、この長さになるまで何回も読み込みを行います。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <limits.h>
#include <unistd.h>

int retryTotal=0;

#define PORT 9876 //サーバープログラムとポート番号を合わせてください

int connect2(struct sockaddr_in * dstAddr, int dstAddrLen) {
  int dstSocket;
  int retry = 0;
  int retryMax = 10;
  time_t start_time, end_time;
  double dtime;

  while (1) {
    //ソケットの生成
    dstSocket = socket(AF_INET, SOCK_STREAM, 0);
  
    //接続
    write(1,"Connecting...",13);
    time( &start_time );
    if (connect(dstSocket, (struct sockaddr *)dstAddr, dstAddrLen) == 0) return(dstSocket);
    time( &end_time );
    dtime=difftime( end_time, start_time );
    retry++;
    printf("接続できませんでした retry=%d difftime=%f\n",retry,dtime);
    retryTotal++;
    close(dstSocket);
    if (retry > retryMax) return(-1);
    sleep(10);
  }
}

ssize_t _read(int dstSocket, char *buf, size_t count) {
  int numrcv;
  numrcv = read(dstSocket, buf, 1);
  //printf("numrcv=%d\n",numrcv);
  if(numrcv ==0 || numrcv ==-1 ) return numrcv;
  int ilen = buf[0];
  //printf("ilen=%d\n",ilen);
  int rlen = ilen;
  int index = 0;

  while(1) {
    numrcv = read(dstSocket, &buf[index], rlen);
    index = index + numrcv;
    //printf("numrcv=%d index=%d\n",numrcv,index);
    if (index == ilen) break;
    rlen = rlen - numrcv;
  }
  return ilen;
}

ssize_t _write(int dstSocket, char *buf, size_t count){
  char *ptr;
  ptr = (char *)malloc(count+1);
  if(ptr == NULL){
    printf("malloc fail\n");
    return 0;
  }
//  memcpy(ptr,&count,1);
  *ptr = count;
  memcpy(ptr+1,buf,count);
  write(dstSocket, ptr, count+1);
  free(ptr);
  return count;
}

int main(int argc, char **argv){
  // IP アドレス,ソケット,sockaddr_in 構造体
  char destination[32];
  int dstSocket;
  struct sockaddr_in dstAddr;

  //struct sockaddr_in addr;
  struct hostent *hp;
  char  buf[1024];
  int number = 0;
  int numrcv;
  int loopMax;
  int loopCnt;

  time_t start_time=0, end_time;
  double dtime;

  loopMax=INT_MAX;
  if (argc == 2) {
    loopMax=atoi(argv[1]);
  }
  // 相手先アドレスの入力と送る文字の入力
  printf("サーバーマシンのIPは?:");
  scanf("%s", destination);
  printf("destination=%s\n",destination);
  
  //sockaddr_in 構造体のセット
//  bzero((char *)&dstAddr, sizeof(dstAddr));
  memset((char *)&dstAddr, 0, sizeof(dstAddr));
  dstAddr.sin_family = AF_INET;
  dstAddr.sin_port = htons(PORT);
  
  hp = gethostbyname(destination);
  if (hp == NULL) {
    printf("不正な名前です\n");
    return(-1);
  }
//  bcopy(hp->h_addr, &dstAddr.sin_addr, hp->h_length);
  memcpy(&dstAddr.sin_addr, hp->h_addr, hp->h_length);

  for(loopCnt=0;loopCnt<loopMax;loopCnt++) {
    dstSocket = connect2(&dstAddr, sizeof(dstAddr));
    printf("dstSocket=%d\n",dstSocket);
    if (dstSocket < 0) {
      printf("%s に接続できませんでした\n",destination);
      return(-1);
    }
    printf("%s に接続しました\n",destination);
    if (start_time == 0) time( &start_time );
  
    //パケットの送信
    memset(buf,0,sizeof(buf));
    sprintf(buf,"data from raspberry %05d",number);
    number++;
    printf("Send=[%s]",buf);
    //write(dstSocket, buf, strlen(buf));
    _write(dstSocket, buf, strlen(buf));

    //パケットの受信
    memset(buf,0,sizeof(buf));
    //numrcv = read(dstSocket, buf, sizeof(buf));
    numrcv = _read(dstSocket, buf, sizeof(buf));
    printf("→Recv=[%s]\n",buf);
    close(dstSocket);
    sleep(5);
  } // end for
  time( &end_time );
  dtime=difftime( end_time, start_time );
  printf("loop=%d difftime=%f[sec] %f[min] retryTotal=%d\n",loopMax,dtime,dtime/60,retryTotal);
  return(0);
}

【サーバー側】

こちらも同様に、read()とwrite()を_read()と_write()に置き換えています。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <ctype.h>
#include <limits.h>
#include <unistd.h>

#define PORT 9876 //クライアントプログラムとポート番号を合わせてください

ssize_t _read(int dstSocket, char *buf, size_t count) {
  int numrcv;
  numrcv = read(dstSocket, buf, 1);
  //printf("numrcv=%d\n",numrcv);
  if(numrcv ==0 || numrcv ==-1 ) return numrcv;
  int ilen = buf[0];
  //printf("ilen=%d\n",ilen);
  int rlen = ilen;
  int index = 0;

  while(1) {
    numrcv = read(dstSocket, &buf[index], rlen);
    index = index + numrcv;
    //printf("numrcv=%d index=%d\n",numrcv,index);
    if (index == ilen) break;
    rlen = rlen - numrcv;
  }
  return ilen;
}

ssize_t _write(int dstSocket, char *buf, size_t count){
  char *ptr;
  ptr = (char *)malloc(count+1);
  if(ptr == NULL){
    printf("malloc fail\n");
    return 0;
  }
//  memcpy(ptr,&count,1);
  *ptr = count;
  memcpy(ptr+1,buf,count);
  write(dstSocket, ptr, count+1);
  free(ptr);
  return count;
}

int main(int argc, char **argv){
  int i;
  int srcSocket; //自分
  int dstSocket; //相手
  // sockaddr_in 構造体
  struct sockaddr_in srcAddr;
  struct sockaddr_in dstAddr;
  socklen_t dstAddrSize;
  // 各種パラメータ
  int numrcv;
  char buf[1024];
  int loopMax;
  int loopCnt;

  time_t start_time=0, end_time;
  double dtime;

  loopMax=INT_MAX;
  if (argc == 2) {
    loopMax=atoi(argv[1]);
  }

  // sockaddr_in 構造体のセット
//  bzero((char *)&srcAddr, sizeof(srcAddr));
  memset((char *)&srcAddr, 0, sizeof(srcAddr));
  srcAddr.sin_port = htons(PORT);
  srcAddr.sin_family = AF_INET;
  srcAddr.sin_addr.s_addr = INADDR_ANY;
    
  // ソケットの生成(ストリーム型)
  srcSocket = socket(AF_INET, SOCK_STREAM, 0);
  // ソケットのバインド
  bind(srcSocket, (struct sockaddr *)&srcAddr, sizeof(srcAddr));
  // 接続の許可
  listen(srcSocket, 5);
  
   for(loopCnt=0;loopCnt<loopMax;loopCnt++) {
 //  while(1){
     // 接続の受付け
    printf("接続を待っています\nクライアントプログラムを動かして下さい\n");
    dstAddrSize = sizeof(dstAddr);
    dstSocket = accept(srcSocket, (struct sockaddr *)&dstAddr, &dstAddrSize);
    printf("%s から接続を受けました\n",inet_ntoa(dstAddr.sin_addr));
    if (start_time == 0) time( &start_time );
        
    while(1) { // クライアントがSocketをクローズしてからこちらもクローズする
      //パケットの受信
      memset(buf,0,sizeof(buf));
      //numrcv = read(dstSocket, buf, sizeof(buf));
      numrcv = _read(dstSocket, buf, sizeof(buf));
      if(numrcv ==0 || numrcv ==-1 ){ // client close socket
        close(dstSocket); break;
      }
      printf("numrcv=%d\n",numrcv);
      printf("Recv=[%s]",buf);
      for (i=0; i< numrcv; i++){ // bufの中の小文字を大文字に変換
        if(isalpha(buf[i])) {
           if(islower(buf[i])) {
             buf[i] = toupper(buf[i]);
           } else {
             buf[i] = tolower(buf[i]);
           }
         }
      }
      // パケットの送信
      //write(dstSocket, buf, numrcv);
      _write(dstSocket, buf, numrcv);
      printf("->Send=[%s]\n",buf);
     } // end while
   } // end for
   time( &end_time );
   dtime=difftime( end_time, start_time );
   printf("loop=%d difftime=%f[sec] %f[min]\n",loopMax,dtime,dtime/60);
   return(0);
}