1. ホーム
  2. スクリプト・コラム
  3. リナックスシェル

Linuxネットワークプログラミングの基本機能を学ぶ

2022-01-09 17:59:23

1、ソケットを作成する

関数のプロトタイプです。

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

パラメータの一覧です。

domainパラメータには以下の値があります。

AF_INET。IPv4プロトコル
AF_INET6:IPv6プロトコル
AF_LOCAL。Unix ドメインプロトコル
AF_ROUTE: ルーティングソケット
AF_KEY: キースイートインターフェース

型の値です。

SOCKET_STREAM: bidirectional reliable data stream, corresponding to TCPSOCKET_DGRAM: bidirectional unreliable datagram, corresponding to UDPSOCKET_RAW: provides protocols below the transport layer that can access internal network interfaces, such as receiving and sending ICMP messages

プロトコルの値は以下の通りです。

<ブロッククオート

type が SOCKET_RAW の場合、この値はプロトコルタイプを示すために必要であり、そうでない場合は 0 に設定される。

指定されたフォーマットのソケットを生成し、そのディスクリプタを返す関数であり、成功すればディスクリプタを、失敗すれば -1 を返す。

2、バインドソケットバインド

関数のプロトタイプです。

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);

パラメータの一覧です。

sockfd 先に作成したソケットディスクリプタの場合

my_addr は、TCPプロトコルのプログラミングでよく使われる汎用ソケット構造体へのポインタです。 sockaddr_in 構造体

構造体の内容は以下の通りです。

struct socketaddr_in
{
   unsigned short int sin_family;//corresponding address family IP v4 fill AF_INTE
   uint16_t sin_port;//corresponding to port number
   struct in_addr sin_addr;//corresponding ip address
   unsigned char sin_zero[8];
};
struct in_addr
{
   uint32_t s_addr;
};

addrlenは上記の構造体のサイズであり、sizeofを使用して求めることができます。

bind関数を使用する前に、sockaddr_in型の構造体を作成し、サーバーの情報を構造体に保存しておく必要があります。

成功すれば0、失敗すれば-1が返されます。

ポート番号とIPを設定する場合、まず構造体をクリアします。main関数にパラメータが渡された場合、対応するポート番号とIPは文字列形式となります

変換は、以下の形式の関数で行う必要があります。

char port[]="8888"
char ip[]="192.168.1.1"
struct sockaddr_in seraddr'
seraddr.sin_port=htos(atoi(port))
seraddr.sin_addr.s_addr=inet_addr(ip);

3, リスナーを作成する; listen

関数のプロトタイプです。

int listen(int fd, int backlog);

パラメータの一覧です。

fdはリスニングするソケットのディスクリプタ、backlogはリスニングキューのサイズです。

(1) listen 実行後、ソケットはパッシブモードに移行する。

(2) キューが満杯になると、新しい接続要求は拒否されます。クライアントはコネクションDエラーWSAECONNREFUSEDを取得する。

(3)リッスン中のソケットでリッスンを実行してもうまくいかない。

(4) 接続の受け付けを待つ

関数のプロトタイプです。

#include <sys/socket.h>
 int accept(int s, struct sockaddr * addr, int * addrlen);

bind関数を比較すると、引数はほぼ同じですが、acceptのaddrがconstで変更されていないことがわかります。

ということは、addrは接続されたクライアントのアドレス情報を格納するために使用され、Yang addlenで返されるaddrと同じサイズであることを意味します。

つまり、accept関数が行っているのは、接続されたクライアントのファイルディスクリプタを返すことです。

で、クライアントのアドレス情報を新しい sockaddr_in 構造体に保存します。リンクに失敗すると -1 が返されます。

5、メッセージの送信と受信 send and recv

機能のプロトタイプです。

  int send( SOCKET s, const char FAR *buf, int len, int flags );
  int recv( SOCKET s, char FAR *buf, int len, int flags); 

この関数のパラメータです。

  • 送受信側のソケットディスクリプタを指定する第1パラメータ。
  • 第2パラメータは、アプリケーションから送信されるデータを保持するバッファを指定します。
  • 第3パラメータは、実際に送受信するデータのバイト数を指定する。
  • 第4パラメータは、通常0に設定される。

送信の流れ

ここでは、同期型ソケットの送信機能の実行フローのみを説明します。

この関数が呼ばれると、send はまず、送信するデータの長さ len とソケット s の送信バッファの長さを比較します。

  •  len が s の送信バッファの長さより大きい場合、この関数は SOCKET_ERROR を返す。
  • len が s の送信バッファの長さ以下の場合、send はまずプロトコルが s の送信バッファのデータを送信しているかどうかを確認する
  • もしそうなら、プロトコルがデータを送信し終えるのを待ちます。 プロトコルが s の送信バッファのデータの送信を開始していない場合、または s の送信バッファにデータがない場合、s の送信バッファの残りのスペースと len を比較する。
  • len が残りのスペースサイズより大きい場合、s の送信バッファのデータを送信し終えるまでプロトコルを待つ。
  • len が残りの領域のサイズより小さい場合 send は buf のデータを残りの領域にコピーするだけです (s の送信バッファのデータを接続の相手側に渡すのは send ではなく、プロトコルであることに注意してください。s は buf のデータを s の送信バッファの残りの領域に単にコピーします)。
  • send関数がデータのコピーに成功した場合、コピーされた実際のバイト数を返します。
  • send がデータコピー中にエラーを起こした場合、SOCKET_ERROR を返します。
  • また、send がデータ転送のプロトコルを待っている間にネットワークが切断された場合、send 関数は SOCKET_ERROR を返します。

なお、send関数は、bufのデータをsの送信バッファの残りのスペースにコピーすることに成功した後に戻りますが、データは必ずしもすぐに接続の相手側に渡されるわけではありません。

  • 次のソケット関数は、その後のプロトコルの転送中にネットワークエラーが発生した場合、SOCKET_ERROR を返します。
  • (send 以外の全てのソケット関数は、その実行開始時に必ずソケットの送信バッファ内のデータがプロトコルによって転送されるのを待ってから処理を進める。
  • (待機中にネットワークエラーが発生した場合、ソケット関数はSOCKET_ERRORを返す)。

受信の流れ

ここでは、同期型ソケットのrecv関数の実行フローのみを説明します。

アプリケーションがrecv関数を呼び出すと、recvはまずsの送信バッファのデータがプロトコルによって転送されるのを待ちます

  • プロトコルによる s の送信バッファからのデータ転送中にネットワークエラーが発生した場合、recv 関数は SOCKET_ERROR を返します。
  • s の送信バッファにデータがない場合、またはプロトコルによってデータが正常に送信された場合、recv はまずソケット s の受信バッファをチェックし、次に
  • s の受信バッファにデータがないか、プロトコルがデータを受信している場合、recv はプロトコルがデータを受信し終えるまで待ちます。
  • プロトコルがデータの受信を終了すると、recv 関数は s の受信バッファのデータを buf にコピーする。

(プロトコルが受信したデータは buf の長さより大きい場合があるので、この場合、s の受信バッファのデータをコピーし終えるまでに recv 関数を数回呼び出す必要があることに注意。

recv関数はデータをコピーするだけで、実際の受信はプロトコルが行う) recv関数は実際にコピーしたバイト数を返します。

  • recvはコピー中にエラーが発生した場合、SOCKET_ERRORを返します。
  • recv関数がプロトコルのデータ受信待ちの間にネットワークを切断した場合、0を返します。
  • tcpプロトコル自体が信頼できるからといって、tcpを使ってデータを送信するアプリケーションが必ずしも信頼できるとは限りません。
  • 送信のサイズは、ブロックするかどうかにかかわらず、相手側がどれだけのデータを回収したかを意味するものではありません。
  • ブロッキングモードでは、送信機能はアプリケーションから要求されたデータを送信キャッシュにコピーし、確認応答とともに送り返すことで進行します。

ただし、送信キャッシュがあるため、送信キャッシュのサイズが要求サイズより大きい場合、送信関数は直ちに戻り、データをネットワークに送信する。

そうでない場合は、sendはキャッシュが保持できない部分をネットワークに送信し、相手側の確認を待ってから戻ります(受信側は単に受信キャッシュのデータを受信するだけです)。

受信者は、受信キャッシュでデータを受信するとすぐに確認し、アプリケーションがrecvを呼び出すのを待つ必要はない)。

  • ノンブロッキングモードでは、送信関数は単にデータをスタックのバッファにコピーするだけです。
  • キャッシュに十分な空き容量がない場合は、可能な限りコピーする。
  • コピーに成功した場合、そのサイズを返します。バッファに利用可能な領域がゼロの場合、-1を返し、 errnoにEAGAINをセットします。

5, ソケットディスクリプタを閉じる close

関数を使用します。

close(sockfd)です。

(a) ファイル操作と同様に、ソケットは使い終わったら閉じるべきファイルである。

6、tcpプロトコルに基づくC/Sサーバーモデル

tcpモデルの図解

7、実装コード

サーバーサイド

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
 
typedef struct sockaddr_in SIN;
typedef struct sockaddr SA;
 
int main(int argc,char *argv[])
{
    SIN seraddr;
    SIN cliaddr;
    int len=sizeof(SIN);
    // Create a listening socket
    int lisfd=socket(AF_INET,SOCK_STREAM,0);
    if(lisfd<0)
    {
        perror("socket");
        exit(0);
    }
    printf("Creating socket %d successful \n",lisfd);
    bzero(&seraddr,sizeof(seraddr));
    seraddr.sin_family=AF_INET;
    seraddr.sin_port=htons(8888);
    seraddr.sin_addr.s_addr=inet_addr("192.168.1.6");
    //bind the socket
    int ret=bind(lisfd,(SA*)(&seraddr),len);
    if(ret<0)
    {
        perror("bind");
        exit(0);
    }
    printf("bind successfully \n");
    //Start listening
    ret=listen(lisfd,1024);
    if(ret<0)
    {
        perror("listen");
        exit(0);
    }
    printf("Listening success \n");
    //wait for connection, save the connected socket information
    int clifd=accept(lisfd,(SA*)(&cliaddr),(socklen_t *)(&len));
    if(clifd<0)
    {
        perror("accept");
        exit(0);
    }
    printf("Client %d connected successfully \n",clifd);
    //read/write
    char readbuf[1024]={0};
    char sendbuf[1024]={0};
    while(1)
    {
        recv(clifd,readbuf,sizeof(readbuf),0);
        printf("recv:%s\n",readbuf);
        fgets(sendbuf,sizeof(sendbuf),stdin);
        send(clifd,sendbuf,sizeof(sendbuf),0);
    }
    //close the socket
    close(clifd);
    close(lisfd);
    return 0;
}

クライアント

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
 
typedef struct sockaddr_in SIN;
typedef struct sockaddr SA;
 
int main(int argc,char *argv[])
{
    SIN seraddr;
    //create listening sockets
    int serfd=socket(AF_INET,SOCK_STREAM,0);
    if(serfd<0)
    {
        perror("socket");
        exit(0);
    }
    printf("Creating socket %d successful \n",serfd);
    bzero(&seraddr,sizeof(seraddr));
    seraddr.sin_family=AF_INET;
    seraddr.sin_port=htons(8888);
    seraddr.sin_addr.s_addr=inet_addr("192.168.1.6");
    //request connection
    int ret=connect(serfd,(SA*)(&seraddr),sizeof(SIN));
    if(ret==-1)
    {
        perror("connect");
        exit(0);
    }
    printf("Connected successfully \n");
    //read/write
    char senbuf[1024]={0};