【ソケットプログラミング】 TCPサーバー、UDPサーバー、ローカルソケット 【C言語コード実装】

目次

0. 知識を準備する

0.1 ビッグエンディアンとスモールエンディアンの概念

0.2 ネットワークバイトオーダーとホストバイトオーダー間の変換

0.3 ドット10進数文字列変換(IPアドレス変換機能)

0.4 IPV4 構造:(man 7 ip)

0.5 IPV6 ソケット構造: (man 7 ipv6)

0.6 ユニバーサルソケット構造

1. ネットワークソケット機能

1.1ソケット

1.2 接続する

1.3 バインド

1.4 聞く

1.5 受け入れる

1.6 ポートの再利用

2.ラッピング機能

2.1 ラップ.c

2.2 ラップ.h

3.TCPサーバー

3.1 簡易版

3.2 マルチプロセスバージョン

3.3 マルチスレッドバージョン

4. UDPサーバー

5. ローカルソケット

要約:


0. 知識を準備する

0.1 ビッグエンディアンとスモールエンディアンの概念

大端存储模式: データの下位エンディアン順序がメモリの上位アドレスに格納され、データの上位エンディアン順序が
小端存储模式メモリの下位アドレスに格納されることを意味します。 : データの下位エンディアン順序がメモリの上位アドレスに格納されることを意味しますデータはメモリの低位アドレスに格納され、データはメモリの低エンディアン アドレスに格納されます。データはメモリの高エンディアンの上位アドレスに格納されます。

別の保存方法を使用する場合、保存されるデータは 0x12345678 になります
ここに画像の説明を挿入します

0.2 ネットワークバイトオーダーとホストバイトオーダー間の変換

        TCP/IP プロトコルでは、ネットワーク データ フローがビッグ エンディアン バイト オーダー、つまり下位アドレスと上位バイトである必要があると規定しています。ホストがビッグエンディアンの場合、送受信に変換は必要ありません。同様に、32 ビット IP アドレスでは、ネットワークのバイト順序とホストのバイト順序も考慮する必要があります。

        ネットワーク プログラムを移植可能にし、コンパイル後にビッグ エンディアンとリトル エンディアンのコンピューターで同じ C コードを正常に実行できるようにするには、次のライブラリ関数を呼び出して、ネットワーク バイト オーダーとホスト バイト オーダーを変換します

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

uint16_t htons(uint16_t hostlshort);

uint32_t ntohl(uint32_t netlong);

uint16_t ntohs(uint16_t netshort);

h はホスト、n はネットワーク、l は 32 ビット、s は 16 ビットを表します。

ホストがリトル エンディアンの場合、これらの関数はパラメータをそれに応じて変換して返しますが、ホストがビッグ エンディアンの場合、これらの関数はパラメータを変換せず、変更せずに返します。

コード例 1:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    char buf[4] = {
        192, 168, 1, 2
    };
    unsigned int num = *(int*)buf;
    unsigned int sum = htonl(num);
    unsigned char* p = (unsigned char*)&sum;

    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    unsigned short a = 0x0102;
    unsigned short b = htons(a);
    printf("%#x\n", b);

    return 0;
}

実行のスクリーンショット:

 コード例 2:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    unsigned char buf[4] = {
        1, 1, 168, 192
    };
    int num = *(int*)buf;
    int sum = ntohl(num);
    unsigned char* p = (unsigned char*)&sum;

    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    return 0;
}

実行のスクリーンショット:

 

0.3 ドット10進数文字列変換(IPアドレス変換機能)

        通常表示される IP アドレスは文字列「192.168.1.2」ですが、これは変換する必要があります。

#include <apra/inet.h>

//ドット付き 10 進数文字列を 32 ビット ネットワーク ビッグ エンディアン データに変換します

int inet_pton(int af, const char *src, void *dst);

IPV4およびIPV6をサポート

パラメータ:

        の:

                AF_INET:IPV4

                AF_INET6:IPV6

        src: ドット付き 10 進数文字列のアドレス

        dst: 32ビットネットワークデータが保存されているアドレス

戻り値:

        成功: 1

        失敗しました: 0

#include <apra/inet.h>

//32 ビットのネットワーク ビッグエンディアン データをドット付き 10 進数文字列に変換します

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

パラメータ:

        の:

                AF_INET:IPV4

                AF_INET6:IPV6

        src: 32ビットネットワークデータのアドレス

        dst: ドット付き 10 進数文字列が格納されるアドレス

        size: 記憶ポイントの 10 進文字列配列のサイズ (桁数は具体的に決定する必要があります)

                INET_ADDRSTRLEN マクロ値 16

戻り値:

        ドット付き 10 進数文字列の最初のアドレスを格納します

コード例:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    char buf[] = "192.168.1.2";
    unsigned int num = 0;

    inet_pton(AF_INET, buf, &num);
    unsigned char* p = (unsigned char*)&num;
    printf("%d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3));

    char ip[16] = "";
    inet_ntop(AF_INET, &num, ip, 16);
    printf("%s\n", ip);

    return 0;
}

実行のスクリーンショット:

ネットワーク通信は、プロトコル、IP、ポートという 3 つの主要な問題を解決します。

0.4 IPV4 構造:(man 7 ip)

 struct sockaddr_in {                sa_family_t sin_family; /* アドレス ファミリ: AF_INET */                in_port_t sin_port; /* ネットワークバイトオーダーのポート */                struct in_addr sin_addr; /* インターネットアドレス */            };



 /* インターネットアドレス。*/
           struct in_addr {                uint32_t s_addr; /* ネットワークバイトオーダーのアドレス */            };

0.5 IPV6 ソケット構造: (man 7 ipv6)

 struct sockaddr_in6 {                sa_family_t sin6_family; /* AF_INET6 */                in_port_t sin6_port; /* ポート番号 */                uint32_t sin6_flowinfo; /* IPv6 フロー情報 */                struct in6_addr sin6_addr; /* IPv6 アドレス */                uint32_t sin6_scope_id; /* スコープ ID (2.4 の新機能) */            };





           struct in6_addr {                unsigned char s6_addr[16]; /* IPv6 アドレス */            };

0.6 ユニバーサルソケット構造

struct sockaddr {     sa_family_t sa_family; // アドレスファミリー     char sa_data[14]; // アドレスデータ};


注: 通常は次の形式が使用されます

構造体 sockaddr_in アドレス;
バインド(sockfd, (struct sockaddr*)&addr, sizeof(addr));

1. ネットワークソケット機能

1.1ソケット

#include <sys/types.h>

#include <sys/socket.h>

int ソケット (int ドメイン、int タイプ、int プロトコル);

機能: ソケットの作成

パラメータ:

        ドメイン:
                AF_INET これは、ほとんどのソケットで使用されるプロトコルです。送信には TCP または UDP を使用し、IPv4 アドレスを使用します。
                AF_INET6 は上記と似ていますが、IPv6 アドレスを使用します。
                AF_UNIX ローカル プロトコル。Unix および Linux システムで使用されます。通常、
        タイプ: SOCK_STREAMクライアントとサーバーが同じコンピュータ上にある場合に使用される
                このプロトコルは、シーケンシャルで信頼性の高い、データ統合されたバイト ストリーム ベースの接続です。これは最も一般的に使用されるソケット タイプであり、このソケットは送信に TCP を使用します
                SOCK_DGRAM このプロトコルは、コネクションレス型の固定長転送呼び出しです。このプロトコルは信頼性が低く、接続に UDP を使用します。
                SOCK_SEQPACKET このプロトコルは、送信用の固定長データ パケットを送信する二重回線の信頼性の高い接続です。このパケットは、読み取られる前に完全に受け入れられる必要があります。
                SOCK_RAW ソケット タイプは、単一のネットワーク アクセスを提供します。このソケット タイプは、ICMP パブリック プロトコルを使用します。(ping およびtraceroute はこのプロトコルを使用します)
                SOCK_RDM このタイプはめったに使用されず、ほとんどのオペレーティング システムには実装されていません。データ リンク層に提供され、データ パケットの順序は保証されません。プロトコル
:

戻り値:

        成功: ファイル記述子

        失敗: -1

1.2 接続する

#include <sys/types.h>

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

機能: サーバーに接続

パラメータ:

        sockfd: ソケットファイル記述子

        addr: IP アドレスとポート番号を含む ipv4 ソケット構造のアドレス

        addrlen: IPv4 ソケット構造の長さ

戻り値:

        成功: 0

        失敗: -1

1.3 バインド

#include <sys/types.h>

#include <sys/socket.h>

int binding(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

機能: バインドバインディング

パラメータ:

        sockfd: ソケットファイル記述子

        addr: IP アドレスとポート番号を含む ipv4 ソケット構造

        addrlen: IPv4 ソケット構造のサイズ

戻り値:

        成功: 0

        失敗: -1

1.4 聞く

#include <sys/types.h>

#include <sys/socket.h>

int listen(int sockfd, int backlog);

機能: リッスンモニタリング

パラメータ:

        sockfd: ソケットファイル記述子

        backlog: 完了キューと未完了キューの合計の最大値は 128

                        ビュー: cat /proc/sys/net/ipv4/tcp_max_syn_backlog 

戻り値:

        成功: 0

        失敗: -1

1.5 受け入れる

#include <sys/types.h>

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

機能: 完了した接続キューから新しい接続を抽出します (新しい接続がない場合、accept はブロックします)

パラメータ:

        sockfd:ソケット

        addr: ipv4 ソケット構造

        addrlen: ipv4 ソケット構造のサイズのアドレス

戻り値:

        成功: 新しく接続されたソケットのファイル記述子

        失敗: -1

1.6 ポートの再利用

サーバー コードのソケット呼び出しとバインド呼び出しの間に次のコードを挿入します。

int オプト = 1;

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 

注: 再利用のためにプログラムで特定のポートを設定します。これより前の他のネットワーク プログラムはこのポートを使用できません。 

2.ラッピング機能

2.1 ラップ.c

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

void perr_exit(const char* s)
{
    perror(s);
    exit(-1);
}

int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr)
{
    int n;

again:
    if ((n = accept(fd, sa, salenptr)) < 0) {
        if ((errno == ECONNABORTED) || (errno == EINTR))//如果是被信号中断和软件层次中断,不能退出
            goto again;
        else
            perr_exit("accept error");
    }
    return n;
}

int Bind(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int n;

    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");

    return n;
}
int Connect(int fd, const struct sockaddr* sa, socklen_t salen)
{
    int n;

    if ((n = connect(fd, sa, salen)) < 0)
        perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
    int n;

    if ((n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");

    return n;
}
ssize_t Read(int fd, void* ptr, size_t nbytes)
{
    ssize_t n;

again:
    if ((n = read(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)//如果是被信号中断,不应该退出
            goto again;
        else
            return -1;
    }
    return n;
}

ssize_t Write(int fd, const void* ptr, size_t nbytes)
{
    ssize_t n;

again:
    if ((n = write(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}

int Close(int fd)
{
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");

    return n;
}

/*参三: 应该读取固定的字节数数据*/
ssize_t Readn(int fd, void* vptr, size_t n)
{
    size_t  nleft;              //usigned int 剩余未读取的字节数
    ssize_t nread;              //int 实际读到的字节数
    char* ptr;

    ptr = vptr;
    nleft = n;

    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;
            else
                return -1;
        }
        else if (nread == 0)
            break;

        nleft -= nread;
        ptr += nread;
    }
    return n - nleft;
}

/*:固定的字节数数据*/
ssize_t Writen(int fd, const void* vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char* ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ((nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }

        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

static ssize_t my_read(int fd, char* ptr)
{
    static int read_cnt;
    static char* read_ptr;
    static char read_buf[100];

    if (read_cnt <= 0) {
    again:
        if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if (errno == EINTR)
                goto again;
            return -1;
        }
        else if (read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;

    return 1;
}

ssize_t Readline(int fd, void* vptr, size_t maxlen)
{
    ssize_t n, rc;
    char    c, * ptr;

    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ((rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c == '\n')
                break;
        }
        else if (rc == 0) {
            *ptr = 0;
            return n - 1;
        }
        else
            return -1;
    }
    *ptr = 0;

    return n;
}

int tcp4bind(short port, const char* IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));
    if (IP == NULL) {
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }
    else {
        if (inet_pton(AF_INET, IP, &serv_addr.sin_addr.s_addr) <= 0) {
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    //端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    return lfd;
}

2.2 ラップ.h

#ifndef __WRAP_H_                                                                                                                                         
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char* s);
int Accept(int fd, struct sockaddr* sa, socklen_t* salenptr);
int Bind(int fd, const struct sockaddr* sa, socklen_t salen);
int Connect(int fd, const struct sockaddr* sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void* ptr, size_t nbytes);
ssize_t Write(int fd, const void* ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void* vptr, size_t n);
ssize_t Writen(int fd, const void* vptr, size_t n);
ssize_t my_read(int fd, char* ptr);
ssize_t Readline(int fd, void* vptr, size_t maxlen);
int tcp4bind(short port, const char* IP);
#endif

3.TCPサーバー

ソケットモデルの作成フローチャート:

3.1 簡易版

client.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERVER_IP "192.168.0.105"
#define SERVER_PORT 8008
int main()
{
    //创建套接字
    int sock_fd;
    sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    //连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, SERVER_IP, &addr.sin_addr);
    connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
    //读写数据
    char buf[1024] = "";
    while (1)
    {
        int n = read(STDIN_FILENO, buf, sizeof(buf));
        write(sock_fd, buf, n);//发送数据
        n = read(sock_fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, n);
    }
    //关闭
    close(sock_fd);

    return 0;
}

サーバー.c

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

#define SERVER_PORT 8008
#define SERVER_IP "192.168.0.106"
#define BACKLOG 128
int main()
{
    //创建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    //绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(SERVER_PORT);
    //addr.sin_addr.s_addr = INADDR_ANY;  //绑定的是通配地址
    inet_pton(AF_INET, SERVER_IP, &addr.sin_addr.s_addr);
    bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    //监听
    listen(lfd, BACKLOG);
    //提取
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
    char ip[16] = "";
    printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
    //读写
    char buf[1024] = "";
    while (1)
    {
        bzero(buf, sizeof(buf));
        int n = read(STDIN_FILENO, buf, sizeof(buf));
        write(cfd, buf, n);
        n = read(cfd, buf, sizeof(buf));
        printf("%s\n", buf);
    }
    //关闭
    close(lfd);
    close(cfd);

    return 0;
}

クライアントとサーバーが起動したら、netstat コマンドを使用してリンクのステータスを表示できます。

netstat -apn|grep 6666

3.2 マルチプロセスバージョン

サーバー.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include "wrap.h"

#define SERVER_PORT 8000
#define SERVER_IP "192.168.0.106" 
#define BACKLOG 128

void free_process(int sig)
{
    pid_t pid;
    while ((pid = waitpid(-1, NULL, WNOHANG)) > 0)
    {
        printf("child pid = %d has exited\n", pid);
    }
}
void handle_client(int cfd)
{
    char buf[1024];
    ssize_t n;

    while ((n = read(cfd, buf, sizeof(buf))) > 0)
    {
        printf("from clent :%s\n", buf);
        if (write(cfd, buf, n) < 0)
        {
            perror("Fail to sedn response to client");
            Close(cfd);
            exit(1);
        }
    }

    if (n < 0)
    {
        perror("Fail to read from client");
    }
    printf("Client closed connection\n");
    Close(cfd);
    exit(0);
}
int main()
{
    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = free_process;
    if (sigaction(SIGCHLD, &act, NULL) < 0)
    {
        perror("fail to sigaction");
        exit(1);
    }
    //创建套接字
    int lfd = tcp4bind(SERVER_PORT, NULL);
    //监听
    Listen(lfd, BACKLOG);
    //提前
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);

    while (1)
    {
        char ip[16] = "";
        //提取连接
        int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
        printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
        //fork创建子进程
        pid_t pid;
        pid = fork();

        if (pid < 0)
        {
            perror("fail to fork");
            Close(cfd);
            continue;
        }
        else if (pid == 0)
        {
            Close(lfd);
            handle_client(cfd);
            break;
        }

        Close(cfd);
    }
    //关闭
    Close(lfd);

    return 0;
}

3.3 マルチスレッドバージョン

サーバー.c

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include "wrap.h"
#include <arpa/inet.h>

typedef struct c_info {
    int cfd;
    struct sockaddr_in cliaddr;
}CINFO;


void* client_fun(void* arg)
{
    CINFO* info = (CINFO*)arg;
    char ip[16] = "";
    printf("new client ip =%s port =%d\n", inet_ntop(AF_INET, &(info->cliaddr.sin_addr.s_addr), ip, 16), ntohs(info->cliaddr.sin_port));

    while (1)
    {
        char buf[1024] = "";
        int count = 0;
        count = read(info->cfd, buf, sizeof(buf));
        if (count < 0)
        {
            perror("");
            break;
        }
        else if (count == 0)
        {
            printf("client close\n");
            break;
        }
        else
        {
            printf("%s\n", buf);
            write(info->cfd, buf, count);
        }
    }
    Close(info->cfd);
    free(info);
    pthread_exit(NULL);
}
int main(int argc, char* argv[])
{
    if (argc < 2)
    {
        perr_exit("argc < 2\n ./a.out 8000\n");
    }
    pthread_attr_t attr;
    int s = pthread_attr_init(&attr);
    if (s != 0)
    {
        perr_exit("pthread_attr_init error");
    }
    s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    if (s != 0)
    {
        perr_exit("pthread_attr_setdetachstate error");
    }
    short port = atoi(argv[1]);
    int lfd = tcp4bind(port, NULL);

    Listen(lfd, 128);
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);
    CINFO* info;

    while (1)
    {
        int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
        char ip[16] = "";

        pthread_t pthid;
        info = (CINFO*)malloc(sizeof(CINFO));
        if (NULL == info)
        {
            perr_exit("malloc error");
        }
        info->cfd = cfd;
        info->cliaddr = cliaddr;
        pthread_create(&pthid, &attr, client_fun, info);
    }
    return 0;
}

4. UDPサーバー

        TCP と比較すると、UDP 通信はテキスト メッセージの送信に似ています。データ送信前に接続を確立して維持する必要はありません。データを取得することに集中してください。3ウェイハンドシェイク処理を省略することで通信速度は大幅に向上しますが、それに伴う通信の安定性や精度は保証できません。したがって、UDP を「コネクションレスで信頼性の低いメッセージ配信」と呼びます。

        接続を作成する必要がないため、UDP のオーバーヘッドが小さく、データ転送速度が速く、リアルタイム性が優れています。主に、ビデオ会議や電話会議など、リアルタイム性が要求される通信状況で使用されます。ただし、信頼性の低いデータ送信も伴い、送信データの精度、送信シーケンス、フローを制御および保証することはできません。したがって、通常の状況ではデータ伝送には UDP プロトコルが使用されますが、データの正確性を保証するには、UDP の欠点を補う補助的な検証プロトコルをアプリケーション層に追加して目的を達成する必要があります。信頼性の高いデータ伝送。

        TCP と同様に、UDP でもバッファがいっぱいになった後にデータを受信するとパケット損失が発生する可能性があります。TCP スライディング ウィンドウ メカニズムがないため、通常は次の 2 つの方法が解決に使用されます。

  1. サーバー アプリケーション層は、データの送信速度を制御するフロー制御を設計します。
  2. 受信バッファサイズを変更するにはsetsockopt関数を使用します。のように:

#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

C/S モデル UDP:

 

サーバー.c

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

#define SERVER_PORT 8001
#define MAXLINE 1024

int main()
{
    int sockfd;
    char buf[MAXLINE];
    struct sockaddr_in seraddr, cliaddr;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    memset(&seraddr, 0, sizeof(seraddr));
    memset(&cliaddr, 0, sizeof(cliaddr));

    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SERVER_PORT);
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd, (const struct sockaddr*)&seraddr, sizeof(seraddr)) < 0)
    {
        perror("fail to bind");
        exit(1);
    }
    socklen_t len = sizeof(cliaddr);
    int n;

    while (1)
    {
        memset(buf, 0, sizeof(buf));
        n = recvfrom(sockfd, buf, MAXLINE, MSG_WAITALL, (struct sockaddr*)&cliaddr, &len);
        if (n < 0)
        {
            perror("fail to recvfrom");
            break;
        }
        else
        {
            printf("From Client data:%s\n", buf);
            if (sendto(sockfd, buf, n, 0, (const struct sockaddr*)&cliaddr, len) == -1)
            {
                perror("fail to sendto");
                break;
            }
        }
    }

    close(sockfd);
    return 0;
}

 client.c

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

#define SERVER_PORT 8001
#define MAXLINE 1024    

int main()
{
    int sockfd;
    char buf[MAXLINE];
    struct sockaddr_in seraddr;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("fail to socket");
        exit(1);
    }

    memset(&seraddr, 0, sizeof(seraddr));

    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);

    int n;
    socklen_t len = sizeof(seraddr);

    while (1)
    {
        n = read(STDIN_FILENO, buf, sizeof(buf));
        if(sendto(sockfd, buf, n, 0, (const struct sockaddr*)&seraddr, len) == -1)
        {
            perror("fail to sendto");
            break;
        }

        memset(buf, 0, sizeof(buf));
        n = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&seraddr, &len);
        if (n < 0)
        {
            perror("fail to recvfrom");
            break;
        }
        else
        {
            printf("From Server data:%s\n", buf);
        }
    }
    close(sockfd);

    return 0;
}

5. ローカルソケット

        ソケット API はもともとネットワーク通信用に設計されましたが、後に UNIX ドメイン ソケットであるソケット フレームワークに基づいて IPC メカニズムが開発されました。ネットワーク ソケットは同じホスト上のプロセス間通信 (ループバック アドレス 127.0.0.1 経由) にも使用できますが、UNIX ドメイン ソケットは IPC にとってより効率的です。ネットワーク プロトコル スタック、パッケージ化とアンパックを通過する必要がなく、チェックサム計算、シーケンス番号と応答などの維持、アプリケーション層データをあるプロセスから別のプロセスにコピーするだけです。これは、ネットワーク プロトコルが信頼性の低い通信向けに設計されているのに対し、IPC メカニズムは本質的に信頼性の高い通信であるためです。UNIX ドメイン ソケットには、TCP と UDP に似た 2 つのストリーム指向とパケット指向の API インターフェイスも用意されていますが、メッセージ指向の UNIX ドメイン ソケットも信頼性が高く、メッセージが失われたり順序が崩れたりすることはありません。

        UNIX ドメイン ソケットは全二重であり、豊富な API インターフェイス セマンティクスを備えています。他の IPC メカニズムに比べて明らかな利点があります。これは最も広く使用されている IPC メカニズムとなっています。たとえば、X Window サーバーと GUI プログラム間の通信は UNIX ドメイン ソケットを介して行われます. .

        UNIX ドメイン ソケットを使用するプロセスは、ネットワーク ソケットのプロセスと非常によく似ています。最初にソケット ファイル記述子を作成するには、socket() を呼び出す必要があります。アドレス ファミリは AF_UNIX として指定され、タイプは SOCK_DGRAM または SOCK_STREAM で、プロトコルはパラメータは引き続き 0 に指定されます。

        UNIX ドメイン ソケットとネットワーク ソケット プログラミングの最も明らかな違いは、構造 sockaddr_un で表されるアドレス形式が異なることです。ネットワーク プログラミングのソケット アドレスは IP アドレスとポート番号を加算したものですが、UNIX ドメイン ソケットのアドレスはファイル システム内のソケット タイプ ファイルです。このソケット ファイルのパスは、bind() 呼び出しによって作成されます。bind() 呼び出し時にファイルがすでに存在する場合、bind() はエラーを返します。

        ネットワーク ソケット アドレス構造とローカル ソケット アドレス構造を比較します。

struct sockaddr_in {     __kernel_sa_family_t sin_family; /* アドレス ファミリ */ アドレス構造タイプ         __be16 sin_port; /* ポート番号 */ ポート番号         struct in_addr sin_addr; /* インターネット アドレス */ IP アドレス}; struct sockaddr_un {     __kernel_sa_family_t sun_family; /* AF_UNIX * / アドレス構造型         char sun_path[UNIX_PATH_MAX]; /* パス名 */ ソケットファイル名(パスを含む)};







次のプログラムは、UNIX ドメイン ソケットをアドレスにバインドします。 

サイズ = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->member) 

 サービス:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

char *socket_path = "/tmp/demo_socket";

int main(void) {
    struct sockaddr_un addr;
    char buf[100];
    int fd,cl,rc;

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

    unlink(socket_path);

    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind error");
        exit(-1);
    }

    if (listen(fd, 5) == -1) {
        perror("listen error");
        exit(-1);
    }

    while (1) {
        if ((cl = accept(fd, NULL, NULL)) == -1) {
            perror("accept error");
            continue;
        }

        while ((rc=read(cl,buf,sizeof(buf))) > 0) {
            printf("read %u bytes: %.*s\n", rc, rc, buf);
            write(cl, buf, rc);
        }
        if (rc == -1) {
            perror("read");
            exit(-1);
        }
        else if (rc == 0) {
            printf("EOF\n");
            close(cl);
        }
    }

    return 0;
}

クライアント:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

char *socket_path = "/tmp/demo_socket";

int main(void) {
    struct sockaddr_un addr;
    char buf[100];
    int fd,rc;

    if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path)-1);

    if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("connect error");
        exit(-1);
    }

    while(1) {
        printf("Enter message to send: ");
        fgets(buf, sizeof(buf), stdin);
        if ((rc = write(fd, buf, strlen(buf))) > 0) {
            printf("Message sent\n");
            read(fd, buf, sizeof(buf));
            printf("Server replied : %s\n", buf);
        }
        else {
            printf("Error or connection closed\n");
            break;
        }
    }

    return 0;
}

要約:

        C言語で実装されたコードですので、理解して自分で入力することをお勧めします。

おすすめ

転載: blog.csdn.net/crr411422/article/details/131680574