第一章 理解网络编程和socket

![INFO] 网络编程就是编写程序使用两台连网的计算机相互交换数据.

这就是整个网络编程的全部内容.我觉得不需要去详细的理解TCP/IP的细节.

下面我按我的理解这一章的内容.

如果我们想到在电脑上创建一个server端的软件,用来接收其它电脑上另一个程序通过网络发来的信息,应该如何做呢? 应该按下面的流程来做

---
title: server socket 创建流程
---
flowchart TD
    step_1[1.创建socket,API:socket]
    step_2[2.绑定端口与ip: bind]
    step_3[3.开启监听 listen]
    step_4[4.接受产生的socket: accept ]
    step_1 --> step_2 --> step_3 --> step_4;

第一步创建 socket(插座)

alt text

如果我们家里需要网络通信,首先要有一个网络面板(网络插座)用来连接网络,然后才能通信. 同样我们也需要先创建一个socket,这个socket的作用就是用于后面的网络连接.

int socket(int domain,int type,int protocal)

具体参数的含义与理解见一下章节

第二步 bind函数

通过第一步我们创建了一个socket(插座),那么应该把它放在哪里呢? 其实就是根据哪些信息可以找到这个socket.

  1. 信息首先要能发送给电脑,也就是需要知道电脑的IP,一个电脑可以有多个IP.

多网口

比如这个电脑就有两个网口,(单网口也可以有多IP)

  1. 可以想像到一个电脑上能进行网络通信的软件有多个,那么就有很多个socket.

2

3

4

电脑为了和外部通信,创建了端口这个概念.

你可以把端口想像成电脑一个一个孔洞,每一个数据通过这个孔洞发流入进电脑,如何你的socket"安装"在这个端口上,那么就可以接收到这个数据了.

然后这个socket和还需要和某一端口绑定在一起.

#include <sys/socket.h>
int bind(int sockfd,struct sockaddr_in * myaddr,socklen_t addrlen)

第三步: 激活监听,listen

现在我们有了一个socket,并把它放到了合适的位置(bind),接下就能通信了吗? 不能,还需要启用,激活它,就像家里弄了网络面板,网线,还需要网络公司给我们开通网络权限.

int listen(int sockfd,int backlog)

listen的其实是在socket创建一个监听队列,队列大小为backlog,每一个client请求连接后,就会加入到队列里,等待处理

第四步: accept,处理连接请求

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

上一步,可以接收连接,然后加入到队列里,但是还需要对这些队列里的内容进行处理.

accept的作用,从队列取出一个连接数据,然后创建一个socket.你可能会问,为什么又创建了一个socket,原来不是有一个吗?

首先我们要明确的知道一个概念,两个socket一在一起才能进行通信,或者说通信的两个电脑之间各有一个socket,不允许一socket对应多个socket.

第一个socket,我们的成为socket,我们称为server socket,它在同一时刻与一个client socket进行连接,然后接收到必要的信息之后.根据这些信息产生一个socket A.这个新的socket A已后只与这个client socket进行通信.

这个过程我们称为accept

server端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


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



const int port = 8989; //端口

char message[] ="Hello World!";

void error_handle(char *message) {
    printf("error: %s\n",message);
}

int main(int argc, char *argv[])
{

    //创建一个server 端的socket
    // man 7 socket, man 2 socket
    int serv_sock = socket(AF_INET,SOCK_STREAM,0);
    if(serv_sock == -1) {
        error_handle("socket() failed!");
        return 1;
    }

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;

    //清空
    // man 7 ip
    memset(&serv_addr,0,sizeof(serv_addr));
    //赋值
    serv_addr.sin_family = AF_INET;
    //host to network short
    //short 16位 2字节
    serv_addr.sin_port = htons(port);
    //host to network long 4字节
    // INADDR_ANY man 7 ip
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 第二步: bind 端口与ip
    //通用套接字地址结构体sockaddr
    if( bind(serv_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr) ) == -1) {
        error_handle("bind() failed!");
        return 1;
    }

    //第三步: 激活监听
    if( listen(serv_sock,5) == -1) {
        error_handle("listen() failed!");
        return 1;
    }

    //第四步: 等待连接
    socklen_t client_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock,(struct sockaddr *) &clnt_addr,&client_addr_size);
    if(clnt_sock == -1)
    {
        error_handle("accept() error!");
        return 1;
    }
    write(clnt_sock,message,sizeof(message));
    close(clnt_sock);
    close(serv_sock);
    return 0;
    return EXIT_SUCCESS;
}

编译

gcc -g -o server 1-hello_world_server.c

测试

netcat -v 127.0.0.1 8989

结果

➞  netcat -v 127.0.0.1 8989                                                                                                                                  
localhost [127.0.0.1] 8989 (sunwebadmins) open
Hello World!%

client端代码

client实现比较简单

  1. 创建socket
  2. 调用connect,与server socket进行连接
  3. 发送数据
  4. 接收返回的数据
  5. 结束client socket
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8989

void error_handle(char *message)
{
    printf("error: %s\n", message);
}

int main()
{
    // 创建一个client 端的socket
    //  man 7 socket, man 2 socket
    int clnt_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (clnt_sock == -1)
    {
        error_handle("socket() failed!");
        return 1;
    }

    // 连接到服务器
    struct sockaddr_in clnt_addr;
    memset(&clnt_addr, 0, sizeof(clnt_addr));
    clnt_addr.sin_family = AF_INET;
    //inet_addr man 7 ip
    clnt_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
    clnt_addr.sin_port = htons(SERVER_PORT);
    if (connect(clnt_sock, (struct sockaddr *)&clnt_addr, sizeof(clnt_addr)) == -1)
    {
        error_handle("connect() failed!");
        return 1;
    }
    // 发送数据
    // send(clnt_sock, message, sizeof(message), 0);
    // 接收数据
    char buffer[1024];
    int str_len = read(clnt_sock, buffer, sizeof(buffer) - 1);
    if (str_len == -1)
    {
        error_handle("read() failed!");
        return 1;
    }
    else
    {
        buffer[str_len] = '\0';
        printf("Message from server: %s\n", buffer);
    }
    close(clnt_sock);
    return 0;
}

运行结果

➞  ./1-hello_world_client.out
Message from server: Hello World!

inet_addr 函数

inet_addr 是一个用于将 IPv4 地址从点分十进制字符串表示转换为网络字节序的 32 位二进制值的函数。它在 Linux 和其他类 Unix 系统的网络编程中广泛使用。

man 3 inet_addr

函数原型:

#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);

参数:

  • cp:以点分十进制格式表示的 IPv4 地址字符串,例如 "192.168.0.1"。

返回值:

  • 成功时,返回转换后的 32 位网络字节序的 IPv4 地址。
  • 失败时,返回 INADDR_NONE(通常为 -1)。

inet_addr 函数将点分十进制格式的 IPv4 地址字符串转换为网络字节序的 32 位二进制值。它执行以下步骤:

  1. 将点分十进制的 IPv4 地址字符串按照点号分隔符分割为四个部分,每个部分表示一个字节。
  2. 将每个部分转换为对应的整数值(0-255)。
  3. 将四个字节组合成一个 32 位的二进制值,并按照网络字节序排列。

例如,对于 IP 地址 "192.168.0.1",inet_addr 函数将返回一个 32 位的整数值 0x0100A8C0(网络字节序)。

使用示例:

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

int main() {
    const char *ip_str = "192.168.0.1";
    in_addr_t ip_addr = inet_addr(ip_str);
    
    if (ip_addr == INADDR_NONE) {
        printf("无效的 IP 地址\n");
    } else {
        printf("IP 地址的网络字节序表示: 0x%08X\n", ip_addr);
    }
    
    return 0;
}

输出:

IP 地址的网络字节序表示: 0x0100A8C0

需要注意的是,inet_addr 函数只支持 IPv4 地址的转换,对于 IPv6 地址,需要使用 inet_pton 函数进行转换。

另外,由于 inet_addr 函数不能处理格式错误的 IP 地址字符串,因此在实际使用中,通常推荐使用更加健壮和通用的 inet_pton 函数来进行 IP 地址的转换。

linux的文件操作

每一个程序都有多个文件描述符,从 0 到 1023,这些0,1,2文件描述符被称为标准输入、标准输出、标准错误。

这个文件描述符是由linux系统维护的.

int open(const char *pathname, int flags);
int close(int fd);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);