Linux ネットワーク プログラミング (c/s モデル)

ネットワーク バイト オーダー:

ビッグ エンディアンとリトル エンディアンの概念:

ビッグエンド:ステータスアドレスに上位データが格納され、上位アドレスにステータスデータが格納されます

リトル エンディアン: ステータス アドレスにはステータス データが格納され、上位アドレスには上位データが格納されます。

コンピュータ内のデータの格納方式はリトルエンディアン形式ですがネットワーク上ではビッグエンディアン形式で格納されるため、Linuxには格納方式を変換する機能がいくつかあります。

関数によってインクルードされるヘッダー ファイル: <arpa/inet.h>

  1. htonl 関数: uint32_t htonl(uint32_t hostlong);

機能: ローカル (リトル エンディアン) IP アドレスをネットワークで必要な IP アドレス形式 (ビッグ エンディアン) に変換します。

  1. htons 関数: uint16_t htons(uint16_t hostshort);

機能: ローカル (リトル エンディアン) のポート番号をネットワークで必要なポート番号 (ビッグ エンディアン) に変換します。

  1. ntohl 関数: uint32_t ntohl(uint32_t netlong);

機能: ネットワーク内の (ビッグ エンディアン) IP アドレスをローカル (リトルエンディアン) IP アドレス形式に変換します。

  1. ntohs 関数: uint16_t ntohs(uint16_t netshort);

機能: ネットワークの (ビッグ エンディアン) ポート番号をローカル (リトルエンディアン) ポート番号形式に変換します。

リスニング ステータスと接続ステータスを表示するコマンド

netstat コマンド: netstat -anp | grep 8888

a はすべて表示することを意味します

n は、表示時にデジタル表示されることを示します。

p は、プロセスのメッセージを表示することを意味します

  1. シンプルな c/s モデル

シンプルな c/s モデル

1. 関数の使用法:

1.socket 関数: ヘッダー ファイルに含まれる <sys/types.h> または <sys/socket.h>

➡関数プロトタイプ: int socket(int domain, int type, int protocol);

役割: 接続用のソケットを作成する

2.connect 関数: ヘッダー ファイル <sys/types.h> または <sys/socket.h> に含まれる

➡関数プロトタイプ: int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

役割:サーバーに接続する機能

3.memset 関数: ヘッダー ファイル <string.h> に含まれています

➡関数プロトタイプ: void *memset(void *s, int c, size_t n);

機能: 配列の内容を特定の値に設定する

4.inet_pton および inet_ntop 関数: ヘッダー ファイル <arpa/inet.h> に含まれています

➡関数プロトタイプ: int inet_pton(int af, const char *src, void *dst);

パラメータ af: 指定された IP プロトコル タイプ。AF_INET と AF_INET6 の 2 つのオプションがあります。

パラメータ src: 着信 IP アドレス (ドット付き 10 進数)

パラメータ dst: 発信アドレス、変換されたネットワーク バイト順の IP アドレス

戻り値: 成功すると 1 が返され、例外 (つまり、無効な IP アドレスを指している) の場合は 0 が返され、失敗すると -1 が返されます。

機能: sockaddr_in 構造体の s_addr の値にアドレス値を付加し、ドット 10 進法のバイト オーダーをネットワークのバイト オーダーに変換する

➡関数プロトタイプ: const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

パラメータ af: 指定された IP プロトコル タイプ。AF_INET と AF_INET6 の 2 つのオプションがあります。

パラメータ src: 着信 IP アドレス、ネットワーク バイト順 IP アドレス

パラメータ dst: アドレス変換後に格納されるバッファ、ローカル バイト オーダー

パラメータサイズ: バッファサイズ

戻り値: 成功した場合は dst を返し、失敗した場合は NULL を返します

機能: ネットワーク バイト オーダーを小数点付き 10 進バイト オーダーに変換します。

5.bind 関数: ヘッダー ファイルに含まれる <sys/types.h> または <sys/socket.h>

➡関数プロトタイプ: int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

役割: バインド IP アドレス

6.listen 関数: ヘッダー ファイルに含まれる <sys/types.h> または <sys/socket.h>

➡関数プロトタイプ: int listen(int sockfd, int backlog);

役割: クライアントのリンク要求を聞く

7. accept 関数: ヘッダー ファイルに含まれる <sys/types.h> または <sys/socket.h>

➡関数プロトタイプ: int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

役割: クライアントからの接続を受け入れる

サーバーコード

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>//使用网络包含的库
#include<unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>

int main()
{
        int lfd = socket(AF_INET, SOCK_STREAM, 0);
        if(lfd < 0)
        {
                perror("soket error");
                return -1;
        }
        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv));//该函数初始化serv
        serv.sin_family = AF_INET;
        serv.sin_port = htons(8888);
        serv.sin_addr.s_addr = htonl(INADDR_ANY);//表示使用本地任意可用IP地址
        int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
        if(ret < 0)
        {
                perror("bind error");
                return -1;
        }
        //监听
        listen(lfd, 128);
        //接受链接
        int cfd = accept(lfd, NULL, NULL);
        printf("lfd==[%d], cfd==[%d]\n", lfd, cfd);

        int n = 0;
        int i = 0;
        char buf[1024];
        while(1)
        {
                //读数据
                memset(buf, 0x00, sizeof(buf));
                n = read(cfd, buf, sizeof(buf));
                if(n <= 0)
                {
                        printf("read error or client close\n");
                        break;
                }
                printf("n==[%d], buf==[%s]\n", n, buf);

                for(i = 0;i < n;i++)
                {
                        //buf[i] = toupper(buf[i]);//该函数把小写字母转换成大写字母
                }
                //发生数据
                write(cfd, buf, n);
        }
        //关闭监听文件描述符和通信文件描述符
        close(lfd);
        close(cfd);
        return 0;
}

                                   

クライアントコード

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>

int main()
{
        //创建socket,与服务端进行通信
        int cfd = socket(AF_INET, SOCK_STREAM, 0);
        if(cfd < 0)
        {
                perror("socket error");
                return -1;
        }
        //连接服务器
        struct sockaddr_in serv;
        serv.sin_family = AF_INET;
        serv.sin_port = htons(8888);
        inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);//赋值地址
        printf("[%x]\n", serv.sin_addr.s_addr);//输出地址
        int ret = connect(cfd, (struct sockaddr*)&serv, sizeof(serv));
        if(ret < 0)
        {
                perror("connect error");
                return -1;
        }
        int n = 0;
        char buf[256];
        while(1)
        {
                //读标准输入数据
                memset(buf, 0x00, sizeof(buf));
                n = read(STDIN_FILENO, buf, sizeof(buf));
                //发送数据
                write(cfd, buf, n);
                //读服务端发来的数据
                memset(buf, 0x00, sizeof(buf));
                n = read(cfd, buf, sizeof(buf));
                if(n <= 0)
                {
                        printf("read error or server closed\n");
                        break;
                }
                printf("n==[%d], buf==[%s]\n", n, buf);
        }
        //关闭套接字
        close(cfd);
        return 0;
}
  1. マルチプロセス同時 c/s モデル

1. 使用する機能

  1. fork 関数: ヘッダー ファイル <sys/types.h> および <unistd.h> に含まれています

➡関数プロトタイプ: pid_t fork(void);

役割: 子プロセスを作成する

戻り値: fork 関数には 2 つの戻り値があります

親プロセスでは、fork は新しく作成された子プロセスの ID を返します。

子プロセスでは、fork は 0 を返します

注: fork 関数を呼び出した後、fork 関数の背後にあるすべてのコードが 2 回実行されます。

2 つの実行プロセス:

fork が正常に実行されると、最初に子プロセスの ID である 0 より大きい数値が返され、親プロセスのコードが実行され、fork 関数の背後にあるコードが実行された後、プログラムはコードを実行します。再び fork 関数の後ろに. fork が値 0 を返すとき

  1. struct sigaction 構造体: ヘッダー ファイル <signal.h> に含まれています

構造プロトタイプ:

struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };

メンバ sa_handler: これは、信号処理関数を指定する関数ポインタです。

メンバー sa_mask: シグナルの処理時に、sa_mask によって指定されたシグナル セットを一時的に確保するために使用されます。

メンバー sa_falgs: 信号処理の他の関連操作を設定するために使用されます。次の値が利用可能です

利用可能なオプション:

1. SA_RESETHAND : シグナル処理関数が呼び出されると、シグナル処理関数をデフォルト値 SIG_DFL にリセットします。

2. SA_RESTART : シグナルがプロセスのシステム コールを中断した場合、システムは自動的にシステム コールを開始します。

3. SA_NODEFER : 通常、シグナル処理機能が実行されている場合、カーネルは特定のシグナルをブロックします。ただし、SA_NODEFER フラグが設定されている場合、シグナル ハンドラの実行中にカーネルはシグナルをブロックしません。

  1. sigaction 関数: ヘッダー ファイル <signal.h> に含まれています

➡関数プロトタイプ: int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

役割: 指定されたシグナルに関連付けられた処理アクションを確認または変更します

signum パラメータは、キャプチャする信号のタイプを示します

act パラメータは、新しいシグナル処理方法を指定します。

oldact パラメータは、前の信号の処理方法を出力します

サーバーコード

#include<stdio.h>                                                  
#include<stdlib.h>                                                  
#include<sys/socket.h>                                               
#include<arpa/inet.h>
#include<sys/wait.h>                                                  
#include<string.h>
#include<strings.h>                                                     
#include<unistd.h>                                                     
#include<errno.h>  
#include<arpa/inet.h>
#include<ctype.h>
#include<signal.h>                                                    
#include<pthread.h>

#include "wrap.h"

#define SRV_PORT 10110

void catch_child(int signum)
{
    while((waitpid(0, NULL, WNOHANG))>0);//阻塞子进程
    return;
}

int main(int argc, int *argv[])
{
    int lfd;
    struct sockaddr_in srv_addr;
    //memset(&srv_addr, 0, sizeof(srv_addr));
    bzero(&srv_addr, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(SRV_PORT);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    lfd = Socket(AF_INET, SOCK_STREAM, 0);

    Bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
    
    Listen(lfd, 128);

    struct sockaddr_in clt_addr;//客户端使用的
    socklen_t clt_addr_len = sizeof(clt_addr);//accept参数需要使用的类型
    pid_t pid;
    int cfd;
    int retf;
    while(1)
    {
        cfd = Accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
        //创建子进程
        pid = fork();
        if(pid < 0)//子进程创建失败
        {
            perr_exit("fork error");
        }
        else if(pid == 0){//子进程创建成功
            break;
        }
        else//处理主进程
       {
            //回收子进程
            struct sigaction act;//创建一个结构体
            act.sa_handler = catch_child;//捕捉子进程           
            sigemptyset(&act.sa_mask);//清空子进程
            act.sa_flags = 0;
            retf = sigaction(SIGCHLD, &act, NULL);
            if(retf != 0)
            {
                perr_exit("sigaction error");
            }

            close(cfd);
            continue;
        }
    }
    char buf[1024] = {0};
    int ret = 0;
    int i = 0;
    if(pid == 0)
    {
        while(1)
        {
            ret = Read(cfd,buf, sizeof(buf));
            if(ret == 0)
            {
                close(cfd);
                exit(0);
            }
            for(i = 0;i < ret; i++);
            {
                buf[i] = toupper(buf[i]);
            }
            write(cfd, buf, ret);
            write(STDOUT_FILENO, buf, ret);
        }
    } 
    return 0;
}

3. マルチスレッド同時 c/s モデル

1. 使用する機能

1.pthread_create 関数: ヘッダー ファイル <pthread.h> に含まれています

➡関数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

機能: 子スレッドを作成する

パラメーター 1: 事前に作成された pthread_t 型のパラメーター。成功すると、tidp が指すメモリ ユニットが、新しく作成されたスレッドのスレッド ID に設定されます。

パラメータ 2: さまざまなスレッド プロパティのカスタマイズに使用

パラメーター 3: 新しく作成されたスレッドは、この関数から実行を開始します

パラメータ 4: start_rtn 関数のパラメータ。パラメータがない場合は、NULL に設定します。パラメータがある場合は、パラメータのアドレスを入力します。複数のパラメーターを構造体で渡す必要がある場合

戻り値: スレッドが正常に作成された場合は 0 を返し、それ以外の場合はエラー番号を返します

2.pthread_detach 関数: ヘッダー ファイル <pthread.h> に含まれています

➡関数プロトタイプ: int pthread_detach(pthread_t thread);

機能: スレッド分離状態では、スレッドはメイン制御スレッドからアクティブに切断されます。pthread_exit を使用するか、スレッドが自動的に終了した後、その終了ステータスは他のスレッドによって取得されませんが、自動的に直接解放されます

パラメータ: 指定されたスレッド番号を渡します

戻り値: 成功すると 0 が返され、失敗するとエラー番号が返されます。

サーバーコード

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>

#include "wrap.h"

#define MAXLINE 8192
#define SERV_PORT 8000

struct s_info {                     //定义一个结构体, 将地址结构跟cfd捆绑
    struct sockaddr_in cliaddr;
    int connfd;
};

void *do_work(void *arg)
{
    int n,i;
    struct s_info *ts = (struct s_info*)arg;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];      //#define INET_ADDRSTRLEN 16  可用"[+d"查看

    while (1) {
        n = Read(ts->connfd, buf, MAXLINE);                     //读客户端
        if (n == 0) {
            printf("the client %d closed...\n", ts->connfd);
            break;                                              //跳出循环,关闭cfd
        }
        printf("received from %s at PORT %d\n",
                inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
                ntohs((*ts).cliaddr.sin_port));                 //打印客户端信息(IP/PORT)

        for (i = 0; i < n; i++) 
            buf[i] = toupper(buf[i]);                           //小写-->大写

        Write(STDOUT_FILENO, buf, n);                           //写出至屏幕
        Write(ts->connfd, buf, n);                              //回写给客户端
    }
    Close(ts->connfd);

    return (void *)0;//结束该子线程
}

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    pthread_t tid;
    struct s_info ts[256];      //根据最大线程数创建结构体数组.
    int i = 0;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);                     //创建一个socket, 得到lfd

    bzero(&servaddr, sizeof(servaddr));                             //地址结构清零
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);                   //指定本地任意IP
    servaddr.sin_port = htons(SERV_PORT);                           //指定端口号 8000

    Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定

    Listen(listenfd, 128);      //设置同一时刻链接服务器上限数

    printf("Accepting client connect ...\n");

    while (1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);   //阻塞监听客户端链接请求
        ts[i].cliaddr = cliaddr;
        ts[i].connfd = connfd;

        /* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
        pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
        pthread_detach(tid); //线程已经创建好,需要关闭线程句柄                                                //子线程分离,防止僵线程产生.
        i++;
    }

    return 0;
}

1.setsockopt 関数: ヘッダー ファイル <sys/types.h> および <sys/socket.h> に含まれています

関数プロトタイプ:

int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

役割: 任意のタイプおよび任意の状態ソケットのオプション値を設定するために使用されます

パラメータの意味:

sockfd: ソケットを識別する記述子。

level: オプション定義のレベル; サポート SOL_SOCKET, IPPROTO_TCP, IPPROTO_IP および IPPROTO_IPV6; optname: 設定するオプション;

optval: 設定するオプションの新しい値を格納するバッファへのポインタ;

optlen: optval バッファ長;

戻り値: 成功した場合は 0 を返し、エラーが発生した場合は -1 を返します。

使用事例

int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));

2.getsockopt 関数: ヘッダー ファイル <sys/types.h> および <sys/socket.h> に含まれています

関数プロトタイプ:

int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);

機能: 任意のタイプおよび任意の状態ソケットのオプションの現在の値を取得し、その結果を optval に格納するために使用されます

パラメータの意味:

sockfd: ソケットを識別する記述子。

level: オプション定義のレベル; サポート SOL_SOCKET, IPPROTO_TCP;

optname: 取得するソケット オプション。

optval: 取得したオプション値が格納されているバッファを指すポインタ。

optlen: optval バッファーの長さの値を指すポインター。

戻り値: 成功した場合は 0 を返し、エラーが発生した場合は -1 を返します。

4.半分閉じた

意味:TCP接続で、AがクローズするFINリクエストを送信し、BがACKで応答した後、AはFIN_WAIT_2状態に入り、半クローズ状態(実際には片端のみクローズ)に入る

close 関数と shutdown 関数の違い:

close 関数は、ソケットまたはファイル記述子のみを閉じることができます

シャットダウン機能は、読み取りのみを閉じ、書き込みのみを閉じ、読み取りと書き込みを閉じることができ、状況はより洗練されています。同時に、複数のファイル記述子が同じソケットを指している場合は、shutdown を呼び出してすべてのポインターを切断します。

知らせ:

  1. 複数のプロセスがソケットを共有している場合、close が呼び出されるたびに、カウントが 0 になるまで、つまり、使用中のすべてのプロセスが close を呼び出し、ソケットが解放されるまで、カウントが 1 ずつ減らされます。

  1. マルチプロセスでは、プロセスが shutdown(sfd, SHUT RDWR) を呼び出すと、他のプロセスは通信できなくなります。ただし、1 つまたは 2 つのプロセスを閉じても (sfd)、他のプロセスには影響しません。

シャットダウン関数のプロトタイプ: ヘッダー ファイル <sys/socket.h> に含まれています

int shutdown(int sockfd, int how);

パラメータ sockfd: 指定されたソケット

パラメータ how: 指定された切断方法、次のオプション

  • SHUT_RD: sockfd の読み取り機能をオフにします. このオプションにより, sockfd は読み取り操作を実行できなくなります. ソケットはデータを受け入れなくなり, 現在のソケットバッファ内のデータは破棄されます.

  • SHUT_WR: sockfd の書き込み機能をオフにします。このオプションは sockfd の書き込みを許可しません。プロセスはこのソケットに書き込むことができません。

  • SHUT_RDWR: sockfd の読み取りおよび書き込み機能をオフにします。これは、shutdown を 2 回呼び出すことと同じです。最初に SHUT_RD、次に SHUT_WR を呼び出します。

おすすめ

転載: blog.csdn.net/weixin_62859191/article/details/128822145