Programação de soquete [Linux] (introdução de soquete, ordem de bytes, endereço de soquete, função de conversão de endereço IP, função de soquete, implementação de comunicação TCP)

cor laranja

1. Introdução aos soquetes

O chamado soquete é uma abstração de terminais para comunicação bidirecional entre processos de aplicativos em diferentes hosts da rede.

Um soquete é uma extremidade da comunicação do processo na rede, fornecendo um mecanismo para que os processos da camada de aplicação troquem dados usando protocolos de rede. Em termos de posição, o soquete está conectado ao processo de aplicação e a pilha de protocolo de rede está conectada.É a interface para o programa aplicativo se comunicar através do processo de protocolo de rede e a interface para o programa aplicativo interagir com o protocolo de rede .

É uma API para comunicação em ambiente de rede, cada soquete em uso possui um processo conectado a ele. Durante a comunicação, uma das aplicações de rede grava uma informação a ser transmitida no soquete do host onde está localizada, e o soquete envia a informação para o soquete de outro host através do meio de transmissão conectado à interface de rede cartão (NIC), para que a outra parte possa receber essas informações. socket是由IP地址和端口结合的, fornece um mecanismo para que os processos da camada de aplicação transmitam pacotes de dados.

Socket originalmente significa "soquete".No ambiente Linux, é um tipo de arquivo especial usado para representar a comunicação de rede entre processos. É essencialmente um pseudo arquivo formado pelo kernel com a ajuda de um buffer. Configure-o como um arquivo para facilitar nossa operação, podemos operar através do descritor de arquivo. Assim como acontece com o tipo pipe, o objetivo de encapsular períodos em arquivos no sistema Linux é unificar a interface para que a leitura e gravação de soquetes e a leitura e gravação de arquivos funcionem da mesma forma. A diferença é que os pipes são usados ​​para comunicação local entre processos, enquanto os soquetes são usados ​​principalmente para transferência de dados entre processos de rede.

Socket é uma comunicação full-duplex, ou seja, os dados podem ser lidos e emitidos ao mesmo tempo.
Insira a descrição da imagem aqui

Endereço IP (endereço lógico): identifica exclusivamente um host na rede
Número da porta: identifica exclusivamente um processo em um host
IP+número da porta: identifica exclusivamente um processo no ambiente de rede.

-Lado do servidor: aceita conexões passivamente e geralmente não inicia conexões ativamente.

-Cliente: inicia ativamente uma conexão com o servidor

2. Endianismo

Introdução

Agora o acumulador da CPU pode carregar (pelo menos) 4 bytes por vez (máquina de 32 bits), ou seja, um número inteiro. Então a ordem em que esses 4 bytes são organizados na memória afetará o valor inteiro carregado pelo acumulador. Este é um problema de ordem de bytes. Nas várias arquitecturas informáticas, os mecanismos de armazenamento de bytes, palavras, etc. são diferentes, o que levanta uma questão muito importante no domínio da comunicação informática, ou seja, em que ordem devem ser transmitidas as unidades de informação trocadas pelas partes comunicantes. Se as regras acordadas não forem alcançadas, as partes comunicantes não serão capazes de realizar a codificação/decodificação correta, resultando em falha na comunicação.

Ordem de bytes, como o nome indica, é a ordem em que os dados de um tipo maior que um byte são armazenados na memória (é claro que não há necessidade de falar sobre a ordem dos dados de um byte).

A ordem dos bytes é dividida em Big-Endian e Little-Endian. 大端字节序是指一个整数的高位字节存储在内存的低地址位置,低位字节存储在内存的高地址位置。小端字节序则是指一个整数的高位字节存储在内存高地址处,而低位字节则存储在内存的低地址处.

Insira a descrição da imagem aqui

Obviamente, para um número, o número mais à esquerda é o bit mais alto e o número mais à direita é o bit mais baixo.

A seguir, escreva um programa para detectar a ordem de bytes do host atual:
Se você não sabe sobre sindicatos, pode consultar este artigo - Linguagem C | Explicação detalhada dos sindicatos

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

// 通过代码检测当前主机的字节序
#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;
}

Insira a descrição da imagem aqui

Função de conversão de ordem de bytes

Quando dados formatados são passados ​​diretamente entre dois hosts usando ordens de bytes diferentes, o terminal receptor irá inevitavelmente interpretá-los incorretamente. A maneira de resolver o problema é: a extremidade emissora sempre converte os dados a serem enviados em dados de ordem de bytes big-endian antes de enviá-los, e a extremidade receptora sabe que os dados enviados pela outra parte estão sempre em ordem de bytes big-endian , então a extremidade receptora pode A ordem de bytes adotada por si só determina se os dados recebidos devem ser convertidos (a máquina little endian converte, a máquina big endian não converte).

网络字节顺序É um formato de representação de dados especificado em TCPIP. Não tem nada a ver com o tipo específico de CPU, sistema operacional, etc., garantindo assim que os dados possam ser interpretados corretamente quando transmitidos entre diferentes hosts. A ordem de bytes da rede adota a classificação big-endian .

BSD Socket fornece uma interface de conversão encapsulada para conveniência do programador. Incluindo as funções de conversão da ordem de bytes do host para a ordem dos bytes da rede: htons, htonl; as funções de conversão da ordem dos bytes da rede para a ordem dos bytes do host: 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;
}

Insira a descrição da imagem aqui

Pergunta: O que acontece quando o número impresso é 1677830336?
Resposta: // 192 168 1 100
  // 11000000 10101000 000000001 01101000
//Esta máquina é little endian, então 192 está no bit baixo e 100 está no bit alto, então o num é // 01101000 00000001
10101000 11000 000 = 1677830336

3. endereço do soquete

Na interface de programação de rede de soquete, o endereço do soquete é a estrutura sockaddr, que é definida da seguinte forma:

#include <bits/socket.h>

struct sockaddr{
    
                                    //已经被废弃掉

        sa_family_t sa_family;
        char sa_data[14];
};

typedef unsigned short int sa_family_t;

Membros:
    O membro sa_family é uma variável do tipo de família de endereços (sa_family_t). Os tipos de família de endereços geralmente correspondem aos tipos de protocolo. As famílias de protocolos comuns e as famílias de endereços correspondentes são as seguintes:

família de protocolo endereço família descrever
PF_UNIX OF_UNIX Conjunto de protocolos de domínio local UNIX
PF_INET OF_INET Conjunto de protocolos TCP/IPv4
PF_INET6 AF_INET6 Conjunto de protocolos TCP/IPv6

A família de protocolos PF_* e a família de endereços AF_* são ambas definidas no arquivo de cabeçalho bits/socket.h. Eles têm o mesmo valor e podem ser usados ​​de forma mista (de qualquer forma, ambos são definições de macro. As definições de macro são substituições de macro no estágio de pré-processamento , portanto, são adequados para uso misto. Não haverá impacto na compilação e execução)

O membro sa_data é usado para armazenar o valor do endereço do soquete. No entanto, os valores de endereço de diferentes famílias de protocolos têm significados e comprimentos diferentes.Pode
Insira a descrição da imagem aqui
-se observar que 14 bytes só podem conter endereços IPv4, mas não podem conter endereços IPv6. Portanto, esta representação de estrutura foi abandonada. O Linux define a seguinte nova estrutura de endereço de soquete universal. Essa estrutura não apenas fornece espaço suficiente para armazenar valores de endereço, mas também é alinhada à memória [alinhamento da memória] Pode acelerar o acesso à CPU]

Esta estrutura é definida em:/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;

Endereço de soquete privado

Muitas funções de programação de rede nasceram antes do protocolo IPv4 (use um protocolo personalizado e ambas as partes concordam com uma regra). Naquela época, todas elas usavam a estrutura socketaddr atingida. *Para compatibilidade futura, socketaddr agora degenerou em ( void ) Sua função é passar um endereço para a função. Se a função é sockaddr_in ou sockaddr_in6 é determinado pela família de endereços e, em seguida, a função é forçada a converter o tipo para o tipo de endereço necessário internamente .

A principal coisa a lembrar é que a segunda struct sockaddr_in
Insira a descrição da imagem aqui
família de protocolos de domínio local UNIX na figura abaixo usa a seguinte estrutura de endereço de soquete dedicado:

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

O conjunto de protocolos TCP/IP possui duas estruturas de endereço de soquete dedicadas, sockaddr_in e sockaddr_in6, que são usadas para IPv4 e IPv6 respectivamente:

#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))

Todas as variáveis ​​de tipo de endereço de soquete especial (e sockaddr_storage) precisam ser convertidas para o tipo de endereço de soquete geral sockaddr (apenas forçar a conversão) quando realmente usadas, porque o tipo de parâmetro de endereço usado por todas as interfaces de programação de soquete é sockaddr .

4. Função de conversão de endereço IP

As pessoas estão acostumadas a usar strings legíveis para representar endereços IP, como strings decimais pontilhadas para representar endereços IPv4 e strings hexadecimais para representar endereços IPv6, mas na programação precisamos primeiro convertê-los em números inteiros (binários). Pelo contrário, para o log, precisamos converter o endereço IP representado por um número inteiro em uma string legível.

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

Exemplo de código:

/*
    #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;
}

Insira a descrição da imagem aqui

5. Função de soquete

#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. Implementação da comunicação TCP (servidor e cliente)

Observe que este programa foi escrito com base nas etapas do servidor que recebe informações durante a comunicação TCP. Você pode consultar este artigo primeiro .

Terminal de serviço

// 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;
}

Pergunta: Suponha que o servidor chame read uma vez para terminar de ler o conteúdo do descritor de arquivo do cliente. Neste momento, o cliente não grava dados no descritor, mas não desconecta, então o servidor chama read pela segunda vez. O que será retornado?
Resposta: Características dos pipes de leitura, quando não há dados no pipe: 1. O final da escrita está completamente fechado e a leitura retorna 0 (equivalente à leitura do final do arquivo).2. O final da escrita não está completamente fechado, e lê blocos e espera. Consulte meu artigo [Linux] As características de leitura e gravação de pipes e a configuração de pipes como sem bloqueio

cliente

// 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;
}

O endereço IP na linha 21 do cliente deve ser o endereço IP do seu próprio host. O endereço IP do meu host é 192.168.177.146

Compile e execute os dois arquivos separadamente. Os resultados são os seguintes:
Insira a descrição da imagem aqui
Insira a descrição da imagem aqui
Você pode ver que no programa de execução do servidor, o IP do cliente é impresso como 192.168.177.146 e a porta é atribuída aleatoriamente a 35302

Nota: Inicie primeiro o servidor e depois o cliente.

Lição de casa: Mude o servidor para um servidor de eco, ou seja, o servidor devolve os dados enviados pelo cliente. Mude o cliente para inserir dados do teclado e enviá-los para o servidor. Portanto, o efeito final é que eu digito pelo teclado, o cliente envia para o servidor e o servidor envia o mesmo conteúdo de volta.

Servidor:

// 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;
}

Cliente:

// 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;
}

Insira a descrição da imagem aqui
Insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/mhyasadj/article/details/131181974
Recomendado
Clasificación