一、程序

首先上程序

client端的程序

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define SERVER_PORT 5001 // 本地字节序
#define SERVER_HOST "192.168.0.43"
#define SERVER_BACKLOG 5
#define SERVER_QUIT "q"
#define SERVER_SIZE 32

int test_client()
{
    printf("%s %d \n", __func__, __LINE__);

    int fd = -1;
    struct sockaddr_in addr_in;
    char buf[SERVER_SIZE];
    /*
    AF_INET:IPV4
    SOCK_STREAM:TCP
    */
    // 1、创建socket 得到fd
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        printf("%s %d fd<0\n", __func__, __LINE__);
        return 0;
    }

    /*
    2、连接
    */
    memset(&addr_in, 0, sizeof(addr_in));  // 将变量addr_in置0
    addr_in.sin_port = htons(SERVER_PORT); // 将端口号从本地字节序转换为网络字节序
    addr_in.sin_family = AF_INET;
    addr_in.sin_addr.s_addr = inet_addr(SERVER_HOST); // 将主机从点分形式转换为32字节

    int reccon = connect(fd, (struct sockaddr *)&addr_in, sizeof(addr_in));
    if (reccon < 0)
    {
        printf("%s %d reccon<0\n", __func__, __LINE__);
        return 0;
    }

    // 读写

    while (1)
    {
        memset(buf, 0, SERVER_SIZE);
        char *rec_p = fgets(buf, SERVER_SIZE - 1, stdin);

        int recwrite = write(fd, buf, strlen(buf));
        if (recwrite > 0)
        {
            printf("%s %d buf = %s\n", __func__, __LINE__, buf);
            // int recstr = strcmp(buf, SERVER_QUIT);
            int recstr = strncasecmp(buf, SERVER_QUIT, strlen(SERVER_QUIT));
            printf("%s %d recstr = %d\n", __func__, __LINE__, recstr);
            if (0 == recstr)
            {
                printf("%s %d q======\n", __func__, __LINE__);
                break;
            }
        }

        usleep(100);
    }
    close(fd);
    printf("%s %d xxxxxxxxxxxxxx\n", __func__, __LINE__);
    return 0;
}

server端的程序

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define SERVER_PORT 5001 // 本地字节序
#define SERVER_HOST "192.168.0.43"
#define SERVER_BACKLOG 5
#define SERVER_QUIT "q"
#define SERVER_SIZE 32


int test_server()
{
    printf("%s %d \n", __func__, __LINE__);

    int fd = -1;
    struct sockaddr_in addr_in;
    char buf[SERVER_SIZE];
    /*
    AF_INET:IPV4
    SOCK_STREAM:TCP
    */
    // 1、创建socket 得到fd
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        printf("%s %d fd<0\n", __func__, __LINE__);
        return 0;
    }

    // 2、绑定
    memset(&addr_in, 0, sizeof(addr_in));  // 将变量addr_in置0
    addr_in.sin_port = htons(SERVER_PORT); // 将端口号从本地字节序转换为网络字节序
    addr_in.sin_family = AF_INET;
    // int rec = inet_pton(AF_INET,SERVER_HOST,(void*)&addr_in.sin_addr.s_addr);
    //    if(rec!= 1){
    //     printf("%s %d rec!= 1\n", __func__, __LINE__);
    //         return 0;
    //    }

    addr_in.sin_addr.s_addr = inet_addr(SERVER_HOST); // 将主机从点分形式转换为32字节
    int rec = bind(fd, (struct sockaddr *)&addr_in, sizeof(addr_in));
    if (rec < 0)
    {
        printf("%s %d rec<0\n", __func__, __LINE__);
        return 0;
    }

    // 3、listen() 将主动套接字转换为被动套接字
    int reclisten = listen(fd, SERVER_BACKLOG); // 允许正在进行连接的客户端数目
    if (reclisten < 0)
    {
        printf("%s %d reclisten<0\n", __func__, __LINE__);
        return 0;
    }
    // 4、阻塞等待客户端连接请求
    int newfd = accept(fd, NULL, NULL);
    if (newfd < 0)
    {
        printf("%s %d newfd<0\n", __func__, __LINE__);
        return 0;
    }

    // 5、读写
    // 和最新的newfd进行通信

    while (1)
    {
        memset(buf, 0, SERVER_SIZE);
        int recread = read(newfd, buf, SERVER_SIZE - 1);
        if (recread > 0)
        {
            printf("%s %d buf = %s\n", __func__, __LINE__, buf);
            if (!strncasecmp(buf, SERVER_QUIT, strlen(SERVER_QUIT)))
            {
                printf("%s %d q======\n", __func__, __LINE__);
                break;
            }
        }

        usleep(100);
    }
    close(fd);
    close(newfd);
    printf("%s %d xxxxxxxxxxxxxx\n", __func__, __LINE__);
    return 0;
}

使用方法

1、新建两个程序,分别引用两个函数,先执行server端的程序,再执行client端的程序
2、实现功能:当client和sever连接成功后,从client输入什么都会传输给server端,当输入第一个字母为q时 两端程序都会退出
3、特别注意:需要修改SERVER_HOST 为自己主机地址
4、本程序编写的环境,如果时windows下执行可能需要修改头文件什么的,耐心一点看就好

gcc -v
gcc version 11.3.0 (Ubuntu 11.3.0-1ubuntu1~22.04.1)

cmake -version
cmake version 3.22.1

make -v
GNU Make 4.3

基本顺序

客户端的函数主要就是socket和connect,服务器端主要就是socket,bing,listen和accept函数,write和read函数和操作文件读写时说一样的使用方法。

函数细节

int socket(int domain, int type, int protocol)

使用man socket可以查看函数细节
需要包含的头文件:

#include <sys/types.h>
#include <sys/socket.h>

函数原型:

 int socket(int domain, int type, int protocol);

domain:指定网络通信使用的协议族或地址族,常见的值包括
AF_INET : IPv4 Internet protocols
AF_INET6 : IPv6 Internet protocols

type:指定套接字的类型,常见的值包括
SOCK_STREAM:提供面向连接的、可靠的、双向字节流的服务,使用TCP协议。
SOCK_DGRAM:提供无连接的、不可靠的、具有固定最大长度的消息传递服务,使用UDP协议。
SOCK_RAW:提供原始套接字访问,可以直接发送和接收底层协议的数据包。

protocol:指定具体的传输协议,一般情况下可以设置为0,由操作系统自动选择合适的协议。

返回值:
创建成功时,返回一个新的套接字描述符(socket descriptor),用于后续的网络通信操作。
创建失败时,返回 -1,并设置全局变量 errno 表示具体的错误类型。

这个地方的返回就是相当于文件描述符,要记得记录,后面需要一直用到

重点:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

sockfd:要绑定的套接字描述符。

addr:指向用于绑定的地址结构体的指针,类型为 struct sockaddr*。

addrlen:地址结构体的长度。

这个函数首先要关注const struct sockaddr *addr,将代码跳转过去可以看到sockaddr 的定义如下所示,但是在实际的应用当中我们却不定义struct sockaddr 类型的变量,而是定义struct sockaddr_in的变量,最后进行强制类型转换填入函数bind。

解释原因:

bind() 函数的 addr 参数要求传入一个指向 struct sockaddr 结构体的指针,以指定要绑定的地址信息。但是,由于struct sockaddr是一个通用的结构体类型,只包含地址族和地址数据,并没有特定协议族的详细信息。因此,在实际应用中,我们使用具体的地址结构体类型来填充 struct sockaddr

struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];        /* Address data.  */
  };

例如,在使用IPv4协议(AF_INET)时,我们使用 struct sockaddr_in 结构体来表示IPv4地址信息,它定义如下:

struct sockaddr_in {
    sa_family_t    sin_family;  // 地址族(协议族)
    in_port_t      sin_port;    // 端口号
    struct in_addr sin_addr;    // IPv4地址
    unsigned char  sin_zero[8]; // 填充字段(通常设置为0)
};

在实际应用中,我们通常会创建一个 struct sockaddr_in 类型的变量,填充需要的地址信息,然后将其转换为 struct sockaddr* 类型的指针传递给 bind() 函数。这样可以确保在绑定时使用了特定协议族的地址结构体,以便正确地指定地址和端口

uint16_t htons(uint16_t hostshort);

htons() 是一个函数,用于将一个16位无符号整数(hostshort)从主机字节序(Host Byte Order)转换为网络字节序(Network Byte Order)

在计算机网络中,字节序指定了多字节数据在内存中的存储顺序。主机字节序是指当前主机所使用的字节序,而网络字节序是一种标准的字节序,用于在不同主机之间进行数据交换。

htons() 函数的参数 hostshort 是一个16位无符号整数(uint16_t 类型),表示需要转换的主机字节序的值。

函数的返回值是一个16位无符号整数,表示转换为网络字节序后的值

该函数名中的 “htons” 表示 “host to network short”,其中 “short” 表示16位整数类型。类似的函数还有 htonl() 用于转换32位整数。

例如,假设我们有一个16位整数 value 需要在网络中传输,我们可以使用 htons() 函数将其转换为网络字节序:

uint16_t value = 5001;  // 假设需要转换的主机字节序的端口号
uint16_t networkValue = htons(value);  // 将主机字节序转换为网络字节序

in_addr_t inet_addr(const char *cp);

inet_addr() 是一个函数,用于将一个点分十进制字符串形式的IPv4地址转换为32位无符号整数(in_addr_t 类型)的网络字节序表示。

函数的参数 cp 是一个指向以空字符结尾的字符串的指针,该字符串表示一个IPv4地址。

函数的返回值是一个32位无符号整数,表示转换后的网络字节序的IPv4地址。如果转换失败,则返回 INADDR_NONE。

inet_addr()
函数将点分十进制表示的IPv4地址转换为网络字节序的32位整数表示。IPv4地址由四个用点分隔的十进制数表示(例如:192.168.0.1)。该函数将这种字符串形式的IPv4地址转换为二进制格式,以便在网络中进行传输和处理。

以下是使用 inet_addr() 函数的示例:

const char *ipAddress = "192.168.0.1";
in_addr_t addr = inet_addr(ipAddress);

int listen(int sockfd, int backlog);

listen() 函数用于将一个套接字(socket)设置为被动模式,并开始监听传入的连接请求。

参数说明:

sockfd:要监听的套接字描述符。
backlog:定义了连接请求队列的最大长度。

在网络编程中,当一个套接字处于监听状态时,它可以接收传入的连接请求。listen()
函数将套接字设置为监听模式,并指定传入连接请求的最大队列长度。

调用 listen() 函数时,套接字必须先通过 bind() 函数绑定到一个具体的地址和端口上。然后,使用 listen()
函数来指定该套接字的监听参数。

在监听状态下,当有新的连接请求到达时,操作系统会将其放入连接请求队列中。队列的长度由 backlog
参数指定,超过队列长度的连接请求将被拒绝

需要注意的是,listen() 函数仅适用于面向连接的套接字(如 TCP 套接字)。对于无连接的套接字(如 UDP 套接字),不需要调用 listen() 函数。

int accept(int sockfd, struct sockaddr _addr, socklen_t _addrlen);

accept() 函数用于从已监听的套接字中接受一个传入的连接请求,并创建一个新的套接字来与客户端进行通信。

参数说明:

sockfd:已监听的套接字描述符。

addr:指向用于存储客户端地址信息的结构体的指针,类型为 struct sockaddr*。

addrlen:指向一个整数值,表示 addr 结构体的长度的指针。 flags:可选的标志参数,通常设置为0。

函数在调用时会阻塞,直到有客户端发起连接请求。当一个连接请求到达监听的套接字时,

函数会接受该请求,并创建一个新的套接字来与客户端进行通信。该新创建的套接字用于与客户端之间的数据传输。

函数返回值是一个新创建的套接字描述符,用于与客户端进行通信。
如果出现错误,返回值为 -1,并设置相应的错误码。

在上面的代码当中 第二个和低三个参数都填了NULL,是目前代码当中没有用到连接client的信息,如果需要用到,和前面定义server端 的方式类似

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

connect() 函数用于在套接字上建立与远程服务器的连接。

参数说明:

sockfd:套接字描述符,用于标识要进行连接的套接字。 addr:指向远程服务器地址信息的结构体指针,类型为 struct sockaddr*。

addrlen:远程服务器地址信息结构体的长度。

调用 connect()函数时,客户端套接字尝试与远程服务器建立连接。需要在调用 connect() 函数之前,先使用 bind()函数将套接字绑定到本地地址和端口。

函数返回值:
如果连接建立成功,返回值为0。
如果连接建立失败,返回值为-1,并设置相应的错误码。

其他函数

在代码当中使用到的write,read,close和操作文件时的函数基本一致,这里不赘述。