《TCP/IP网络编程》记录

unit 1

课后习题

1.套接字在网络编程中的作用是什么?为什么称它为套接字?

套接字是网络数据传输用的软件设备
我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到因特网,而编程中的“套接字”就是用来连接该网络的工具。它本身就带有“连接”的含义,如果将其引申,则还可以表示两台计算机之间的网络连接。

 

2.在服务器端创建套接字后,会依次调用listen函数和accept函数。请比较并说明两者作用

listen:将套接字转为可接收链接状态
accept:受理链接请求

 

3.Linux中,对套接字数据进行I/O时可以直接使用I/O相关函数;而在Windows中则不可以。原因为何?

对于Linux而言,socket操作和文件操作没有区别,但是在Windows中socket进行文件操作,则需要调用特殊的数据传输相关函数。


4.创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?

区分来自不同机器的套接字,通过bind()分配地址信息(IP地址和端口号)


5.Linux中的文件描述符与Windows的句柄实际上非常类似。请以套接字为对象说明他们的含义。

Linux中的文件描述符是系统分配给文件或套接字的整数
Windows中和Linux概念相同,不过Windows要区分文件句柄和套接字句柄

 

6.底层文件I/O函数与ANSI标准定义的文件I/O函数之间有何区别?

文件I/O 又称为低级磁盘I/O,遵循POSIX相关标准。任何兼容POSIX标准的操作系统上都支持文件I/O。标准I/O被称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是GLIBC,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。

通过文件I/O读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销,标准I/O可以看成是在文件I/O的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。

文件I/O中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。而标准I/O中用FILE(流)表示一个打开的文件,通常只用来访问普通文件。

 

7.参考本书给出的示例low_open.c和low_read.c,分别利用底层文件I/O和ANSI标准I/O编写文件复制程序。可任意指定复制程序的使用方法

服务端

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

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAddr, clntAddr;
    FILE* fp;
    int strLen;
    int szClntAddr;
    char message[100];
    char filename[30];

    if (argc != 2) {
        printf("Usage:%s <port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup Error!\n");
    }

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET) {
        ErrorHandling("sock() error!\n");
    }
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        ErrorHandling("bind() error!\n");
    }

    if (listen(hServSock, 5) == SOCKET_ERROR) {
        ErrorHandling("listen() error!\n");
    }

    szClntAddr = sizeof(clntAddr);
    hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
    if (hClntSock == INVALID_SOCKET) {
        ErrorHandling("accept() error!\n");
    }

    printf("请输入文件名:");
    scanf("%s", &filename);
    send(hClntSock, filename, sizeof(filename), 0);

    strLen = recv(hClntSock, message, sizeof(message) - 1, 0);
    if (strLen == -1) {
        ErrorHandling("recv() error!");
    }

    if ((fp = fopen(filename, "w+")) == NULL) {
        ErrorHandling("fopen() error!");
    }

    fputs(message, fp);
    fclose(fp);
    closesocket(hServSock);
    closesocket(hClntSock);
    WSACleanup();

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

客户端

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

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hSocket;
    SOCKADDR_IN servAddr;
    int strLen;
    FILE* fp;

    char message[100] = { 0 };
    char tmpmessage[100] = { 0 };
    char filename[30];
    if (argc != 3) {
        printf("Usage: %s <IP><port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup() error!");
    }
    hSocket = socket(PF_INET, SOCK_STREAM, 0);
    if (hSocket == INVALID_SOCKET) {
        ErrorHandling("socket() error!");
    }
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        ErrorHandling("connect() error!");
    }

    strLen = recv(hSocket, filename, sizeof(filename) - 1, 0);

    if (strLen == -1) {
        ErrorHandling("recv() error!");
    }

    if ((fp = fopen(filename, "r")) == NULL) {
        ErrorHandling("fopen() error!");
    }

    while (fgets(tmpmessage, 255, fp) != NULL) {
        strcat(message, tmpmessage);
    }
    message[strlen(message)] = '\0';

    send(hSocket, message, sizeof(message), 0);

    fclose(fp);
    closesocket(hSocket);
    WSACleanup();

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

unit 2

课后习题

1.什么是协议?在收发数据中定义协议有何意义?

为了完成数据交换而定好的约定。

对数据传输中所需承诺进行定义。

 

2.面向连接的TCP套接字传输特性有3点,请分别说明。

  1. 传输过程中数据不会消失。
  2. 按序传输数据。
  3. 传输的数据不存在数据边界(Boundary)

 

3.面哪些是面向消息的套接字的特性?

a,c,e

 

4.下列数据适合用哪类套接字传输?并给出原因。

UDP,需要快速传输

TCP,传输过程中数据不会消失

TCP,传输过程中数据不会消失

 

5.何种类型的套接字不存在数据边界?这类套接字接收数据时需要注意什么?

面向连接的套接字(SOCK_STREAM),保证缓冲区数据不会被填满,或者传输速度不大于读取速度。

 

6.tcp_server.c和ltcp_client.c中需多次调用read函数读取服务器端调用1次write函数传递的字符串。更改程序,使服务器端多次调用(次数自拟)write函数传输数据,客户端调用1次read函数进行读取。为达到这一目的,客户端需延迟调用read函数,因为客户端要等待服务器端传输所有数据。Windows和Linux都通过下列代码延迟read或recv函数的调用。

服务端

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

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAddr, clntAddr;
    int szClntAddr = 0, idx = 0, send_len = 0;
    char message[100] = "hello world!";

    if (argc != 2) {
        printf("Usage:%s <port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup Error!");
    }

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET) {
        ErrorHandling("socket() error!");
    }
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        ErrorHandling("bind() error!");
    }

    if (listen(hServSock, 5) == SOCKET_ERROR) {
        ErrorHandling("listen() error!");
    }
    szClntAddr = sizeof(clntAddr);
    hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
    if (hClntSock == INVALID_SOCKET) {
        ErrorHandling("accept() error!");
    }

    while (message[idx-1] != '\0') {
        send(hClntSock, &message[idx++], 1, 0);
    }

    closesocket(hServSock);
    closesocket(hClntSock);
    WSACleanup();
    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

客户端

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

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAddr, clntAddr;
    int szClntAddr,idx = 0, send_len = 0;
    char message[100] = "hello world!";

    if (argc != 2) {
        printf("Usage:%s <port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup Error!");
    }

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET) {
        ErrorHandling("sock() error!");
    }
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        ErrorHandling("bind() error!");
    }

    if (listen(hServSock, 5) == SOCKET_ERROR) {
        ErrorHandling("listen() error!");
    }

    szClntAddr = sizeof(clntAddr);
    hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
    if (hClntSock == INVALID_SOCKET) {
        ErrorHandling("accept() error!");
    }
    
    send(hClntSock, message, sizeof(message), 0);

    closesocket(hServSock);
    closesocket(hClntSock);
    WSACleanup();

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

unit 3

课后习题

1.IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?

IPv4使用4字节地址族,IPv6使用16字节地址族。

IPv6是为了应对2010年前后IP地址耗尽的问题而提出的标准。

 

2.通过IPV4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程

传输数据首先通过IPv4的网络ID传输到网络中,路由器接受到数据后,通过主机ID传输给目标计算机。

 

3.套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?

IP用于区分计算机,端口号用于区分同一操作系统中的不同套接字。

 

4.请说明IP地址的分类方法,并据此说出下面这些IP地址的分类。

根据IP地址的边界区分网络地址

C,A,B

 

5.计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用

路由器和交换机是数据传输的中介。

 

6.什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP合同FTP端口号各是多少?

那些由互联网名称与数字地址分配机构(ICANN)预留给传输控制协议(TCP)和用户数据包协议(UDP)使用的端口号。

0~1023

80和21

 

7.题目大概意思是:为什么bind中第二个参数是sockaddr,但是传入的是sockaddr_in

sockaddr中sa_data保存IP和端口号会很麻烦,因为sockaddr_in和sockaddr结构体结构相同,因此可以通过强制转换为sockaddr类型,传递给bind函数。

 

8.请解释大端序、小端序、网络字节序,并说明为何需要网络字节序

大端序:高位字节存储到低位上,低位字节存储到高位上

小端序:高位字节存储到高位上,低位字节存储到低位上

网络字节序:大端序

为网络数据传输制定标准。

 

9.大端计算机希望将4字节整型数据12传到小端序计算机。请说出数据传输过程中发生的字节序变换过程

因为计算机为大端序(00 00 00 0c),因此传输到网络中不需要交换顺序(00 00 00 0c),从网络将数据传输到小端计算机需要交换顺序(0c 00 00 00)

 

10.怎么表示回送地址?其含义是什么?如果向回送地址传输数据将会发生什么情况?

回送地址就是计算机本身的地址127.0.0.1,

不会经过网络传输数据,数据会直接返回。

 

unit 4

课后习题

1.请说明TCP/IP的4层协议栈,并说明TCP和UDP套接字经过的层级结构差异

链路层 IP层 TCP层 应用层

UDP和TCP第三层不同,UDP为UDP层

 

2.请说出TCP/IP协议栈中链路层和IP层的作用,并给出两者关系。

链路层:专门定义LAN,WAN,MAN等网络标准,是物理链接领域标准化的结果。

IP层:定义网络数据传输路径的层级。

IP层负责以链路层为基础的数据传输。

 

3.为何需要把TCP/IP协议栈分成4层(或7层)?结合开放式系统回答

将复杂的TCP/IP协议分层化的话,就可以将分层的层级标准发展成开放系统。实际上,TCP/IP是开放系统,各层级都被初始化,并以该标准为依据组成了互联网。因此,按照不同层级标准,硬件和软件可以相互替代,这种标准化是TCP/IP蓬勃发张的依据。

 

4.客户端调用connect函数向服务器端发送连接请求。服务器端调用哪个函数后,客户端可以调用connect函数?

listen

 

5.什么时候创建连接请求等待队列?它有何作用?与accept有什么关系

listen

创建请求等待队列

accept函数受力连接请求等待队列中待处理的客户端连接请求。

 

6.客户端中为何不需要调用bind函数分配地址?如果不调用bind函数,那何时、如何向套接字分配IP地址和端口号?

客户端的IP地址和端口在调用connect函数时自动分配,无需调用标记的bind函数进行分配。

何时:调用connect函数时,如何:IP用计算机(主机)的IP,端口随机。

 

7.改成迭代服务器端

服务端

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

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN servAddr, clntAddr;
    int szClntAddr,idx = 0, send_len = 0;
    char message[100] = "hello world!";
    int strLen;

    if (argc != 2) {
        printf("Usage:%s <port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup Error!");
    }

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET) {
        ErrorHandling("sock() error!");
    }
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));


    if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        ErrorHandling("bind() error!");
    }

    if (listen(hServSock, 5) == SOCKET_ERROR) {
        ErrorHandling("listen() error!");
    }

    szClntAddr = sizeof(clntAddr);
    hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
    if (hClntSock == INVALID_SOCKET) {
        ErrorHandling("accept() error!");
    }
    while (1) {
        send(hClntSock, message, sizeof(message), 0);

        strLen = recv(hClntSock, message, sizeof(message), 0);
        if (strLen == -1)
            ErrorHandling("recv() error!");
        printf("Message from client:%s\n", message);
        //closesocket(hClntSock);
    }
    closesocket(hClntSock);
    closesocket(hServSock);
    WSACleanup();

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

客户端

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Windows.h>

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hSocket;
    SOCKADDR_IN servAddr;
    int strLen = 0, recv_len = 0;

    char message[100] = { 0 };

    if (argc != 3) {
        printf("Usage: %s <IP><port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup() error!");
    }
    hSocket = socket(PF_INET, SOCK_STREAM, 0);
    if (hSocket == INVALID_SOCKET) {
        ErrorHandling("socket() error!");
    }
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) {
        ErrorHandling("connect() error!");
    }

    //Sleep(3000);
    while (1) {
        recv_len = recv(hSocket, message, sizeof(message), 0);
        if (recv_len == -1)
            ErrorHandling("recv() error!");
        printf("Message from server:%s\n", message);
        send(hSocket, message, sizeof(message), 0);
        Sleep(1000);
    }
    closesocket(hSocket);
    WSACleanup();

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

unit 5

练习

客户端(op_client.c)

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

#define N 1024

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET serv_sock;
    SOCKADDR_IN serv_addr;
    int i;

    if(argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStarup error!");

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == INVALID_SOCKET)
        ErrorHandling("socket() error!");
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if (connect(serv_sock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
        ErrorHandling("connect() error");
    else
        puts("Connected... ...\n");
    while (1) {
        char a[N] = { 0 }, s[10] = { 0 }, result[10] = { 0 };
        int op_count = 0;
        fputs("operand count:", stdout);
        scanf("%d", &op_count);

        for (i = 0; i < op_count; ++i) {
            fputs("Operand:", stdout);
            scanf("%s", &a[i*5]);
        }

        fputs("Operator:", stdout);
        scanf("%s", &a[op_count*5]);

        if (send(serv_sock, a, N, 0) == -1)
            ErrorHandling("send() error!");

        int flag = recv(serv_sock, result, 10, 0);
        if ( flag == -1 )
            ErrorHandling("recv() error!");
        if (flag != 0) {
            printf("Operation:%s\n", result);
            Sleep(1000);
        }
        
        fputs("Continue?(Y or N):", stdout);
        scanf("%s", s);
        if (!strcmp(s,"n") || !strcmp(s, "N"))
            break;
        memset(a, 0, N);
    }
    closesocket(serv_sock);
    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

服务端(op_server.c)

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

#define N 1024
#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")

void ErrorHandling(const char* message);
int computer(int* num, char* arr, int n);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKADDR_IN serv_addr, clnt_addr;
    SOCKET serv_sock, clnt_sock;
    int i, strLen = 0, sz = 0;
    char a[N] = { 0 };
    int numarr[N] = { 0 };

    if (argc != 2) {
        printf("Usage: %s<port>", argv[0]);
        system("PAUSE");
        return 0;
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");
    
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == INVALID_SOCKET)
        ErrorHandling("socket() error!");
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
        ErrorHandling("bind() error!");

    if (listen(serv_sock, 5) == SOCKET_ERROR)
        ErrorHandling("listen() error!");

    sz = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr*) & clnt_addr, &sz);
    if (clnt_sock == -1)
        ErrorHandling("accept() error!");

    while (1) {
        char result[10] = { 0 };
        while ((strLen = recv(clnt_sock, a, N, 0)) != 0) {
            //printf("message from client:");
            if (strLen > 0) {
                for (i = 0; i < N; i += 5) {
                    if (a[i + 5] == '\0') {
                        itoa(computer(numarr, a, i), result,10);
                        send(clnt_sock, result, 10, 0);
                        break;
                    }
                    //printf("%s  ", &a[i]);
                    numarr[i / 5] = atoi(&a[i]);
                }
                memset(a, 0, N);
                break;
            }

        }
        //closesocket(clnt_sock);
    }

    closesocket(serv_sock);

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

int computer(int* num, char* arr, int n) {
    int i, result = num[0];
    //for (i = 0; i < 10; ++i) {
    //    printf("%d\t", num[i]);
    //    printf("\n%s\n", &arr[n]);
    //}

    if (!strcmp(&arr[n], "+")) {
        for (i = 1; i < 10; ++i)
            result += num[i];
    }
    else if (!strcmp(&arr[n], "-")) {
        for (i = 1; i < 10; ++i)
            result -= num[i];
    }
    else {
        printf("error!\n");
    }

    printf("%d\n", result);

    return result;
}

 

课后习题

1.请说明TCP套接字连接设置的三次握手过程。尤其是3次数据交换过程每次收发的数据内容。

A:SEQ:1000, ACK:-

B:SEQ:2000,ACK:1001

A:SEQ:1001,ACK:2001

 

2.TCP是可靠的数据传输协议,但在通过网络通信的过程可能丢失数据。请通过ACK和SEQ说明TCP通过何种机制保证丢失数据的可靠传输。

通过SEQ传输信息编号,ACK通过信息编号返回编号信息。

 

3.TCP套接字中调用write和read函数时数据如何移动?结合I/O缓冲进行说明

write将数据写入输出缓存区,再通过网络传输将输出缓存区数据传输到输入缓存区,read从输入缓存区读取数据。

 

4.对方主机的输入缓冲剩余50字节空间时,若本方主机通过write函数请求传输70字节,问TCP如何处理这种情况?

多出的数据会在输入缓存区中等待。

 

5.第2章示例tcp_server.c....

tcp_server.c

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

#define BUF_SIZE 1024

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKADDR_IN serv_addr, clnt_addr;
    SOCKET hServSock, hClntSock;
    char message[BUF_SIZE] = { "Hello World!" };
    int i = 0, strLen = 0;

    if (argc != 2) {
        printf("Usage: %s <port>", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStarup() error!");

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET)
        ErrorHandling("socket() error!");
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if (bind(hServSock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
        ErrorHandling("bind() error!");
    
    if (listen(hServSock, 5) == SOCKET_ERROR)
        ErrorHandling("listen() error!");

    strLen = sizeof(clnt_addr);

    hClntSock = accept(hServSock, (struct sockaddr*) & clnt_addr, &strLen);
    if (hClntSock == SOCKET_ERROR)
        ErrorHandling("accept() error!");

    for (; i < 3; ++i) {
        strLen = strlen(message) + 1;
        send(hClntSock, (char*)&strLen, 4, 0);
        send(hClntSock, message, strLen, 0);

        memset(message, 0, strLen);

        recv(hClntSock, (char*)&strLen, 4, 0);
        recv(hClntSock, message, strLen, 0);
        
        printf("Message from client: %s\n", message);
    }
    
    closesocket(hClntSock);
    closesocket(hServSock);

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    
    system("PAUSE");
    exit(1);
}

 

tcp_client.c

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

#define BUF_SIZE 1024
#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hSock;
    SOCKADDR_IN serv_addr;
    int i = 0, strLen = 0;
    char message[BUF_SIZE] = { 0 };

    if (argc != 3) {
        printf("Usage: %s <IP><port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");

    hSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hSock == INVALID_SOCKET)
        ErrorHandling("socket() error!");

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if (connect(hSock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR)
        ErrorHandling("connect() error!");

    for (; i < 3; ++i) {
        recv(hSock, (char*)&strLen, 4, 0);
        recv(hSock, message, strLen, 0);

        printf("Message from server: %s\n", message);

        strLen = strlen(message) + 1;
        send(hSock, (char*)&strLen, 4, 0);
        send(hSock, message, strLen, 0);

        memset(message, 0, strLen);
    }

    closesocket(hSock);

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);

    system("PAUSE");
    exit(1);
}

 

7.创建手法文件的服务端/客户端,实现顺序如下。

  • 客户端接受用户输入的传输文件名。
  • 客户端请求服务器端传输该文件名所指的文件。
  • 如果指定文件存在,服务端就将其发送给客户端;反之,则断开连接。

 

file_server.c

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

#define BUF_SIZE 1024
#define FILE_NAME_SIZE 100
#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    SOCKADDR_IN hServAddr, hClntAddr;
    int strLen = 0, i = 0;
    char FileArr[BUF_SIZE] = { 0 }, FileName[FILE_NAME_SIZE] = { 0 };
    FILE* fp;
    
    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);

        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET)
        ErrorHandling("socket() error!");

    memset(&hServAddr, 0, sizeof(hServAddr));
    hServAddr.sin_family = AF_INET;
    hServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    hServAddr.sin_port = htons(atoi(argv[1]));

    if (bind(hServSock, (struct sockaddr*) & hServAddr, sizeof(hServAddr)) == SOCKET_ERROR)
        ErrorHandling("bind() error!");

    if (listen(hServSock, 5) == SOCKET_ERROR)
        ErrorHandling("listen() error!");

    strLen = sizeof(hClntAddr);
    hClntSock = accept(hServSock, (struct sockaddr*) & hClntAddr, &strLen);
    if (hClntSock == SOCKET_ERROR)
        ErrorHandling("accept() error!");

    if (recv(hClntSock, FileName, FILE_NAME_SIZE, 0) == -1)
        ErrorHandling("recv() error!");
    if ((fp = fopen(FileName, "r")) == NULL)
        ErrorHandling("Open file error!");
    fread(FileArr, sizeof(char), BUF_SIZE, fp);
    send(hClntSock, FileArr, BUF_SIZE, 0);

    fclose(fp);
    closesocket(hClntSock);
    closesocket(hServSock);

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);

    system("PAUSE");
    exit(1);
}

 

file_client

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

#define BUF_SIZE 1024
#define FILE_NAME_SIZE 100

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hSock;
    SOCKADDR_IN hServAddr;
    char FileName[FILE_NAME_SIZE] = { 0 }, FileArr[BUF_SIZE] = { 0 };
    FILE* fp;

    if (argc != 3) {
        printf("Usage:%s<IP><port>\n", argv[0]);
        system("PAUSE");
        return 0;
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() error!");

    hSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hSock == INVALID_SOCKET)
        ErrorHandling("socket() error!");

    memset(&hServAddr, 0, sizeof(hServAddr));
    hServAddr.sin_family = AF_INET;
    hServAddr.sin_addr.s_addr = inet_addr(argv[1]);
    hServAddr.sin_port = htons(atoi(argv[2]));

    if (connect(hSock, (struct sockaddr*) & hServAddr, sizeof(hServAddr)) == SOCKET_ERROR)
        ErrorHandling("connect error!");

    printf("请输入文件名:");
    scanf("%s", FileName);

    send(hSock, FileName, FILE_NAME_SIZE, 0);
    if (recv(hSock, FileArr, BUF_SIZE, 0) == -1)
        ErrorHandling("文件打开失败或者不存在!");

    if ((fp = fopen(FileName, "w")) == NULL)
        ErrorHandling("fopen() error!");

    fwrite(FileArr, sizeof(char), BUF_SIZE, fp);

    fclose(fp);
    closesocket(hSock);

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);

    system("PAUSE");
    exit(1);
}

 

unit 6

1.UDP为什么比TCP速度快?为什么TCP数据传输可靠而UDP数据传输不可靠?

没有收发数据前后进行的连接设置和清除过程。

没有收发数据过程中为保证可靠性而添加的流控制。

 

2.

bce

 

3.UDP数据包向对方主机的UDP套接字传递过程中,IP和UDP分别负责哪些部分?

IP:连接目标机,UDP:数据传输

 

4.UDP一般比TCP快,但根据交换数据的特点,其差异可大可小。请说明何种情况下UDP的性能优于TCP

一对多,多对多目标传输。

少量数据,多次传输。

 

5.客户端TCP套接字调用connect函数时自动分配IP和端口号。UDP中不调用bind函数,那何时分配IP和端口号?

sendto

 

6.TCP客户端必须调用connect函数,而UDP中可以选择性调用。请问,在UDP中调用connect函数有哪些好处?

在长时间多次与同一主机传输数据中,减少套接字的创建与删除,数据传输速率得到提高。

 

7.收发的消息均要输出到控制台窗口

client

 

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

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET servSock;
    int strLen = 0;
    char message[BUF_SIZE] = { 0 };

    SOCKADDR_IN servAddr;

    if (argc != 3) {
        printf("Usage:%s<IP><port>\n", argv[0]);
        system("PAUSE");
        return 0;
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() Error!");

    servSock = socket(PF_INET, SOCK_DGRAM, 0);
    if (servSock == INVALID_SOCKET)
        ErrorHandling("socket() Error!");
    
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    connect(servSock, (struct sockaddr*) & servAddr, sizeof(servAddr));

    while (1) {
        fputs("input message(q to quit):", stdout);
        fgets(message, BUF_SIZE - 1, stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;
        send(servSock, message, strlen(message), 0);
        memset(message, 0, BUF_SIZE);
        recv(servSock, message, BUF_SIZE, 0);
        printf("message from server:%s\n", message);
        memset(message, 0, BUF_SIZE);
    }

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    system("PAUSE");
    exit(1);
}

 

 

server

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

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET servSock;
    char message[BUF_SIZE] = { 0 };
    int strLen = 0, clntAddrsz;

    SOCKADDR_IN servAddr, clntAddr;

    if (argc != 2) {
        printf("Usage:%s<port>\n", argv[0]);
        system("PAUSE");
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() Error!");

    servSock = socket(PF_INET, SOCK_DGRAM, 0);
    if (servSock == INVALID_SOCKET)
        ErrorHandling("socket() Error!");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if (bind(servSock, (struct sockaddr*) & servAddr, sizeof(servAddr)) == SOCKET_ERROR)
        ErrorHandling("bind() Error!");

    while (1) {
        clntAddrsz = sizeof(clntAddr);
        strLen = recvfrom(servSock, message, BUF_SIZE, 0, (struct sockaddr*) & clntAddr, &clntAddrsz);
        printf("message from client:%s", message);
        memset(message, 0, BUF_SIZE);
        fputs("input message(q to quit):", stdout);
        fgets(message, BUF_SIZE - 1, stdin);
        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;
        sendto(servSock, message, BUF_SIZE, 0, (struct sockaddr*) & clntAddr, clntAddrsz);
        memset(message, 0, BUF_SIZE);
    }

    closesocket(servSock);

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);

    system("PAUSE");
    exit(1);
}

 

unit 7

1.解释TCP中“流”的概念。UDP中能否形成流?请说明原因

指套接字建立连接,传输数据的过程。

不能,因为UDP不需要建立连接,通过完整数据包直接传输数据。

 

2.Linux中的close函数或Windows中的closesocket函数属于单方面断开连接的方法,有可能带来一些问题。什么是单方面断开连接?什么情况下会出现问题?

服务器和客户端都无法接收或者发送数据,当服务器/客户端在传输数据到客户端/服务器时,客户端/服务器断开连接,会造成数据丢失。

 

3.什么是半关闭?针对输出流执行半关闭的主机处于何种状态?半关闭会导致对方主机接收什么信息?

指只关闭输入或者输出流,若输出流关闭,则无法发送数据,但是可以接收数据。

 

unit 8

1.

b,d

 

2.

方法可行,当本地网络没有限制时,可以通过设置正常DNS服务器来解析。

 

3.再浏览器地址输入 www.orentec.co.kr ,并整理出主页显示过程。假设浏览器访问默认 DNS 服务器中并没有关于 www.orentec.co.kr 的地址信息.

  1. 主机向默认DNS解析域名为IP,无法解析
  2. 默认服务器向上级DNS服务器询问,直到询问到IP(最多询问到根服务器)
  3. DNS服务器将IP原路返回到发起请求的主机

 

unit 9

习题

1.下列关于Time-wait状态的说法错误的是?

a,c

2.TCP_NODELAY可选项与Nagle算法有关,可通过它禁止Nagle算法。请问何时应考虑禁用Nagle算法?结合收发数据的特性给出说明

在"大文件"的传输上

 

unit 12

课后练习

1.请解释复用技术的通用含义,并说明何为I/O复用。

在一个通信频道中传递多个数据(信号)的技术

将需要I/O的套接字捆绑在一起,利用最少限度的资源来收发数据的技术

 

2.多进程并发服务器的缺点有哪些?如何在I/O复用服务器端中弥补?

并发服务器需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法。

因为复用服务器是将套接字与I/O捆绑在一起的,因此我们能使用进程管理所有的I/O操作。

 

3.

bc

 

4.select函数的观察对象中应包含服务器端套接字(监听套接字),那么应将其包含到哪一类监听对象集合?请说明原因

服务端套接字用来判断是否有请求连接,读取状态,因此应该将其包含到“读取”当中。

 

5.select函数使用的fd_set结构体在Windows和Linux中具有不同的声明。请说明却别,同时解释存在区别的必然性

Linux的文件描述符从0开始递增,因此可以找出当前文件描述符数量和最后生成的文件描述符之间的关系。但Windows的套接字句柄并非从0开始,而且句柄的整数值之间并无规律可循,因此需要直接保存句柄的数组和记录句柄数的变量。

 

I/O复用

服务端

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

#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")

#define BUF_SIZE 1024

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hServSock, hClntSock;
    int strLen = 0, fdNum = 0, i;
    char buf[BUF_SIZE] = { 0 };
    fd_set reads, cpyReads;
    struct timeval timeout;

    int adrSz = 0;
    SOCKADDR_IN servAdr, clntAdr;

    if (argc != 2) {
        printf("Usage:%s <port>\n", argv[0]);
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup() Error!");
    }

    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if (hServSock == INVALID_SOCKET) {
        ErrorHandling("socket() Error!");
    }
    memset(&servAdr, 0, sizeof(servAdr));
    servAdr.sin_family = AF_INET;
    servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAdr.sin_port = htons(atoi(argv[1]));

    if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) {
        ErrorHandling("bind() Error!");
    }

    if (listen(hServSock, 5) == SOCKET_ERROR) {
        ErrorHandling("listen() Error!");
    }

    // 初始化fd_set
    FD_ZERO(&reads);
    // PD_SET的第一个参数文件描述符设定为服务器端套接字,这样当服务端有接受数据时,即新的连接请求,即读取数据,就会激活状态。
    FD_SET(hServSock, &reads);

    while (1) {
        // 拷贝值和设定超时的时间
        cpyReads = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;
        //调用select函数,判断套接字中是否有数据传输,即新的连接请求,这里SOCKET_ERROR为-1,即当select失败时
        if ((fdNum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR) {
            break;
        }
        // 当超时时继续返回循环,监视服务器端套接字
        if (fdNum == 0) {
            continue;
        }
        // 当套接字有数据传输时,循环使用FD_ISSET查找发生变化的文件描述符
        for (i = 0; i < reads.fd_count; ++i) {
            if (FD_ISSET(reads.fd_array[i], &cpyReads)) {
                // 若发生状态变化的文件标识符是服务器套接字,则受理客户端连接请求
                if (reads.fd_array[i] == hServSock) {
                    adrSz = sizeof(clntAdr);
                    hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &adrSz);
                    // 注册与客户端连接的套接字,检测客户端连接套接字,当客户端传输数据时,就会激活else,接收数据
                    FD_SET(hClntSock, &reads);
                    printf("connected client:%d \n", hClntSock);
                }
                // 如果服务器端的套接字未发生变化,判断接受的数据表示断开还是字符串
                else {
                    strLen = recv(reads.fd_array[i], buf, BUF_SIZE - 1, 0);
                    // 断开连接
                    if (strLen == 0) {
                        // 从reads中删除该文件描述符套接字
                        FD_CLR(reads.fd_array[i], &reads);
                        closesocket(cpyReads.fd_array[i]);
                        printf("closed client:%d \n", cpyReads.fd_array[i]);
                    }
                    // 字符串数据
                    else {
                        send(reads.fd_array[i], buf, strLen, 0);
                    }
                }
            }
        }
    }

    closesocket(hServSock);
    WSACleanup();

    system("PAUSE");
    return 0;
}

/**
 * @brief 
 * @param message 
*/
void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);

    system("PAUSE");
    exit(1);
}

 

客户端

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

#define BUF_SIZE 1024

#pragma warning(disable:4996)
#pragma comment(lib,"ws2_32.lib")

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
    WSADATA wsaData;
    SOCKET hSocket;
    char message[BUF_SIZE] = { 0 };
    int strLen;
    SOCKADDR_IN servAddr;

    if (argc != 3) {
        printf("Usage: %s <IP><port>\n", argv[0]);
        exit(1);
    }

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        ErrorHandling("WSAStartup() Error!");

    hSocket = socket(PF_INET, SOCK_STREAM, 0);
    if (hSocket == INVALID_SOCKET)
        ErrorHandling("socket() error!");
    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    if (connect(hSocket, (struct sockaddr*) & servAddr, sizeof(servAddr)) == SOCKET_ERROR)
        ErrorHandling("connect error!");
    else
        printf("connected... ...\n");
    while (1) {
        fputs("Input message(Q to quit):", stdout);
        fgets(message, BUF_SIZE, stdin);

        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;
        send(hSocket, message, BUF_SIZE, 0);
        strLen = recv(hSocket, message, BUF_SIZE, 0);
        printf("Message from server: %s\n", message);
    }

    closesocket(hSocket);
    WSACleanup();

    system("PAUSE");
    return 0;
}

void ErrorHandling(const char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值