2019年11月1日金曜日

tcpサーバー クライアント プログラム


server.c

//@@@ 初歩的なソケットを作成する
//@@@ pythonでしたほうが楽だが情報が少ないので仕方なくcでする
//アドレス下の文章は各アドレスからの引用

//参考
//本 ネットワークプログラミング 雪田修一著
//各アドレスの下の文章はそのアドレスからの引用

//https://paiza.hatenablog.com/entry/2015/09/29/%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E8%91%97%E4%BD%9C%E6%A8%A9%E3%81%AF%E3%81%82%E3%82%8B%EF%BC%9F%E3%82%AA%E3%83%AA%E3%83%B3%E3%83%94%E3%83%83%E3%82%AF%E9%A8%92%E5%8B%95%E3%81%8B%E3%82%89%E8%80%83
//誰が書いても同じになる、Hello Worldのような短いプログラムは、著作物
//と見なされない可能性があります。
//ので本のコードをネットで公開してもいいのでしょう

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

//#include <sys/types.h>
////coseとwrite、exitで警告 implicit declaration of functionがでるので
////includeする
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

/*

https://www.wdic.org/w/TECH/in_port_t


1. *in_port_t*
UNIXおよびPOSIX準拠システム(Linuxなど)のC/C++のINETで使われる、
ポート番号を表わす型。
typedef unit16_t in_port_t などと定義されており16ビット符号なし整数
PORTを相手もつかうので in_port_tでキャストしたほうがいいのだろう
BUF_LENは内部で使うのでキャストしなくていい?
*/

#define PORT    (in_port_t)50000
#define  BUF_LEN 512

/*
接続先のIPアドレスやポート番号の情報を保持するために,sockaddr_in
構造体が 用意されており,各ソケットは,bindシステムコールによって
sockaddr_in構造体のデータと関連づけられる.


2. *sockaddr_in*構造体は次のように 定義されている.
/usr/include/netinet/in.h:
   struct in_addr {
      u_int32_t s_addr;
   };

struct sockaddr_in {
   u_char  sin_len;     (このメンバは古いOSでは存在しない)
   u_char  sin_family;  (アドレスファミリ.今回はAF_INETで固定)
   u_short sin_port;    (ポート番号)2バイト
   struct  in_addr sin_addr;   (IPアドレス)
                                uint32_t(32ビットの符号なし整数型)
   char    sin_zero[8];    (無視してもよい.「詰め物」のようなもの)
};


3. sockaddr_inの各型を分析
https://www.wdic.org/w/TECH/u_char

*u_char*
BSD系のC/C++で使われる変数型で、unsigned charのエイリアスである。現在
はLinuxでも利用できるようである。
するとWindows Mac以外で使えないosがあるのか?

*unsigned char*
「符号なしの文字型」 8ビット
https://qiita.com/huhu/items/453ad5875e0da641f629
char型 保存できる値は-127~127
unsigned char型 保存できる値は 0-255です

なぜunsigned charを使うのか。uint8_tでないと8ビットが保証されず
ダメなのでは?
https://www.wdic.org/w/TECH/uint8_t
typedef unsigned char uint8_t;などと定義されており
unsigned char と uint8_t は同じ
するとどのPCでもunsigned charは8ビットである事が保証されているのか


4. *in_addr*構造体は
struct in_addr {
    in_addr_t s_addr;
};

*s_addr*の型は
typedef uint32_t  in_addr_t;

*s_addr* *sin_addr*
sin_addr.s_addr は結局は uint32_t(32ビットの符号なし整数型)

*typedef*
typedef 既存のデータ型 新しい名前;

*uint32_t*はPOSIXで定義されている整数の型
int short longという整数型は32ビットと64ビットPCでビット幅が異なる
事があります。それから生ずる混乱を避けるためビット数が確定している
整数型を使っている。こうする事でソースの互換性を保つのが容易になる


5. memset
void *memset*(void *buf, int ch, size_t n);
第一引数はメモリのポインタa
第二引数はセットする値
第三引数はセットするサイズ

char buf[] = "ABCDEFGHIJK";
//先頭から2バイト進めた位置に「1」を3バイト書き込む
memset(buf+2,'1',3);
*/

/*
https://www.atmarkit.co.jp/icd/root/72/116970472.html


6. ネットワークバイトオーダー (network byte order)
 ネットワーク上で、バイナリデータをやり取りするときに使われるバイトオー
ダー(バイトの並び順)のこと。

 ネットワークを使った通信では、バイトオーダーの異なるマシンやシステム
同士で通信する可能性があるが、このとき、バイトオーダーが異なっていると
正しく通信できない。たとえば、0x1234という数値を、MSBを先に送ると0x12,
0x34となるが、これをLSBが先だと思って受け取ってしまうと、0x3412という
数値になってしまう。このような不都合が起こらないように、ネットワークで
通信をする場合には、あらかじめバイトオーダーを決めておくことが多い。こ
れをネットワークバイトオーダーという。

 たとえばTCP/IPプロトコルでは、ネットワークバイトオーダーはMSBから先
に送るビッグエンディアンとなっている。IPアドレスやポート番号などのデー
タをパケット中に格納する場合は、常にネットワークバイトオーダーにするこ
とになっているので、異なるバイトオーダーのシステム間でも正しく通信する
ことができるのである。ただし、より上位のアプリケーション層などでのバイ
トオーダーの扱いは、各アプリケーションごとに自由に決定可能で、システム
の都合のよい受け渡し方法を選べばよい。


http://wisdom.sakura.ne.jp/system/winapi/winsock/winSock4.html
現代、多くのパーソナルコンピュータ市場は Intel とその互換プロセッサに
支配され その性質は、下位バイトから順にメモリに配置するリトルエンディ
アン形式です アセンブリ経験者は理解していると思いますが、Intel の x86
プロセッサは 0xABCD というワード長のデータを 0xCDAB という順でメモリに
保存しているのです しかし、ネットワークの世界ではビッグエンディアンが
標準なのです これは、インターネットが建築された当時のコンピュータの性
質が引き継がれているためです


https://www.uquest.co.jp/embedded/learning/lecture05.html
CPUとエンディアン
話をコンピュータに戻そう。

エンディアンはCPUによって決まっていて、PowerPCなどはビッグエンディアン、
Intel系などはリトルエンディアン。また MIPS や ARM、SHなど、どちらにも
なれるプロセッサも存在していて、バイエンディアンと呼ばれているんだ。

//https://www.math.nagoya-u.ac.jp/~garrigue/lecture/2004_AW/network1.pdf
ネットワーク順とはビッグエンディアンのことなので,PowerPC の場合では
特に気にする必要はないが,Intel と逆の順番なので,整数を逐一ネットワーク
順に変換する必要がある.
整数の長さによって *htonl*(32 ビット) と *htons*(16 ビット) を使う.
逆変換は *ntohl* と *ntohs*
*/

//上からの結論
//htonl()でネットワークバイトオーダに変換してから代入.
//と単純にほとんどのサイトで書かれていて本当に理解できない。
//つまり使っているPCがIntelなどのリトルエンディアンならhtonlで
//ネットワークバイトオーダーに変換してやらないとダメという事
//PowerPcならhtonlがいらない
//この「いらない」場合の説明がほとんどのサイトでない
//ただ闇雲にhtonlをつけろと言っているだけ


//戻り値の宣言がないのでint型、でも参考本ではreturn文がない
int main(){
    //変数宣言
    //struct 構造体名 実態名 でmeという構造体の実態を作成
    //なんで2つのソケットがいるんだろうか
    struct sockaddr_in me;  //サーバー(自分)の情報
    int soc_waiting;        //listenするソケット
    int soc;                //送受信に使うソケット
    char buf[BUF_LEN];      //送受信のバッファ

    //サーバーのアドレスをsockaddr_in構造体に格納
    //pythonの場合は構造体に格納てな事はなかったのになんで?
    //meの初期化をしている。(char *)となぜキャストしているか不明
    //meには1バイト2バイト4バイトのメンバーが存在しているのに?
    //ネット上ではキャストしているものは見当たらず
    memset((char *)&me, 0, sizeof(me)); //構造体を0でうめる
    me.sin_family = AF_INET;            //IPv4を表す

    //通常*INADDR_ANY*で指定する.
    //特定のIPアドレスを指定するとそのアドレス にきた要求だけを
    //受け付けるようになる.INADDR_ANYだとどれでも 要求を受け付ける.
    //htonl()でネットワークバイトオーダに変換してから代入.

    //https://www.keicode.com/windows/win17.php
    //注意点としては、ポートと IP アドレス値どちらもネットワークバイト
    //オーダーで設定する必要がある、という点です

    //他のデータはネットワークバイトオーダーに注意しなくてもいいのか
    //バイトオーダとは、複数のバイトから構成されるデータ(すなわち、
    //char 型以外のもの

    //https://teratail.com/questions/60426
    //send()やrecv()などの関数で送受信するデータ実体もネットワークバイ
    //
    //トオーダー(ビッグエンディアン)で通信しているんですか??
    //> 例えば、ペイロード部分が、 you will make me happyという文字列
    //だったら、文字列は数値では・・・・ない・・・から・・??
    //エンディアンの概念は登場しない・・・??
    //
    //エンディアンはバイト単位で入れ替えますね。char型は1バイトですか
    //らエンディアンの概念は存在しません。




    me.sin_addr.s_addr = htonl(INADDR_ANY);
    me.sin_port = htons(PORT);

    //IPv4でストリーム型のソケットを作成
    //socket()でファイルディスクリプタから0以上の整数値が返される。
    //
    //http://e-words.jp/w/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%83%87%E3%82%A3%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%82%BF.html
    //ファイルディスクリプタとは、プログラムがアクセスするファイルや標
    //準入出力などをOSが識別するために用いる識別子。0から順番に整数の
    //値が割り当てられる
    //http://www.ryusuke.net/archives/643
    //socket()はエラーが発生したときには-1を返します
    //
    //http://www.c-tipsref.com/reference/stdio/perror.html
    //*prrror*(str)
    //エラーメッセージを標準エラー出力 (standard error) に出力
    //srt + エラーメッセージ

    if ((soc_waiting = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("socket");
        exit(1);
    }

    //サーバー(自分)のアドレスをソケットに設定
    //
    //http://research.nii.ac.jp/~ichiro/syspro98/server.html
    //bind(int socket, struct scokaddr *addr, int length)
    //socket 設定するソケットの番号
    //addr そのソケット自体のアドレスやポート番号を定義するsockaddr_in
    //構造体へのポインタ
    //length 上の構造体のサイズ
    //
    //ネットでbindを検索してもなんで(struct sockaddr *)にキャストの解
    //説はなかなかでない
    //
    // https://teratail.com/questions/146495
    //(struct sockaddr *)
    //sockaddr構造体にキャストにキャストされているが?
    //仮にキャストだとして最初からその型で宣言することはできないのでしょ
    //うか.
    //
    //bindの第2引数の
    //struct sockaddr_inは、インターネットドメインのソケット用の構造体
    //です。ソケットの種類は他にもたくさんありますので、bindはどんな種
    //類のソケット情報構造体のアドレスも受け入れられるよう、関数プロト
    //タイプの第2引数は、汎用的なソケット情報であるstruct sockaddrで宣
    //言されています。
    //
    //struct sockaddrは、バイト数だけ合わせてあるだけで、値をセットで
    //きるようなフィールドはありません。
    //そのため、struct sockaddrで変数を宣言してしまうと、この構造体に
    //はインターネットドメインソケット固有の情報であるIPアドレスやポー
    //ト番号のフィールドがありませんので、それらの値をセットできません。
    //
    //
    //ポインタへのキャストの参照コード pointer_cast.c
    //構造体のキャストはkouzoutai_cast.c sockaddr_cast.c を参照
    if (bind(soc_waiting, (struct sockaddr *)&me, sizeof(me)) == -1){
        perror("bind");
        exit(1);
    }

    //ソケットで待ち受ける事の設定
    listen(soc_waiting, 1);

    //接続要求が来るまでブロックする
    soc = accept(soc_waiting, NULL, NULL);

    //接続待ちのためのソケットを閉じる
    close(soc_waiting);

    //1:画面 "":表示文字 10:送り出すバイト数
    write(1, "Go ahead!\n", 10);

    //通信のループ
    do{
        int n;
        //標準入力0から読み込む n:読み込まれるバイト数
        n = read(0, buf, BUF_LEN);
        //ソケットsocに書き出す
        write(soc, buf, n);
        //ソケットsocから読む
        n = read(soc, buf, BUF_LEN);
        //標準出力に書き出す
        write(1, buf, n);
    }
    //終了判定
    //quitという文字を受信したらdoループから抜ける
    //*strncmp* bufと"quit"の4番目までの文字がおなじなら0を返す
    //以下の条件式が真の時繰り返し処理をする
    while (strncmp(buf, "quit",4) != 0);

    //ソケットを閉じる
    close(soc);

    return 0;
}






client.c
include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

//implicit declaration of functionがでるので includeする
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>


#define PORT    (in_port_t)50000
#define BUF_LEN 512

int main(){
    //変数宣言
    //サーバーの情報
    struct hostent *server_ent;
    //サーバーのアドレス
    struct sockaddr_in server;
    //ソケットのディスクリプタ
    int soc;
   
    //サーバーのホスト名
    //環境により異なる。linuxコマンドhostnameでわかる
   
    //その後このホスト名よりIP情報を得ているので、これを使わず
    //直接IPアドレスを使う場合は行末//killの行をコメントアウト
    //             行末//saveの行のコメントアウトを消す
   
    //char hostname[] = "moto-PC-VY25AAZ79";//kill
    char ip_str[]   = "192.168.1.4";//save
    struct in_addr ip;//uint32_tの変数 //save
   
    //ip_strを32ビット整数に変換
    //int inet_aton(const char *cp, struct in_addr *inp);
    //*net_aton*() は、インターネットホストのアドレス cp を、 IPv4 の数
    //値とドットによる表記から (ネットワークバイトオーダの) バイナリ値
    //へ 変換し、変換結果を inp が指している構造体に格納する。 アドレ
    //スが有効な場合 0 以外を返し、そうでない場合は 0 を返す。
    inet_aton(ip_str, &ip);             //save
   
    //送受信のバッファ
    char buf[BUF_LEN ];
   
    //サーバーのホスト名からIP情報を得る
    //http://capm-network.com/?tag=C%E8%A8%80%E8%AA%9E-%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0_%E3%83%9B%E3%82%B9%E3%83%88%E5%90%8D%E3%81%8B%E3%82%89IP%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%82%92%E5%BE%97%E3%82%8B
    //gethostbyname:  ホスト名からIPアドレスを得るにはgethostbyname()
    //関数を利用します。
    //gethostbyname()はDNSに問い合わせて正引き(ホスト名に対応するIPア
    //ドレスを調べる)を行います。
    //また、ローカルのホストファイル(/etc/hostsなど)にアドレスが登録
    //してある場合には、そこから名前解決します。
   
    //http://bttb.s1.valueserver.jp/wordpress/blog/2018/07/01/network2/
    //多分hostent構造体に値をセットして戻り値がhostent構造体へのポインタ
    //*hostent*
    //struct hostent
    //{
    //  char *h_name;         /* Official name of host.  */
    //  char **h_aliases;     /* Alias list.  */
    //  int h_addrtype;       /* Host address type.  */
    //  int h_length;         /* Length of address.  */
    //  char **h_addr_list;       /* List of addresses from name server
    //}
    //#define h_addr h_addr_list[0] /* 過去との互換性のため */
    //
    //  char *h_name:    ホストの正式名称
    //  char **h_aliases:エイリアス(別名)リスト。(NULLで終わる配列)
    //  int  h_addrtype: アドレスタイプ(AF_INETとか)
    //  int  h_length:   アドレスの長さ
    //  char **h_addr_list:IPアドレスのリスト
    //  ** はポインタのポインタ。
    //
    //上のサイトの実行結果
    //  $ ./gethostbyname www.google.com
    //  host_info->h_name    :  www.google.com
    //  host_info->h_addr    :  172.217.25.196
    //  host_info->h_addrtype    :  2
    //  host_info->h_length  :  4
    //  host_info->h_addr_list[0]:  172.217.25.196
   
    //if ((server_ent = gethostbyname(hostname)) == NULL){//kill
    //  perror("gethostbyname");                        //kill
    //  exit(1);                                        //kill
    //}                                                 //kill
   
    //サーバーのアドレスをsockaddr_in構造体に格納
    //(char *)とキャストしている。なんでかな?
    memset((char *)&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    //https://bituse.info/c_func/56
    //*memcpy*関数は指定バイト数分のメモリをコピーする関数です。
    //#include <string.h>
    //void *memcpy(void *buf1, const void *buf2, size_t n);
    //第一引数にコピー先のメモリブロックのポインタ
    //第二引数にコピー元のメモリブロックのポインタ
    //第三引数はコピーサイズ
   
    //sockaddr_in構造体の変数であるserverのメンバーのsin_addr
    //(u_int32_tの変数)へ
    //gethostbynameで取得したIPアドレスのリスト(h_addr)をsin_addr:IPア
    //ドレスを コピーする
    //なんで?
    //serverをconnectにてsockaddr構造体にキャストして使うため
    //
    //memcpy((char *)&server.sin_addr, server_ent->h_addr,//kill
    //      server_ent->h_length);                      //kill
    memcpy((char *)&server.sin_addr, &ip, sizeof(ip));  //save
   


    //IPv4でストリーム型のソケットを作成
    if((soc = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("socket");
        exit(1);
    }
   
    //サーバーに接続
    //connect相手プロセスのソケットに接続要求をする
    //http://chokuto.ifdef.jp/advanced/function/connect.html
    //int connect(int s, const sockaddr *name, socklen_t namelen)
    //s: ソケットのディスクリプタ 
    //name: コネクションが確立されるscokaddr構造体へのアドレス
    //namelen: nameが示すsockaddr構造体の大きさ
    if(connect(soc, (struct sockaddr *)&server, sizeof(server))
            == -1){
        perror("connect");
        exit(1);
    }

  //相手が先
    write(1, "Wait\n" ,5);
   
    //通信のループ
    do{
        int n;
        //ソケットから読む
        n = read(soc, buf, BUF_LEN);
        //標準出力に書き出す
        write(1, buf, n);  
        //標準入力0から読む
        n = read(0, buf, BUF_LEN);
        //ソケットに書き出す
        write(soc, buf, n);
    }
    while(strncmp(buf, "quit", 4) != 0);

    //ソケットを閉じる
    close(soc);
}
                                                        

0 件のコメント:

コメントを投稿

About

参加ユーザー

連絡フォーム

名前

メール *

メッセージ *

ページ

Featured Posts