【Linux】ソケットプログラミング(ソケットソケットの紹介、バイトオーダー、ソケットアドレス、IPアドレス変換関数、ソケット関数、TCP通信実装)

オレンジ色

1. ソケットの概要

いわゆるソケットは、ネットワーク内の異なるホスト上のアプリケーション プロセス間の双方向通信のためのエンドポイントを抽象化したものです。

ソケットはネットワーク上のプロセス通信の一端であり、アプリケーション層プロセスがネットワーク プロトコルを使用してデータを交換するためのメカニズムを提供します。位置的には、ソケットはアプリケーション プロセスに接続され、ネットワーク プロトコル スタックは下位に接続されており、アプリケーション プログラムがネットワーク プロトコル プロセスを介して通信するためのインターフェイスであり、アプリケーション プログラムがネットワーク プロトコルと対話するためのインターフェイスです。 。

ネットワーク環境で通信するための API であり、使用する各ソケットにはプロセスが接続されています。通信中、ネットワーク アプリケーションの 1 つが、送信する情報をそれが配置されているホストのソケットに書き込み、ソケットはネットワーク インターフェイスに接続された伝送媒体を介して、その情報を別のホストのソケットに送信します。カード(NIC)に接続して、相手がこのメッセージを受信できるようにします。socket是由IP地址和端口结合的、アプリケーション層プロセスがデータ パケットを送信するためのメカニズムを提供します。

ソケットは元々「ソケット」を意味し、Linux 環境ではプロセス間のネットワーク通信を表すために使用される特別なファイルの種類です。これは本質的に、バッファを利用してカーネルによって形成される疑似ファイルです。操作を容易にするためにファイルとして設定し、ファイル記述子を通じて操作できます。パイプ型の場合と同様、Linux システムでピリオドをファイルにカプセル化する目的は、ソケットの読み取りと書き込み、およびファイルの読み取りと書き込みが同じように動作するようにインターフェイスを統合することです。違いは、パイプはローカルのプロセス間通信に使用されるのに対し、ソケットは主にネットワーク プロセス間のデータ転送に使用されることです。

ソケットは全二重通信です。つまり、データの読み取りとデータの出力を同時に行うことができます。
ここに画像の説明を挿入します

IP アドレス (論理アドレス): ネットワーク内でホストを一意に識別する
ポート番号: ホスト内でプロセスを一意に識別する
IP+ポート番号: ネットワーク環境内でプロセスを一意に識別する

-サーバー側: 接続を受動的に受け入れ、通常は積極的に接続を開始しません。

-クライアント: サーバーへの接続をアクティブに開始します。

2. エンディアンネス

導入

これで、CPU のアキュムレータは一度に (少なくとも) 4 バイト (32 ビット マシン)、つまり整数をロードできるようになりました。これらの 4 バイトがメモリ内で配置される順序は、アキュムレータによってロードされる整数値に影響を及ぼしますが、これはバイト順序の問題です。さまざまなコンピュータ アーキテクチャでは、バイトやワードなどの記憶メカニズムが異なるため、コンピュータ通信の分野では非常に重要な問題、つまり、通信当事者によって交換される情報単位をどのような順序で送信するかが問題となります。合意されたルールに達していない場合、通信当事者は正しいエンコード/デコードを実行できず、通信障害が発生します。

バイトオーダーとは、名前が示すとおり、1 バイトを超える種類のデータがメモリに格納される順序です (もちろん、1 バイトのデータの順序について話す必要はありません)。

バイトオーダーはビッグエンディアンとリトルエンディアンに分けられます。大端字节序是指一个整数的高位字节存储在内存的低地址位置,低位字节存储在内存的高地址位置。小端字节序则是指一个整数的高位字节存储在内存高地址处,而低位字节则存储在内存的低地址处

ここに画像の説明を挿入します

明らかに、数値の場合、さらに左にある数値が上位ビットであり、さらに右にある数値が下位ビットです。

次に、現在のホストのバイトオーダーを検出するプログラムを作成します:
共用体について知らない場合は、この記事を参照してください - C 言語 | 共用体の詳細な説明

/*  
    字节序:字节在内存中存储的顺序。
    小端字节序:数据的高位字节存储在内存的高位地址,低位字节存储在内存的低位地址
    大端字节序:数据的低位字节存储在内存的高位地址,高位字节存储在内存的低位地址
*/

// 通过代码检测当前主机的字节序
#include <stdio.h>

int main() {
    
    

    union {
    
    
        short value;    // 2字节
        char bytes[sizeof(short)];  // char[2]
    } test; 

    test.value = 0x0102;
    if((test.bytes[0] == 1) && (test.bytes[1] == 2)) {
    
    
        printf("大端字节序\n");
    } else if((test.bytes[0] == 2) && (test.bytes[1] == 1)) {
    
    
        printf("小端字节序\n");
    } else {
    
    
        printf("未知\n");
    }

    return 0;
}

ここに画像の説明を挿入します

バイトオーダー変換機能

フォーマットされたデータが、異なるバイト順序を使用して 2 つのホスト間で直接受け渡されると、受信側は必然的にデータを誤って解釈します。この問題を解決する方法は、送信側は送信するデータを常にビッグエンディアンのバイトオーダーデータに変換してから送信し、受信側は相手が送信するデータが常にビッグエンディアンのバイトオーダーであることを認識することです。したがって、受信側は、それ自体が採用するバイト順序によって、受信データを変換するかどうかを決定します (リトル エンディアン マシンは変換しますが、ビッグ エンディアン マシンは変換しません)。

网络字节顺序TCPIP で規定されているデータ表現形式です。特定の CPU タイプやオペレーティング システムなどとは関係がないため、異なるホスト間でデータを送信するときにデータを正しく解釈できます。ネットワーク バイト オーダーはビッグ エンディアン ソートを採用しています。 。

BSD ソケットは、プログラマの利便性を考慮して、カプセル化された変換インターフェイスを提供します。ホスト バイト オーダーからネットワーク バイト オーダーへの変換関数: htons、htonl、ネットワーク バイト オーダーからホスト バイト オーダーへの変換関数: ntohs、ntohl が含まれます。

/*
h - host   主机,主机字节序

to   转换成什么

n - network   网络字节序

s - short unsigned short   端口

l - long unsigned int   IP

 网络通信时,需要将主机字节序转换成网络字节序(大端),
    另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。

    // 转换端口
    uint16_t htons(uint16_t hostshort);		// 主机字节序 - 网络字节序
    uint16_t ntohs(uint16_t netshort);		// 网络字节序 - 主机字节序

    // 转IP
    uint32_t htonl(uint32_t hostlong);		// 主机字节序 - 网络字节序
    uint32_t ntohl(uint32_t netlong);		// 网络字节序 - 主机字节序
*/

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

int main() {
    
    

    // htons 转换端口
    unsigned short a = 0x0102;
    printf("a : %x\n", a);
    unsigned short b = htons(a);
    printf("b : %x\n", b);

    printf("=======================\n");

    // htonl  转换IP
    char buf[4] = {
    
    192, 168, 1, 100};
    int num = *(int *)buf;
    printf("num : %d\n", num);
    
    int sum = htonl(num);
    unsigned char *p = (char *)&sum;

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

    printf("=======================\n");

    // ntohl
    unsigned char buf1[4] = {
    
    1, 1, 168, 192};
    int num1 = *(int *)buf1;
    int sum1 = ntohl(num1);
    unsigned char *p1 = (unsigned char *)&sum1;
    printf("%d %d %d %d\n", *p1, *(p1+1), *(p1+2), *(p1+3));
    
     // ntohs


    return 0;
}

ここに画像の説明を挿入します

質問: 印刷された番号が 1677830336 の場合、何が起こっていますか?
答え: // 192 168 1 100
  // 11000000 10101000 000000001 01101000
// このマシンはリトルエンディアンなので、192 が下位ビットに、100 が上位ビットにあるため、数値は // 01101000 00000001 10101000 11000000 =
1677830336

3. ソケットアドレス

ソケット ネットワーク プログラミング インターフェイスでは、ソケット アドレスは構造体 sockaddr であり、次のように定義されます。

#include <bits/socket.h>

struct sockaddr{
    
                                    //已经被废弃掉

        sa_family_t sa_family;
        char sa_data[14];
};

typedef unsigned short int sa_family_t;

メンバー:
    sa_family メンバーは、アドレス ファミリ タイプ (sa_family_t) の変数です。通常、アドレス ファミリ タイプはプロトコル タイプに対応します。一般的なプロトコル ファミリと対応するアドレス ファミリは次のとおりです。

プロトコルファミリー アドレスファミリー 説明する
PF_UNIX OF_UNIX UNIX ローカル ドメイン プロトコル スイート
PF_INET OF_INET TCP/IPv4プロトコルスイート
PF_INET6 AF_INET6 TCP/IPv6プロトコルスイート

プロトコル ファミリ PF_* とアドレス ファミリ AF_* は両方ともヘッダー ファイル bits/socket.h で定義されています。これらは同じ値を持ち、混在して使用できます (いずれにせよ、どちらもマクロ定義です。マクロ定義は前処理段階でのマクロの置換です)したがって、混合使用に適しています。コンパイルと実行には影響しません)

sa_data メンバーは、ソケット アドレス値を格納するために使用されます。ただし、異なるプロトコル ファミリのアドレス値は異なる意味と長さを持っており
ここに画像の説明を挿入します
、14 バイトには IPv4 アドレスのみを保持でき、IPv6 アドレスを保持できないことがわかります。したがって、この構造表現は廃止されました。Linux では、次の新しいユニバーサル ソケット アドレス構造が定義されています。この構造は、アドレス値を格納するのに十分なスペースを提供するだけでなく、メモリ アライメントも行われます。[メモリ アライメント] CPU アクセスを高速化できます。

この構造は/usr/include/linux/in.h で定義されています。

#include <bits/socket.h>
struct sockaddr_storage
{
    
    
sa_family_t sa_family;
unsigned long int __ss_align; //不用管,用来作内存对齐的
char __ss_padding[ 128 - sizeof(__ss_align) ];
};
typedef unsigned short int sa_family_t;

プライベートソケットアドレス

多くのネットワーク プログラミング関数は、IPv4 プロトコルよりも前に誕生しました (カスタム プロトコルを使用し、双方がルールに同意します)。当時は、それらはすべて、ストライクされたソケット追加構造を使用していました。*上位互換性のために、ソケット追加は現在 (void に縮退しています) ) その機能は、関数にアドレスを渡すことです。関数が sockaddr_in か sockaddr_in6 かはアドレス ファミリによって決定され、関数は内部で必要なアドレス タイプに型を強制的に変換します

覚えておくべき主な点は、struct sockaddr_in
ここに画像の説明を挿入します
下図の 2 番目の UNIX ローカル ドメイン プロトコル ファミリでは、次の専用ソケット アドレス構造が使用されていることです。

#include <sys/un.h>
struct sockaddr_un
{
    
    
sa_family_t sin_family;
char sun_path[108];
};

TCP/IP プロトコル スイートには、sockaddr_in と sockaddr_in6 という 2 つの専用ソケット アドレス構造があり、それぞれ IPv4 と IPv6 に使用されます。

#include <netinet/in.h>
struct sockaddr_in
{
    
    
sa_family_t sin_family;         /* __SOCKADDR_COMMON(sin_) */
in_port_t sin_port;             /* Port number. 2个字节的端口号 */
struct in_addr sin_addr;        /* Internet address. 4个字节的ip地址 */

/* Pad to size of `struct sockaddr'.  剩余填充的部分*/
unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) - sizeof (struct in_addr)];
};


struct in_addr
{
    
    
in_addr_t s_addr;
};


struct sockaddr_in6
{
    
    
sa_family_t sin6_family;
in_port_t sin6_port; /* Transport layer port # */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};


typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

すべての特殊なソケット アドレス (および sockaddr_storage) 型の変数は、実際に使用するときに、一般的なソケット アドレス型 sockaddr (強制変換のみ) に変換する必要があります。これは、すべてのソケット プログラミング インターフェイスで使用されるアドレス パラメーターの型が sockaddr であるためです

4. IPアドレス変換機能

人々は、IPv4 アドレスを表すためにドット付き 10 進数文字列、IPv6 アドレスを表すために 16 進数文字列など、IP アドレスを表すために読みやすい文字列を使用することに慣れていますが、プログラミングでは、最初にそれらを整数 (バイナリ) に変換する必要があります。逆に、ログの場合は、整数で表される IP アドレスを読み取り可能な文字列に変換する必要があります。

p:点分十进制的IP字符串

n:表示network,网络字节序的整数

#include  <arpa/inet.h>

将IP地址从字符串形式转化为二进制整数形式
int inet_pton(int af,const char *src,void *dst);

af:地址族: AF_INET AF_INET6

src:需要转换的点分十进制的IP字符串

dst:转换后的结果保存在这个里面

将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);

af:AF_INET   AF_INE6

src: 要转换的ip的整数的地址

dst: 转换成的IP地址字符串保存的地方

size:第三个参数的大小(数组的大小)

返回值:返回转换后的数据的地址(字符串),和 dst 是一样的

点分十进制 --->  网络字节序   inet_pton

网络字节序 --->  点分十进制   inet_ntop

コード例:

/*
    #include <arpa/inet.h>
    // p:点分十进制的IP字符串,n:表示network,网络字节序的整数
    int inet_pton(int af, const char *src, void *dst);
        af:地址族: AF_INET  AF_INET6
        src:需要转换的点分十进制的IP字符串
        dst:转换后的结果保存在这个里面

    // 将网络字节序的整数,转换成点分十进制的IP地址字符串
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
        af:地址族: AF_INET  AF_INET6
        src: 要转换的ip的整数的地址
        dst: 转换成IP地址字符串保存的地方
        size:第三个参数的大小(数组的大小)
        返回值:返回转换后的数据的地址(字符串),和 dst 是一样的

*/

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


int main() {
    
    

    // 创建一个ip字符串,点分十进制的IP地址字符串
    char buf[] = "192.168.1.4";
    unsigned int num = 0;

    // 将点分十进制的IP字符串转换成网络字节序的整数
    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));


    // 将网络字节序的IP整数转换成点分十进制的IP字符串
    char ip[16] = ""; //字符串IP地址四段,每段最多三个字节,加上3个“.”,再加一个字符串结束符
    const char * str =  inet_ntop(AF_INET, &num, ip, 16);
    printf("str : %s\n", str);
    printf("ip : %s\n", ip);
    printf("%d\n", ip == str);

    return 0;
}

ここに画像の説明を挿入します

5.ソケット機能

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//包含了这个头文件,上面两个就可以省略

int socket(int domain,int type,int protoco1);
	- 功能:创建一个套接字
	- 参数:
		- domain:协议族
			AF_INET:ipv4
			AF_INET6:ipv6
			AF_UNIX,AF_LOCAL:本地套接字通信(进程间通信)
		- type:通信过程中使用的协议类型
			SOCK_STREAM:流式协议(TCP等)
			SOCK_DGRAM:报式协议(UDP等)
		- protocol:具体的一个协议。一般写0
			- SOCK_STREAM:流式协议默认使用TCP
			- SOCK_DGRAM:报式协议默认使用UDP
		- 返回值:
			- 成功:返回文件描述符,操作的就是内核缓冲区
			- 失败:-1	
			
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
	- 功能:绑定,将fd和本地的IP+端口进行绑定
	- 参数:
			- socket:通过socket函数得到的文件描述符
			- addr:需要绑定的socket地址,这个地址封装了ip和端口号的信息
			- addr len:第二个参数结构体占的内存大小
			- 返回值:成功返回0,失败返回-1
			
int listen(int sockfd,int backlog);// /proc/sys/net/cor e/somaxconn
	- 功能:监听这个socket上的连接
	- 参数:
		- sockfd:通过socket()函数得到的文件描述符
		- backlog:未连接的和已连接的和的最大值,超过该设定的最大值的连接会被舍弃掉。但该设定值不能超过/proc/sys/net/cor e/somaxconn这个文件里的数值
		
int accept(int sockfd,struct sockaddr *addr ,sock1en_t *addrlen);
	- 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户的连接
	- 参数:
			- sockfd:用于监听的文件描述符
			- addr:传出参数,记录了连接成功后客户端的地址信息(IP和端口号)
			- addrlen:指定第二个参数的对应的内存的大小
	- 返回值:
			- 成功:返回用于通信的文件描述符
			- -1:失败
			
int connect(int sockfd,const struct sockaddr *addr,socklen_t addr1en);
	- 功能:客户端连接服务器
	- 参数:
			- sockfd:用于通信的文件描述符 
			- addr:客户端要连接的服务器的地址信息
			- addrlen:第二个参数的内存大小
	- 返回值:成功返回0,时报返回-1

ssize_t write(int fd,const void *buf, size_t count);
ssize_t read(int fd,void *buf, size_t count);

6. TCP通信の実装(サーバーとクライアント)

なお、このプログラムは、TCP通信時にサーバーが情報を受信する手順を基に作成されていますので、まずはこちらの記事を参照してください。

サービスターミナル

// TCP 通信的服务器端

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

int main() {
    
    

    // 1.创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    if(lfd == -1) {
    
    
        perror("socket");
        exit(-1);
    }

    // 2.绑定
    struct sockaddr_in saddr;        //这个结构体本文章的上半部分有详细的介绍,不了解可以去看看
    saddr.sin_family = AF_INET;
    // inet_pton(AF_INET, "192.168.193.128", &saddr.sin_addr.s_addr);
    saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0
    saddr.sin_port = htons(9999);
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1) {
    
    
        perror("bind");
        exit(-1);
    }

    // 3.监听
    ret = listen(lfd, 8);
    if(ret == -1) {
    
    
        perror("listen");
        exit(-1);
    }

    // 4.接收客户端连接
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
    
    if(cfd == -1) {
    
    
        perror("accept");
        exit(-1);
    }

    // 输出客户端的信息
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    // 5.通信
    char recvBuf[1024] = {
    
    0};
    while(1) {
    
    
        
        // 获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == -1) {
    
    
            perror("read");
            exit(-1);
        } else if(num > 0) {
    
    
            printf("recv client data : %s\n", recvBuf);
        } else if(num == 0) {
    
    
            // 表示客户端断开连接
            printf("clinet closed...");
            break;
        }

        char * data = "hello,i am server";
        // 给客户端发送数据
        write(cfd, data, strlen(data));
    }
   
    // 关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}

質問: サーバーがクライアントのファイル記述子の内容の読み取りを完了するために read を 1 回呼び出したとします。この時点では、クライアントはデータを記述子に書き込みませんが、切断はしません。その後、サーバーは 2 回目の read を呼び出します。返されるのか?
回答: パイプ内にデータがない場合の読み取りパイプの特性: 1. 書き込み端が完全に閉じられており、読み取りは 0 を返します (ファイルの終わりを読み取るのと同等) 2. 書き込み端が完全に閉じられていないため、そしてブロックを読んで待機します。私の記事[Linux] パイプの読み取りおよび書き込み特性とパイプのノンブロッキング設定を参照してください。

クライアント

// TCP通信的客户端

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

int main() {
    
    

    // 1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
    
    
        perror("socket");
        exit(-1);
    }

    // 2.连接服务器端,注意是要服务器的ip地址和端口
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.177.146", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
    
    
        perror("connect");
        exit(-1);
    }

    
    // 3. 通信
    char recvBuf[1024] = {
    
    0};
    while(1) {
    
    

        char * data = "hello,i am client";
        // 给客户端发送数据
        write(fd, data , strlen(data));

        sleep(1);
        
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
    
    
            perror("read");
            exit(-1);
        } else if(len > 0) {
    
    
            printf("recv server data : %s\n", recvBuf);
        } else if(len == 0) {
    
    
            // 表示服务器端断开连接
            printf("server closed...");
            break;
        }

    }

    // 关闭连接
    close(fd);

    return 0;
}

クライアントの 21 行目の IP アドレスは、自身のホストの IP アドレスである必要があります。私のホストの IP アドレスは 192.168.177.146 です。

2 つのファイルを別々にコンパイルして実行します。結果は次のとおりです。
ここに画像の説明を挿入します
ここに画像の説明を挿入します
サーバーの実行プログラムでは、クライアントの IP が 192.168.177.146 として出力され、ポートが 35302 にランダムに割り当てられていることがわかります。

注: 最初にサーバーを起動し、次にクライアントを起動します。

下調べ: サーバーをエコー サーバーに変更します。つまり、サーバーはクライアントから送信されたデータを送り返します。キーボードからデータを入力してサーバーに送信するようにクライアントを変更します。したがって、最終的な効果は、キーボードから入力すると、クライアントがそれをサーバーに送信し、サーバーが同じコンテンツを送り返すことになります。

サーバ:

// TCP 通信的服务器端

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

int main() {
    
    

    // 1.创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    if(lfd == -1) {
    
    
        perror("socket");
        exit(-1);
    }

    // 2.绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    // inet_pton(AF_INET, "192.168.193.128", &saddr.sin_addr.s_addr);
    saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0
    saddr.sin_port = htons(9999);
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1) {
    
    
        perror("bind");
        exit(-1);
    }

    // 3.监听
    ret = listen(lfd, 8);
    if(ret == -1) {
    
    
        perror("listen");
        exit(-1);
    }

    // 4.接收客户端连接
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
    
    if(cfd == -1) {
    
    
        perror("accept");
        exit(-1);
    }

    // 输出客户端的信息
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    // 5.通信
    char recvBuf[1024] = {
    
    0};
    while(1) {
    
    
        memset(recvBuf, 0, 1024);
        // 获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == -1) {
    
    
            perror("read");
            exit(-1);
        } else if(num > 0) {
    
    
            printf("recv client data : %s\n", recvBuf);
        } else if(num == 0) {
    
    
            // 表示客户端断开连接
            printf("clinet closed...");
            break;
        }

        char * data = recvBuf;;
        // 给客户端发送数据
        write(cfd, data, strlen(data));
    }
   
    // 关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}

クライアント:

// TCP通信的客户端

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

int main() {
    
    

    // 1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
    
    
        perror("socket");
        exit(-1);
    }

    // 2.连接服务器端,注意是要服务器的ip地址和端口
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.177.146", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
    
    
        perror("connect");
        exit(-1);
    }

    
    // 3. 通信
    char recvBuf[1024] = {
    
    0};
    while(1) {
    
    

        char data[1024];
        memset(data, 0, 1024);
        printf("请输入发送数据:\n");
        scanf("%s", data);
        // 给客户端发送数据
        write(fd, data , strlen(data));

        sleep(1);
        
        memset(recvBuf, 0, 1024);
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
    
    
            perror("read");
            exit(-1);
        } else if(len > 0) {
    
    
            printf("recv server data : %s\n", recvBuf);
        } else if(len == 0) {
    
    
            // 表示服务器端断开连接
            printf("server closed...");
            break;
        }

    }

    // 关闭连接
    close(fd);

    return 0;
}

ここに画像の説明を挿入します
ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/mhyasadj/article/details/131181974