网络编程day2——基本TCP服务器与客户端搭建流程

本文通过邮政系统的比喻,详细介绍了服务器编程中的关键步骤:创建套接字(socket)、绑定地址(bind)、监听连接(listen)、接受连接(accept)、接收和发送数据(recv/send),以及最后的关闭套接字(close)。展示了TCP和UDP两种服务类型的实现。
摘要由CSDN通过智能技术生成

1.服务器

socket -> bind -> listen -> accept -> recv -> close

  此篇意在用服务器类比于邮政系统,来进行服务器搭建流程函数的理解,若有需求,务必简单浏览上一篇对于此类比的介绍。

网络编程day1——基本概念理解

1.1socket——建立套接字(获取设立邮局法律授权,取得营业凭证)

#include sys/socket.h

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

功能:站在内核的角度打开网络功能,并且可以产生可以使用网络通信的东西

返回值:成功返回套接字sockfd,相当于法律上邮局的授权;失败返回-1;

参数列表:

domain:协议簇 

//相当于所申请的邮局需要指定邮局服务的地理范围

        IPV4:AF_INET

        IPV6:AF_INET6

type:服务器的类型 :

//相当于明确邮局提供的服务种类,是要慢一点但是有追踪保障的(UDP),还是火速但是有丢失风险的(TCP)。

        TCP: SOCK_STREAM

        UDP: SOCK_DGRAM

protocol:额外协议

        不需要额外协议写0

        通过这个类比,我们可以更清晰地理解 socket() 函数的作用:它是创建网络通信端点(套接字)的第一步,为服务器在网络上提供服务打下基础。这个过程需要明确通信的细节,如服务范围、服务种类和具体协议,以确保后续通信的顺利进行。

1.2 bind——绑定套接字(取得授权后,施工建立邮局)

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

功能:

        给套接字绑定好IP和端口等信息

        //相当于选择好邮局具体的地址,与开设具体的服务窗口。以便于未来有顾客(客户端)有需求时可以找到这个邮局及其具体的窗口。

参数列表:

        sockfd:创建好的套接字//施工时拿出你的授权

        addr:一个指向sockaddr结构的指针,该结构包含了套接字需要绑定的地址信息。

        addrlen:结构体大小

其中addr常用新版结构体:

struct sockaddr_in {
    sa_family_t sin_family;   // 地址族,通常是 AF_INET
    in_port_t   sin_port;     // 网络字节序的端口号
    struct in_addr sin_addr;  // 网络字节序的IP地址
    char        sin_zero[8];  // 填充,保证sockaddr结构的大小一致
};

  • sin_family: 地址族,对于 IPv4 地址,这个字段通常设置为 AF_INET
  • sin_port: 端口号,以网络字节序表示,即大端序。通常使用 htons() 函数进行转换。
  • sin_addr: 结构体,包含一个 IPv4 地址,也是以网络字节序表示。可以使用 inet_addr() 或 inet_pton() 函数进行设置。
    • sin_addr.s_addr: IPv4 地址的无符号整数表示,以网络字节序存储。
  • sin_zero: 一个填充数组,用于确保 sockaddr_in 结构体的大小与更通用的 sockaddr 结构体一致。在大多数现代系统中,这个填充是不必要的,因为 sockaddr_in 结构体的大小已经被调整为与 sockaddr 一致。

1.3 listen——监听套接字(监听邮件收发服务请求)

int listen(int sockfd, int backlog);

功能:

        监听套接字是否有连接,确立监听队列的大小

返回值:

        成功返回0,失败返回-1

参数列表:

        sockfd:已经绑定好信息的套接字

        backlog:队列大小 2*backlog+1

1.4accept——接收套接字(接收并处理用户请求)

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

  • sockfd:这是监听套接字的文件描述符。服务器使用这个文件描述符来等待客户端的连接请求。

  • addr:这是一个可选参数,指向 sockaddr 结构的指针,该结构用于接收连接客户端的地址信息。如果不需要客户端地址,可以设置为 NULL

  • addrlen:这是一个指向 socklen_t 变量的指针,该变量在调用 accept 之前应该被初始化为 addr 所指向结构的长度。accept 函数可能会修改这个长度,以反映实际接收到的地址长度。如果不需要客户端地址,这个参数也可以设为 NULL

1.5recv——处理请求

(这个和邮局点不一样,这里相当于邮局会对顾客的邮件进行处理,也许是读取,也许是完成你的需求,相当于升级的更加多功能的邮局吧)

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  • sockfd: 已连接的套接字的文件描述符。
  • buf: 指向一个缓冲区的指针,用于存储接收到的数据。
  • len: 缓冲区的长度,即可以接收的数据的最大字节数。
  • flags: 用于指定接收操作的特殊选项。常用的标志有:
    • 0: 正常接收数据。
    • MSG_PEEK: 窥视接收队列中的数据,但不从队列中移除。
    • MSG_WAITALL: 等待直到接收到 len 个字节的数据。
  • 成功时,recv 函数返回接收到的字节数,它可以小于 len 指定的缓冲区大小。
  • 如果对方关闭了连接,并且没有更多数据可接收,返回0。
  • 出错时,返回 -1,并设置全局变量 errno 以指示错误类型。

1.6close——关闭(相当于一次需求的处理完成)

int close(int fd);

  • fd: 要关闭的文件描述符(或套接字描述符)。

调用 close 函数时,如果出现错误,它会返回 -1。常见的错误原因包括:

  • EBADF:提供的文件描述符 fd 无效或未打开。
  • EINTR:关闭操作被中断,通常由于接收到信号。

2.客户端

socket -> bind(可选) -> connect -> send/recv -> close

这里和服务器的搭建相比基本相似,可参考服务器来理解。

3.完整示例

3.1服务器代码

        下面是一个使用TCP协议的简单C语言服务器端程序的示例,它遵循了 socket -> bind -> listen -> accept -> send/recv -> close 的流程:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080  // 服务器监听的端口号
#define BACKLOG 5  // 允许的最大等待连接数

int main() {
    int server_fd, client_fd;  // 服务器和客户端的套接字文件描述符
    struct sockaddr_in server_addr, client_addr;  // 服务器和客户端的地址结构
    socklen_t client_len = sizeof(client_addr);  // 客户端地址结构的大小
    char buffer[1024] = {0};  // 数据接收缓冲区
    int ret;

    // 创建套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("Cannot create socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址参数
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;  // 使用IPv4地址
    server_addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用接口
    server_addr.sin_port = htons(PORT);  // 端口号

    // 将套接字绑定到服务器地址
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // 开始监听传入连接
    if (listen(server_fd, BACKLOG) < 0) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    // 无限循环,接受连接
    while (1) {
        printf("Waiting for incoming connections...\n");

        // 接受一个连接(会阻塞,直到一个客户端连接到服务器)
        client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
        if (client_fd < 0) {
            perror("Accept failed");
            exit(EXIT_FAILURE);
        }

        // 打印客户端的IP地址和端口号
        printf("Connection accepted from %s:%d\n",
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        // 通信示例:从客户端接收数据,然后发送数据
        while ((ret = recv(client_fd, buffer, sizeof(buffer) - 1, 0)) > 0) {
            // 发送收到的数据
            send(client_fd, buffer, ret, 0);
        }

        if (ret < 0) {
            perror("Recv failed");
        }

        // 关闭客户端套接字
        close(client_fd);
    }

    // 关闭服务器套接字
    close(server_fd);

    return 0;
}

3.2客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080  // 服务器监听的端口号
#define SERVER "127.0.0.1"  // 服务器的IP地址

int main() {
    int sockfd;  // 套接字文件描述符
    struct sockaddr_in serv_addr;  // 服务器的地址结构
    char buffer[1024] = {0};  // 数据接收缓冲区

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Cannot create socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址参数
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;  // 使用IPv4地址
    serv_addr.sin_port = htons(PORT);  // 端口号
    serv_addr.sin_addr.s_addr = inet_addr(SERVER);  // 服务器IP地址

    // 连接到服务器
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connect failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Connected to the server.\n");

    // 发送消息到服务器
    const char *message = "Hello, Server!";
    int message_len = strlen(message) + 1;
    int ret = send(sockfd, message, message_len, 0);
    if (ret < 0) {
        perror("Send failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    printf("Message sent.\n");

    // 接收服务器的回显响应
    ret = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (ret < 0) {
        perror("Recv failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("The server closed the connection.\n");
    } else {
        // 添加字符串结束符,并打印接收到的数据
        buffer[ret] = '\0';
        printf("Received: %s\n", buffer);
    }

    // 关闭套接字
    close(sockfd);

    return 0;
}

4.后续

        进行基本UDP服务器与客户端的搭建流程示例。

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值