[Linux] programación de sockets (introducción de sockets, orden de bytes, dirección de socket, función de conversión de dirección IP, función de socket, implementación de comunicación TCP)

color naranja

1. Introducción a los enchufes

El llamado socket es una abstracción de puntos finales para la comunicación bidireccional entre procesos de aplicaciones en diferentes hosts de la red.

Un socket es un extremo de la comunicación del proceso en la red y proporciona un mecanismo para que los procesos de la capa de aplicación intercambien datos utilizando protocolos de red. En términos de posición, el socket está conectado al proceso de aplicación y la pila de protocolos de red está conectada hacia abajo. Es la interfaz para que el programa de aplicación se comunique a través del proceso de protocolo de red y la interfaz para que el programa de aplicación interactúe con el protocolo de red. .

Es una API para la comunicación en un entorno de red. Cada socket en uso tiene un proceso conectado. Durante la comunicación, una de las aplicaciones de red escribe una información para ser transmitida en el socket del host donde está ubicada, y el socket envía la información al socket de otro host a través del medio de transmisión conectado a la interfaz de red. tarjeta (NIC), para que la otra parte pueda recibir esta información. socket是由IP地址和端口结合的, proporciona un mecanismo para que los procesos de la capa de aplicación transmitan paquetes de datos.

Socket originalmente significa "socket" y en el entorno Linux es un tipo de archivo especial que se utiliza para representar la comunicación de red entre procesos. Es esencialmente un pseudoarchivo formado por el núcleo con la ayuda de un búfer. Configúrelo como un archivo para facilitar nuestra operación, podemos operar a través del descriptor de archivo. Al igual que con el tipo de tubería, el propósito de encapsular puntos en archivos en el sistema Linux es unificar la interfaz para que los sockets de lectura y escritura y los archivos de lectura y escritura funcionen de la misma manera. La diferencia es que las tuberías se utilizan para la comunicación local entre procesos, mientras que los sockets se utilizan principalmente para la transferencia de datos entre procesos de red.

Socket es una comunicación full-duplex, es decir, los datos se pueden leer y emitir al mismo tiempo.
Insertar descripción de la imagen aquí

Dirección IP (dirección lógica): identifica de forma única un host en la red.
Número de puerto: identifica de forma única un proceso en un host.
IP+número de puerto: identifica de forma única un proceso en el entorno de red.

-Lado del servidor: acepta conexiones pasivamente y generalmente no inicia conexiones activamente.

-Cliente: inicia activamente una conexión con el servidor.

2. Endianidad

Introducción

Ahora el acumulador de la CPU puede cargar (al menos) 4 bytes a la vez (máquina de 32 bits), es decir, un número entero. Entonces, el orden en que se organizan estos 4 bytes en la memoria afectará el valor entero cargado por el acumulador, lo que es un problema de orden de bytes. En diferentes arquitecturas informáticas, los mecanismos de almacenamiento de bytes, palabras, etc. son diferentes, lo que plantea una cuestión muy importante en el campo de la comunicación informática, es decir, en qué orden deben transmitirse las unidades de información intercambiadas por las partes comunicantes. Si no se alcanzan las reglas acordadas, las partes que se comunican no podrán realizar la codificación/decodificación correcta, lo que provocará una falla en la comunicación.

El orden de bytes, como su nombre lo indica, es el orden en que los datos de un tipo mayor que un byte se almacenan en la memoria (por supuesto, no es necesario hablar sobre el orden de los datos de un byte).

El orden de los bytes se divide en Big-Endian y Little-Endian. 大端字节序是指一个整数的高位字节存储在内存的低地址位置,低位字节存储在内存的高地址位置。小端字节序则是指一个整数的高位字节存储在内存高地址处,而低位字节则存储在内存的低地址处.

Insertar descripción de la imagen aquí

Obviamente, para un número, el número más a la izquierda es el bit alto y el número más a la derecha es el bit bajo.

A continuación, escriba un programa para detectar el orden de bytes del host actual:
si no sabe acerca de las uniones, puede consultar este artículo: lenguaje C | Explicación detallada de las uniones

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

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

Insertar descripción de la imagen aquí

Función de conversión de orden de bytes

Cuando los datos formateados se pasan directamente entre dos hosts utilizando un orden de bytes diferente, el extremo receptor inevitablemente los interpretará incorrectamente. La forma de resolver el problema es: el extremo emisor siempre convierte los datos que se enviarán en datos de orden de bytes big-endian antes de enviarlos, y el extremo receptor sabe que los datos enviados por la otra parte siempre están en orden de bytes big-endian. , por lo que el extremo receptor puede El orden de bytes adoptado por sí mismo determina si se deben convertir los datos recibidos (la máquina little endian convierte, la máquina big endian no convierte).

网络字节顺序Es un formato de representación de datos especificado en TCPIP. No tiene nada que ver con el tipo de CPU específico, el sistema operativo, etc., lo que garantiza que los datos se puedan interpretar correctamente cuando se transmiten entre diferentes hosts. El orden de bytes de la red adopta una clasificación big-endian. .

BSD Socket proporciona una interfaz de conversión encapsulada para comodidad del programador. Incluyendo las funciones de conversión del orden de bytes del host al orden de bytes de la red: htons, htonl; las funciones de conversión del orden de bytes de la red al orden de bytes del 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;
}

Insertar descripción de la imagen aquí

Pregunta: ¿Qué sucede cuando el número impreso es 1677830336?
Respuesta: // 192 168 1 100
  // 11000000 10101000 000000001 01101000
//Esta máquina es little endian, por lo que 192 está en el bit bajo y 100 está en el bit alto, por lo que el número es // 01101000 00000001 10101000
1100 0000 = 1677830336

3. dirección del socket

En la interfaz de programación de la red de sockets, la dirección del socket es la estructura sockaddr, que se define de la siguiente manera:

#include <bits/socket.h>

struct sockaddr{
    
                                    //已经被废弃掉

        sa_family_t sa_family;
        char sa_data[14];
};

typedef unsigned short int sa_family_t;

Miembros:
    el miembro sa_family es una variable de tipo de familia de direcciones (sa_family_t). Los tipos de familia de direcciones suelen corresponder a tipos de protocolo. Las familias de protocolos comunes y las familias de direcciones correspondientes son las siguientes:

familia de protocolos dirección familia describir
PF_UNIX OF_UNIX Conjunto de protocolos de dominio local UNIX
PF_INET OF_INET Conjunto de protocolos TCP/IPv4
PF_INET6 AF_INET6 Conjunto de protocolos TCP/IPv6

La familia de protocolos PF_* y la familia de direcciones AF_* están definidas en el archivo de encabezado bits/socket.h. Tienen el mismo valor y se pueden usar de forma mixta (de todos modos, ambas son definiciones de macro. Las definiciones de macro son reemplazos de macros en la etapa de preprocesamiento , por lo que son adecuados para uso mixto (no habrá ningún impacto en la compilación y ejecución).

El miembro sa_data se utiliza para almacenar el valor de la dirección del socket. Sin embargo, los valores de dirección de diferentes familias de protocolos tienen diferentes significados y longitudes
Insertar descripción de la imagen aquí
. Se puede ver que 14 bytes solo pueden contener direcciones IPv4, pero no pueden contener direcciones IPv6. Por lo tanto, esta representación de estructura ha sido abandonada. Linux define la siguiente nueva estructura de dirección de socket universal. Esta estructura no solo proporciona suficiente espacio para almacenar valores de dirección, sino que también está alineada con la memoria [alineación de memoria] Puede acelerar el acceso a la CPU]

Esta estructura está definida en:/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;

Dirección de socket privado

Muchas funciones de programación de red nacieron antes que el protocolo IPv4 (use un protocolo personalizado y ambas partes acuerdan una regla). En ese momento, todas usaban la estructura socketaddr golpeada. *Para compatibilidad futura, socketaddr ahora ha degenerado en ( void ) Su función es pasar una dirección a la función. Si la función es sockaddr_in o sockaddr_in6 está determinada por la familia de direcciones, y luego la función se ve obligada a convertir el tipo al tipo de dirección requerido internamente .

Lo principal que hay que recordar es que la segunda struct sockaddr_in
Insertar descripción de la imagen aquí
familia de protocolos de dominio local UNIX de la siguiente figura utiliza la siguiente estructura de dirección de socket dedicada:

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

El conjunto de protocolos TCP/IP tiene dos estructuras de direcciones de socket dedicadas, sockaddr_in y sockaddr_in6, que se utilizan 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 las variables de tipo de dirección de socket especial (y sockaddr_storage) deben convertirse al tipo de dirección de socket general sockaddr (solo forzar la conversión) cuando realmente se usan, porque el tipo de parámetro de dirección utilizado por todas las interfaces de programación de socket es sockaddr .

4. Función de conversión de dirección IP

La gente está acostumbrada a usar cadenas legibles para representar direcciones IP, como cadenas decimales con puntos para representar direcciones IPv4 y cadenas hexadecimales para representar direcciones IPv6, pero en programación necesitamos convertirlas primero en números enteros (binario). Por el contrario, para iniciar sesión, necesitamos convertir la dirección IP representada por un número entero en una cadena legible.

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

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

Insertar descripción de la imagen aquí

5. Función de enchufe

#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. Implementación de comunicación TCP (servidor y cliente)

Tenga en cuenta que este programa está escrito en función de los pasos del servidor que recibe información durante la comunicación TCP. Puede consultar este artículo primero .

Terminal de servicio

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

Pregunta: Supongamos que el servidor llama a leer una vez para terminar de leer el contenido del descriptor de archivo del cliente. En este momento, el cliente no escribe datos en el descriptor, pero no se desconecta, luego el servidor llama a leer por segunda vez. ¿Será devuelto?
Respuesta: Características de las tuberías de lectura, cuando no hay datos en la tubería: 1. El extremo de escritura está completamente cerrado y la lectura devuelve 0 (equivalente a leer el final del archivo) 2. El extremo de escritura no está completamente cerrado. y leer bloques y esperas. Consulte mi artículo [Linux] Las características de lectura y escritura de las tuberías y la configuración de las tuberías como sin bloqueo

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

La dirección IP en la línea 21 del cliente debe ser la dirección IP de su propio host. La dirección IP de mi host es 192.168.177.146

Compile y ejecute los dos archivos por separado. Los resultados son los siguientes:
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Puede ver que en el programa de ejecución del servidor, la IP del cliente se imprime como 192.168.177.146 y el puerto se asigna aleatoriamente a 35302.

Nota: Inicie primero el servidor y luego el cliente.

Tarea: cambie el servidor a un servidor de eco, es decir, el servidor devuelve los datos enviados por el cliente. Cambie el cliente para ingresar datos desde el teclado y enviarlos al servidor. Entonces, el efecto final es que ingreso desde el teclado, el cliente lo envía al servidor y el servidor devuelve el mismo contenido.

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

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

Supongo que te gusta

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