基于数据报套接字的服务器回射程序设计:分别以循环服务器和并发服务器实现

一、基于数据报套接字的服务器回射程序设计

基于数据报套接字的服务器回射程序设计内容:

编写一服务器程序和客户程序,如图1,要求客户每输入一行数据,服务器接收后加上echo:回送给客户程序,当客户输入“q”后退出。

要求分别以循环服务器和并发服务器实现。

图1 回射程序

无连接的数据报套接字回射客户端 设计思路

请添加图片描述

循环服务器 设计思路

请添加图片描述

并发服务器 设计思路

为了解决接收客户端的多次交互的请求的问题,在客户端发出第一个“ASK Port”请求后服务器为每个客户创建一个的新的套接字,在其上 bind 一个临时端口,创建一个子进程使用该套接字发送对该客户的所有应答。要求客户端查看服务器第一个应答的中的源端口号,并把本请求的后续数据报发送到该端口。

二、代码实现

客户端代码(循环服务器对应的)

#define NETWORK1_2_1_A_COMM_H
#define NETWORK1_2_1_A_COMM_H
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <inaddr.h>
#include <time.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define MAXLINE 4096     // 接收缓冲区长度
#define LISTENQ 1024     // 监听队列长度
#define SEVER_PORT 13442 // 端口 
/*套接字初始化*/ 
int StartUp()
{
    WORD w_version = MAKEWORD(2, 2);
    WSADATA wsa_data;
    int res = WSAStartup(w_version, &wsa_data);
    if (res != 0)
    {
    	printf("WSAStartup Error:%d\n",WSAGetLastError());
        return -1;
    }//版本号错误 
    if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2)
    {
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }

    return 0; //成功返回0 
}
/*释放资源*/ 
int cleanUp()
{
    int res = WSACleanup();
    if (res == SOCKET_ERROR)
    {
    	printf("WSACleanup Error:%d",WSAGetLastError()); 
        return -1;
    }
    return 0;
}
/*断开连接,释放资源*/ 
int closeConn(SOCKET sock_conn)
{
    int res = closesocket(sock_conn); //关闭连接
    if (res == SOCKET_ERROR)
    {
    	printf("关闭连接失败%d\n",WSAGetLastError());
        return -1;
    }
    res = cleanUp();
    return res;
}
/*连接服务器并绑定*/
SOCKET udpClientInit(char *server_ip, u_short port, sockaddr_in &server_addr, bool flag)
{
    int res = -1;
    SOCKET sock_conn;
    // 绑定地址、端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.S_un.S_addr = inet_addr(server_ip);
    //创建套接字
    sock_conn = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_conn == INVALID_SOCKET)
    {
    	printf("连接初始化失败%d\n",WSAGetLastError());
        cleanUp();
        return -1;
    }

    if (flag)
    {
        // 请求连接服务器
        res = connect(sock_conn, (struct sockaddr *)&server_addr, sizeof(server_addr));
        if (res == SOCKET_ERROR)
        {
            // 连接服务器失败
            printf("连接失败:%d\n",WSAGetLastError()); 
            closeConn(sock_conn);
            return -1;
        }
    }

    return sock_conn; //连接成功
}
int getline(char s[],int lim)
{
    int c,i;
    i=0;
    while((c=getchar())!=EOF&&c!='\n'&&i<lim-1)
        s[i++]=c;
    s[i]='\0';
    return i;
}
/*回射客户端发送接收程序*/
int udpEchoClient(SOCKET sock_conn, SOCKADDR *server_addr, int addr_len)
{
    int res = -1;
    char send_data[MAXLINE], recv_data[MAXLINE];
    memset(send_data, 0, MAXLINE);
    memset(recv_data, 0, MAXLINE);
	 
    while (getline(send_data, MAXLINE))//输入缓冲区接收一行数据 
    {
        // 结束
        if (*send_data == 'Q' || *send_data == 'q')
        {
            puts("退出"); 
            return 0; // 正常退出
        }
        // 发送数据
        res = sendto(sock_conn, send_data, (int)strlen(send_data), 0, (SOCKADDR *)server_addr, addr_len);
        if (res == SOCKET_ERROR)
        {
        	printf("发送失败:%d\n",WSAGetLastError());
            return -1;
        }
		printf("发送数据:%s\n",send_data); 
        // 接收数据
        res = recvfrom(sock_conn, recv_data, MAXLINE, 0, NULL, NULL);
        if (res > 0)
        {
        	printf("接收到的数据:%s\n",recv_data);
        }
        else
        {
            printf("接收失败:%d\n",WSAGetLastError());
            break;
        }

        // 清空缓存
        memset(recv_data, 0, MAXLINE);
        memset(send_data, 0, MAXLINE);
    }
    return res;
}
int main()
{
    int res = -1;
    SOCKET sock_conn;
    struct sockaddr_in server_addr;
    char server_ip[] = "10.236.10.200";
    res = StartUp();//初始化动态连接库 
    if (res == -1) return -1;

    sock_conn = udpClientInit(server_ip, SEVER_PORT, server_addr, true);//连接初始化 
    if (sock_conn == -1) return -1;
	puts("连接服务器成功");
    res = udpEchoClient(sock_conn, (SOCKADDR *)&server_addr, sizeof(sockaddr_in));
    if (res == -1)puts("Error");
    closeConn(sock_conn); 
    return res;
}

循环服务器端代码

//回射程序,服务器段端,循环服务器 
#define NETWORK1_2_1_A_COMM_H
#define NETWORK1_2_1_A_COMM_H
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <inaddr.h>
#include <time.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define MAXLINE 4096     // 接收缓冲区长度
#define LISTENQ 1024     // 监听队列长度
#define SEVER_PORT 13442 // 端口 
/*套接字初始化*/ 
int StartUp()
{
    WORD w_version = MAKEWORD(2, 2);
    WSADATA wsa_data;
    int res = WSAStartup(w_version, &wsa_data);
    if (res != 0)
    {
    	printf("WSAStartup Error:%d\n",WSAGetLastError());
        return -1;
    }//版本号错误 
    if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2)
    {
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }

    return 0; //成功返回0 
}
/*释放资源*/ 
int cleanUp()
{
    int res = WSACleanup();
    if (res == SOCKET_ERROR)
    {
    	printf("WSACleanup Error:%d",WSAGetLastError()); 
        return -1;
    }
    return 0;
}
/*断开连接,释放资源*/ 
int closeConn(SOCKET sock_conn)
{
    int res = closesocket(sock_conn); //关闭连接
    if (res == SOCKET_ERROR)
    {
    	printf("关闭连接失败%d\n",WSAGetLastError());
        return -1;
    }
    res = cleanUp();
    return res;
}
/*服务器初始化*/ 
SOCKET udpServerInit(int port)
{
    int res = -1;
    SOCKET sock_listen;
    const int on = 1;

    // 绑定地址、端口
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    //创建监听套接字
    sock_listen = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_listen == INVALID_SOCKET)
    {
    	
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
       	cleanUp();
        return -1;
    }

    //绑定服务器地址
    res = bind(sock_listen, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (res == SOCKET_ERROR)
    {
    	printf("绑定失败:%d\n",WSAGetLastError());
        closeConn(sock_listen);
        return -1;
    }

    return sock_listen;
}
/*循环服务器接收程序*/ 
int udpEchoServer(SOCKET sock_conn)
{
    // 此处利用指针的移动 实现添加Echo
    int res = -1;
    char recv_data[MAXLINE];
    struct sockaddr_in client_addr;
    int addr_len = sizeof(client_addr);

    do
    {
        // 清空缓存
        memset(recv_data, 0, MAXLINE);

        // 循环接收数据
        res = recvfrom(sock_conn, recv_data , MAXLINE , 0, (SOCKADDR *)&client_addr, &addr_len);
        if (res > 0)
        {
			printf("从客户端接受到数据:%s:\n",recv_data);
            // 回射接收的数据
            res = sendto(sock_conn, recv_data, res , 0, (SOCKADDR *)&client_addr, addr_len);
            if (res == SOCKET_ERROR)
            {
            	printf("sendto error:%d\n",WSAGetLastError()); 
                res = -1;
            }
            else
            {
            	printf("服务器回射数据:%s\n",recv_data);
            }
        }
        else
        {
            printf("recvfrom error:%d\n",WSAGetLastError()); 
            res = -1;
        }
    } while (res > 0);

    return res;
}
int main(int argc, char *argv[])
{
    int res = -1;
    SOCKET sock_listen;
    res = StartUp();//初始化 
    if (res == -1) return -1;//初始化失败 
    sock_listen = udpServerInit(SEVER_PORT);//创建监听套接字 
    if (sock_listen == -1) return -1;
	puts("udpServerInit success") ;
	//循环服务器 
    while (1)
    {
        // 回射 如果出错,继续其他客户端请求
        res = udpEchoServer(sock_listen);
        if (res == -1) puts("cilent connectione error,continue to connect to the next client"); 
    }
    closeConn(sock_listen); // 关闭监听服务
    return 0;
}

并发服务器端代码

#define NETWORK1_2_1_A_COMM_H
#define NETWORK1_2_1_A_COMM_H
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#include <inaddr.h>
#include <time.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define MAXLINE 4096     // 接收缓冲区长度
#define LISTENQ 1024     // 监听队列长度
#define SEVER_PORT 13442 // 端口 
/*套接字初始化*/ 
int StartUp()
{
    WORD w_version = MAKEWORD(2, 2);
    WSADATA wsa_data;
    int res = WSAStartup(w_version, &wsa_data);
    if (res != 0)
    {
    	printf("WSAStartup Error:%d\n",WSAGetLastError());
        return -1;
    }//版本号错误 
    if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2)
    {
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }

    return 0; //成功返回0 
}
/*释放资源*/ 
int cleanUp()
{
    int res = WSACleanup();
    if (res == SOCKET_ERROR)
    {
    	printf("WSACleanup Error:%d",WSAGetLastError()); 
        return -1;
    }
    return 0;
}
/*断开连接,释放资源*/ 
int closeConn(SOCKET sock_conn)
{
    int res = closesocket(sock_conn); //关闭连接
    if (res == SOCKET_ERROR)
    {
    	printf("关闭连接失败%d\n",WSAGetLastError());
        return -1;
    }
    res = cleanUp();
    return res;
}
/*服务器初始化*/ 
SOCKET udpServerInit(int port)
{
    int res = -1;
    SOCKET sock_listen;
    const int on = 1;

    // 绑定地址、端口
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    //创建监听套接字
    sock_listen = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_listen == INVALID_SOCKET)
    {
    	
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
       	cleanUp();
        return -1;
    }

    //绑定服务器地址
    res = bind(sock_listen, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (res == SOCKET_ERROR)
    {
    	printf("绑定失败:%d\n",WSAGetLastError());
        closeConn(sock_listen);
        return -1;
    }

    return sock_listen;
}
/*子线程,线程函数*/
void* ThreadUdpEchoServer(LPVOID pParam){
    // 此处利用指针的移动 实现添加Echo
    int sock_res;
    char recv_data[MAXLINE];
    struct sockaddr_in client_addr;
    int addr_len = sizeof(client_addr);
    // 将输入参数转换成连接套接字
    SOCKET sock_conn = *((SOCKET *) pParam);
    do {
        memset(recv_data, 0, MAXLINE);
        // 接收数据
        sock_res = recvfrom(sock_conn, recv_data, MAXLINE, 0, (SOCKADDR *) &client_addr, &addr_len);
        if (sock_res > 0) {
			printf("从客户端接收到的数据:%s\n",recv_data);
            // 回射接收的数据
            sock_res = sendto(sock_conn, recv_data, sock_res, 0, (SOCKADDR *) &client_addr, addr_len);
            if (sock_res == SOCKET_ERROR) {
				printf("sendto error:%d\n",WSAGetLastError());
                sock_res = -1;
            } else {
				printf("发送出的数据:%s\n",recv_data);
				}
        } else {
			printf("recvfrom error:%d\n",WSAGetLastError());
            sock_res = -1;
        }
    } while (sock_res > 0);

    // 出错,继续其他客户端请求
    if (sock_res == -1) puts("此用户连接出错,继续下一个连接...");
    return NULL;
}
/*并发服务器*/
int UdpServer(SOCKET sock_conn){
    int sock_res;
    char recv_data[MAXLINE];
    struct sockaddr_in client_addr;
    int addr_len = sizeof(client_addr);
    SOCKET sock_child;
    struct sockaddr_in server_addr;
    const int on = 1;
    HANDLE hThread;

    do {
        memset(recv_data, 0, MAXLINE);
        // 接收数据
        sock_res = recvfrom(sock_conn, recv_data, MAXLINE, 0, (SOCKADDR *) &client_addr, &addr_len);
        if (sock_res > 0) {
			printf("接收到的数据:%s\n",recv_data);
            // 回射接收的数据

            // 创建一个新的socket
            // 设置地址、端口
            memset(&server_addr, 0, sizeof(server_addr));
            server_addr.sin_family = AF_INET;
            server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
            server_addr.sin_port = 0;
            //创建套接字
            sock_child = socket(AF_INET, SOCK_DGRAM, 0);
            // 创建失败
            if (sock_child == INVALID_SOCKET) {
				printf("sock_child error:%d\n",WSAGetLastError());
                cleanUp();
                return -1;
            }
            
            //绑定服务器地址
            sock_res = bind(sock_child, (struct sockaddr *) &server_addr, sizeof(server_addr));
            if (sock_res == SOCKET_ERROR) {
                printf("bind error:%d\n",WSAGetLastError());
                closeConn(sock_child);
                return -1;
            }

            // 获取端口号
            struct sockaddr_in temp_conn;
            int temp_len = sizeof(temp_conn);
            sock_res = getsockname(sock_child, (SOCKADDR*)&temp_conn, &temp_len);
            if (sock_res != 0){
                puts("getsockname error");
                return -1;
            }
            int port = ntohs(temp_conn.sin_port);
            memset(recv_data, 0, MAXLINE);
            itoa(port, recv_data, 10);

            sock_res = sendto(sock_conn, recv_data, (int)strlen(recv_data), 0, (SOCKADDR *) &client_addr, addr_len);
            if (sock_res == SOCKET_ERROR) {
                printf("sendto error:%d\n",WSAGetLastError());
                sock_res = -1;
            } else {
				printf("发送出的数据:%s\n",recv_data);
				hThread=CreateThread(NULL,0,ThreadUdpEchoServer,&sock_child,0,NULL);
                return 0;
            }
        } else {
			printf("recvfrom error:%d\n",WSAGetLastError());
            sock_res = -1;
        }
    } while (sock_res > 0);

    // 出错,继续其他客户端请求
    if (sock_res == -1) 
	{
		CloseHandle(hThread);
		puts("此用户连接出错,继续下一个连接...");
	}
    return 0;
}
int main(int argc, char *argv[]) {
    int sock_res;
    SOCKET sock_listen;
	HANDLE hThread;

    if (StartUp() == -1) return -1;  // 启动
    sock_listen = udpServerInit(SEVER_PORT);  //监听
    if (sock_listen == -1) return -1;
	puts("udpServerInit success");
    while (1) {
        sock_res = UdpServer(sock_listen);
        // 出错,继续其他客户端请求
        if (sock_res == -1) puts("此用户连接出错,继续下一个连接...");
    }
	CloseHandle(hThread);
    // 关闭监听服务
    closeConn(sock_listen);
    return 0;
}

客户端代码(并发服务器对应的)

//回射程序,并发服务器对应的客户端
#define NETWORK1_2_1_A_COMM_H
#define NETWORK1_2_1_A_COMM_H
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <inaddr.h>
#include <time.h>
#include <string.h>
#pragma comment(lib, "ws2_32.lib")
#define MAXLINE 4096     // 接收缓冲区长度
#define LISTENQ 1024     // 监听队列长度
#define SEVER_PORT 13442 // 端口 
char server_ip[] = "10.236.10.200"; 
/*套接字初始化*/ 
int StartUp()
{
    WORD w_version = MAKEWORD(2, 2);
    WSADATA wsa_data;
    int res = WSAStartup(w_version, &wsa_data);
    if (res != 0)
    {
    	printf("WSAStartup Error:%d\n",WSAGetLastError());
        return -1;
    }//版本号错误 
    if (LOBYTE(wsa_data.wVersion) != 2 || HIBYTE(wsa_data.wVersion) != 2)
    {
       	printf("WSAStartup Error:%d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }

    return 0; //成功返回0 
}
/*释放资源*/ 
int cleanUp()
{
    int res = WSACleanup();
    if (res == SOCKET_ERROR)
    {
    	printf("WSACleanup Error:%d",WSAGetLastError()); 
        return -1;
    }
    return 0;
}
/*断开连接,释放资源*/ 
int closeConn(SOCKET sock_conn)
{
    int res = closesocket(sock_conn); //关闭连接
    if (res == SOCKET_ERROR)
    {
    	printf("关闭连接失败%d\n",WSAGetLastError());
        return -1;
    }
    res = cleanUp();
    return res;
}
int getline(char s[],int lim)
{
    int c,i;
    i=0;
    while((c=getchar())!=EOF&&c!='\n'&&i<lim-1)
        s[i++]=c;
    s[i]='\0';
    return i;
}
/*回射客户端发送接收程序*/
int udpEchoClient(SOCKET sock_conn, SOCKADDR *server_addr, int addr_len)
{
    int res = -1;
    char send_data[MAXLINE], recv_data[MAXLINE];
    memset(send_data, 0, MAXLINE);
    memset(recv_data, 0, MAXLINE);
	 
    while (getline(send_data, MAXLINE))//输入缓冲区接收一行数据 
    {
        // 结束
        if (*send_data == 'Q' || *send_data == 'q')
        {
            puts("退出"); 
            return 0; // 正常退出
        }
        // 发送数据
        res = sendto(sock_conn, send_data, (int)strlen(send_data), 0, (SOCKADDR *)server_addr, addr_len);
        if (res == SOCKET_ERROR)
        {
        	printf("发送失败:%d\n",WSAGetLastError());
            return -1;
        }
		printf("发送数据:%s\n",send_data); 
        // 接收数据
        res = recvfrom(sock_conn, recv_data, MAXLINE, 0, NULL, NULL);
        if (res > 0)
        {
        	printf("接收到的数据:%s\n",recv_data);
        }
        else
        {
            printf("接收失败:%d\n",WSAGetLastError());
            break;
        }

        // 清空缓存
        memset(recv_data, 0, MAXLINE);
        memset(send_data, 0, MAXLINE);
    }
    return res;
}
/*连接服务器并绑定*/
SOCKET udpClientInit(char *server_ip, u_short port, sockaddr_in &server_addr, bool flag)
{
    int res = -1;
    SOCKET sock_conn;
    // 绑定地址、端口
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.S_un.S_addr = inet_addr(server_ip);
    //创建套接字
    sock_conn = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_conn == INVALID_SOCKET)
    {
    	printf("连接初始化失败%d\n",WSAGetLastError());
        cleanUp();
        return -1;
    }

    if (flag)
    {
        // 请求连接服务器
        res = connect(sock_conn, (struct sockaddr *)&server_addr, sizeof(server_addr));
        if (res == SOCKET_ERROR)
        {
            // 连接服务器失败
            printf("连接失败:%d\n",WSAGetLastError()); 
            closeConn(sock_conn);
            return -1;
        }
    }

    return sock_conn; //连接成功
}

/*对应并发服务器,客户端请求新端口函数*/
int UdpClientAskNewPort(SOCKET sock_conn, sockaddr_in server_addr, int addr_len){
    int sock_res;
    char send_data[MAXLINE] = "ASK port", recv_data[MAXLINE];
    memset(recv_data, 0, MAXLINE);

    while (1) {
        sock_res = sendto(sock_conn, send_data, (int) strlen(send_data), 0, (SOCKADDR *)&server_addr, addr_len);
        if (sock_res == SOCKET_ERROR) {
			printf("ASK port send error:%d\n",WSAGetLastError());
            return -1;
        }

        memset(recv_data, 0, MAXLINE);
        sock_res = recvfrom(sock_conn, recv_data, MAXLINE, 0, NULL, NULL);

        if (sock_res > 0) {
			printf("Requset :接收到的新端口号为:%s\n",recv_data);
			//客户端重新连接
            sock_conn = udpClientInit(server_ip, (u_short) atoi(recv_data), server_addr, true);
            if (sock_conn == -1) 
			{
				puts("客户端重新连接新端口失败");
				return -1;
			}
            sock_res = udpEchoClient(sock_conn, (SOCKADDR *)&server_addr, addr_len);
            if (sock_res == -1) puts("客户端连接失败");
            closeConn(sock_conn);

            break;
        } else {
			printf("recv error:%d\n",WSAGetLastError());
            return -1;
        }
    }

    return 0;
}

int main() {
    int sock_res;
    SOCKET sock_conn;
    sockaddr_in server_addr;
    
    if (StartUp() == -1) return -1;  // 启动
    sock_conn = udpClientInit(server_ip, SEVER_PORT, server_addr, true);  //连接
    if (sock_conn == -1) return -1;
    // 连接服务器成功
	puts("客户端启动");
    sock_res = UdpClientAskNewPort(sock_conn, server_addr, sizeof(sockaddr_in));
    if (sock_res == -1) puts("连接失败");
    closeConn(sock_conn);
    return sock_res;
}

三、用户使用说明(输入 / 输出规定)

对回射服务器和客户端(循环服务器以及并发服务器):

  1. 对于服务器端程序,用户无需输入,服务器端程序会自动输出接收到的数据以及每次发送给客户端的数据,若函数调用出现错误,会显示文字(例如:“连接失败”)并显示 WSAGetLastError()返回的错误号。
  2. 对于客户端程序,运行后当显示“连接服务器成功”后,用户可输入发给服务器的内容,回车就会发送,当输入“Q”或“q”时会断开连接。
  3. 对客户端程序,每当发送完数据后,屏幕会显示从服务器端接收到的数据

四、运行结果

回射程序,客户端和循环服务器运行结果

在这里插入图片描述

回射程序,客户端和并发服务器运行结果

在这里插入图片描述

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

20230921

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值