TCP/IP网络编程_第6章基于UDP的服务器端/客户端

在这里插入图片描述

6.1 理解 DUP

我们在第4章学习TCP的过程中, 还同时了解了 TCP/IP 协议. 在4层TCP/IP模型中, 上数第二层传输(Transport)层分为TCP和UDP这两种. 数据交换过程可以分为通过TCP套接字完成的TCP方式和通过UDP套接字完成的UDP方式.

UDP 套接字的特点

下面通过信件说明 UDP 的工作原理, 这是讲解UDP 时使用的传统示例, 它与 UDP 特性完全相符. 寄信前应在信上填好寄信人和收信人的地址, 之后贴上邮票即可. 当然, 信件的特点使我们无法确认对方是否收到. 另外, 邮寄过程中也可能发生信件丢失的情况. 也就是说, 信件是一种不可靠的传输方式. 与之类似, UDP 提供的同样是不可靠的数据传输服务.
在这里插入图片描述
如果只考虑可靠性, TCP 应该的确比 UDP 好. 但UDP 在结构上比 TCP 更简洁. UDP 不会发送类似 ACK 的应答消息, 也不会像SEQ那样给数据包分配序号. 因此, UDP的性能有时比 TCP 高出很多. 编程中实现 UDP 也比 TCP 简单. 另外, UDP 的可靠性虽然比不上 TCP , 但也不会像想象中那么 频繁地数据损毁. 因此, 在更重视性能而非可靠情况下, DUP 是一种很好的选择.

既然如此, UDP 的作用到底是什么呢? 为了提供可靠的数据传输服务, TCP 在不可靠的IP层进行流控制, 而UDP 就缺少这种流控制机制.
在这里插入图片描述
是的, 流控制是区分UDP 和TCP 的最重要的标志. 但若从 TCP 中除去流控制, 所剩内容也屈指可数. 也就是说, TCP 的生命在于流控制. 第5章讲过的"与对方套接字连接及断开连接过程"也是流控制的一部分.
在这里插入图片描述

UDP 内部工作原理

与TCP 不同, UDP 不会进行流控制. 接下来具体讨论UDP的作用, 如图 6-1 所示.
在这里插入图片描述
从图6-1中可以看出, IP的作用就是让离开主机B的UDP数据包准确传达到主机A. 但把UDP包最终交给主机A的某一UDP套机字的过程则是由UDP完成的. UDP最重要的作用就是根据端口号传到主机的数据包交付给最终的UDP套接字.

UDP 的高效使用

虽然貌似大部分网络编程都基于TCP实现, 但也有一些是基于UDP实现的. 接下来考虑何时使用UDP 更有效. 讲解前希望各位明白, UDP也具有一定的可靠性. 网络传输特性导致信息丢失频繁, 可若要传递压缩文件(发送1万个数据包时, 只要丢失一个就会产生问题), 则必须使用TCP, 因为压缩文件只要丢失一部分就很难解压. 但通过网络实时传输视频或音频时的情况有所不同. 对于多媒体数据而言, 丢失一部分也没有太大问题, 这只会引起短暂的画面抖动的情况, 对于多媒体数据而言, 丢失一部分没有太大问题, 这只会引起短暂的画面抖动, 或出细微的杂音. 但因为需要提供实时服务, 速度就成为非常重要的因素. 因此, 第5章的流控制就显得有些多余, 此时需要考虑使用 UDP. 但UDP并非每次都快于 TCP, TCP比 UDP 慢的原因有一下两点.
在这里插入图片描述
如果收发的数据量小但需要频繁连接时, UDP比TCP更高效. 有机会的话, 希望各位深入学习TCP/IP 协议的内容构造. C语言程序员懂计算机结构和操作系统知识就能写出更好的程序, 同样, 网络程序员若能深入理解TCP/IP协议则可大幅度 提高自身实力.

6.2 实现基于 UDP 的服务器端/客户端

接下来通过之前介绍的UDP理论实现真正的程序. 对于UDP 而言, 只要理解之前的内容, 实现并非难事.

UDP 中的服务器端和客户端没有连接

UDP 服务器端/客户端不像TCP那样在连接状态下交换数据, 因此与TCP不同, 无需经过连接过程. 也就是说, 不必调用TCP 连接过程中调用的listen函数和accept函数. UDP 中只有创建套接字过程和数据交换过程.

UDP 服务器端和客户端均只需1个套接字

TCP 中, 套接字之间应该是一对一的关系. 若要向10个客户端提供服务, 则除了守门的服务器套接字外, 还需要10个服务器套接字. 但在UDP 中, 不管是服务器端还是客户端都只需要一个套接字. 之前解析UDP原理是举例了信件的例子, 收发信件时使用的邮箱可以比如为UDP套接字. 只要附近有一个邮箱, 就可以通过它向任意地址寄出信件. 同样, 只需1个UDP套接字就 可以向任意主机传输数据, 如图6-2所示.
在这里插入图片描述
图6-2展示了一个UDP套接字与2个不同主机交换数据的过程. 也就是说, 只需1个UDP 套接字就能和多台主机通信.

基于 UDP 的数据 I/O 函数

创建好TCP套接字后, 传输数据时无需再添加地址信息. 因为TCP 套接字将保持与对方套接字的连接. 换言之, TCP 套机字知道目标地址信息. 但UDP套接字不会保持连接状态(UDP 套机字只有简单的邮箱功能), 因此每次传输数据都要添加目标地址信息. 这相当于寄信中填写地址. 接下来介绍填写地址并传输数据时调用的UDP相关函数.
在这里插入图片描述
上述函数与之前的TCP 输出函数最大区别在于, 此函数需要向它传递目标地址信息. 接下来介绍接收UDP数据的函数. UDP数据的发送端并不固定, 因此该函数定义为可接收发送端信息的形式, 也就是将同时返回UDP数据包中的发送信息.
在这里插入图片描述
编写UDP程序是最核心的部分就在于上述两个函数, 这也说明二者在UDP 数据传输中的地位.

基于 UDP 的回声服务器端/客户端

下面结合之前的内容实现回声服务器. 需要注意的是, UDP 不同于 TCP, 不存在请求连接和受理过程, 因此在某种意义上无法明确区分服务器端和客户端. 只是因其提供服务而称为服务器端, 希望各位不要误解.
服务器端:

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

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock;
    char message[BUF_SIZE];
    int str_len;
    socklen_t clnt_adr_sz;
    struct sockaddr_in serv_adr, clnt_adr;
    if (argc != 2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (serv_sock == -1)
    {
        error_handling("UDP socket creation error");
    }

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

    if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
    {
        error_handling("bind() error");
    }

    while(1)
    {
        clnt_adr_sz = sizeof(clnt_adr);
        str_len = recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz);
    }

    close(serv_sock);
    return 0;
}

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

客户端:

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

#define BUF_SIZE 30

void error_handlin(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    socklen_t adr_sz;

    struct sockaddr_in serv_adr, from_adr;

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

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {
        error_handlin("socket() error");
    }

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

    while(1)
    {
        fputs("Insert message(q to quit): ", stdout);
        fgets(message, sizeof(message), stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
        {
            break;
        }

        sendto(sock, message, strlen(message), 0, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
        adr_sz = sizeof(from_adr);
        str_len = recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&from_adr, &adr_sz);
        message[str_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}

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

客户端;’
在这里插入图片描述
服务器端:
在这里插入图片描述
运行过程中的顺序并不重要. 只需保证在调用sendto函数前, sendto函数的目标主机程序已经开始运行.

UDP 客户端套接字的地址分配

前面讲解了 UDP 服务器/客户端的实现方法. 但如果仔细观察 UDP 客户端会发现, 它缺少把IP和端口分配的过程给套接字的过程. TCP 客户端调用connect 函数自动完成此过程, 而UDP 中连能承担相同的函数语句都没有. 究竟在何处分配IP 和 端口号呢?

UDP 程序中, 调用sendto函数传输数据强应完成对套接字的地址分配工作, 因此调用bind函数. 当然, bind函数在TCP 程序中出现过, 但bind函数不区分和 UDP , 也就是说, 在UDP 程序中同样可以调用. 另外, 如果调用sendto 函数时尚未分配地址信息, 则在首次调用sendto函数是给出相应的套接字自动分配IP和端口. 而且此时分配的地址一直保留到程序结束为止, 因此也可用来与其他UDP 套接字进行数据交换. 当然, IP用主机IP, 端口号尚未使用的任意端口号.

综上所述, 调用sendto 函数是自动分配IP和端口号, 因此, UDP 客户端中通常无需额外的地址分配过程. 所以之前实例中省略了该过程, 这也是普遍的实现方式.

6.3 UDP 的数据传输特性和调用connect 函数

我们之前通过实例验证了TCP传输的数据不存在数据边界, 本节讲验证UDP 数据传输中存在数据边界. 最后讨论UDP 中connect 函数的调用, 以此结束UDP 相关讨论.

存在数据边界的 UDP 套接字

前面说过TCP 数据传输中不存在边界, 这表示"数据传输过程中调用I/O函数的次数不具有任何意义."

相反, UDP 是具有数据边界的协议, 传输中调用I/O函数的次数非常重要. 因此, 输入函数的调用次数和输出函数的调用次数完全一致, 这样才能保证接收全部已发数据. 例如, 调用3次输出函数发送的数据必须通过调用3次输入函数才能接收完. 下面通过简单实例进行验证.

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

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    struct sockaddr_in my_adr, your_adr;
    socklen_t adr_sz;
    int str_len, i;

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

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {
        error_handling("socket() error");
    }

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

    if (bind(sock, (struct sockaddr*)&my_adr, sizeof(my_adr)) == -1)
    {
        error_handling("bind() error");
    }

    for (i=0; i<3; i++)
    {
        sleep(5);
        adr_sz = sizeof(your_adr);
        str_len = recvfrom(sock, message, BUF_SIZE, 0, (struct sockaddr*)&your_adr, &adr_sz);
        printf("Message %d : %s \n", i+1, message);
    }

    close(sock);
    return 0;
}

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

(请到https://www.jiumodiary.com/)
下面本书
上述实例中需要各位特别留意的是第30行中的for语句. 首先在第32行中调用sleep函数, 使程序停顿时间等于传递的时间(以秒为单位)参数. 也就是说, 第30行的for循环中每隔5秒调用1次recvfrom 函数. 另外还添加了验证函数调用次数的语句. 稍后再讲解延迟执行程序的原因.

接下来的实例向之前的bound_host1.c 传输数据, 该实例共调用sendto函数3次以传输字符串数据.
客户端:

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

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char msg1[] = "Hi!";
    char msg2[] = "I'm another UDP host!";
    char msg3[] = "Nice to meet you";

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

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {
        error_handling("socket() error");
    }

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

    sendto(sock, msg1, sizeof(msg1), 0, (struct sockaddr*)&your_adr, sizeof(your_adr));
    sendto(sock, msg2, sizeof(msg2), 0, (struct sockaddr*)&your_adr, sizeof(your_adr));
    sendto(sock, msg3, sizeof(msg3), 0, (struct sockaddr*)&your_adr, sizeof(your_adr));

    close(sock);
    return 0;
}

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

运行结果:
客户端:
在这里插入图片描述
服务器端:
在这里插入图片描述
证明必须在UDP通信过程是I/O函数调用次数保存一致.
在这里插入图片描述

已连接(connected) UDP 套机字与未连接(unconnected) UDP 套接字

TCP 套接字中需注册待传输的目标IP和端口号, 而UDP中则无需注册. 因此, 通过sendto 函数传输数据的过程大致可分以下3阶段.
在这里插入图片描述
每次调用sendto函数时重复上述过程. 每次都变更目标地址. 因此可以重复利用统一UDP套接字向不同目标传输数据. 这种未注册目标地址信息的套机字称为未连接套机字, 反之, 注册了目标地址的套机字称为连接connected 套接字. 显然, UDP 套机字默认属于未连接套机字, 但UDP套接字在下述情况显得不太合理:
在这里插入图片描述
此时需要重复3次上述三阶段. 因此, 要与同一主机进行长时间通信时, 将UDP套机字变成已连接套接字会提高效率. 上述三个阶段中, 第一个和第三个阶段占整个通信过程接近1/3的时间, 缩短这部分时间将大大提高整体性能.

创建已连接UDP套接字

创建已连接UDP套接字的过程格外简单, 只需针对UDP套接字调用connect函数.
在这里插入图片描述
上述代码看似与TCP套接字创建过程一致, 但socket函数的第二个参数分明是SOCK_DGRAM. 也就是说, 创建的的确是UDP套接字. 当然, 针对UDP 套机字调用connect 函数并不意味着要与对方UDP套机字连接, 这只是向UDP 套机字注册目标IP和端口信息.

之后就与TCP 套机字一样, 每次调用sendto 函数时只需传递数据. 因为已经指定了收发对象, 所以可以使用sendto, recvfrom函数, 还可以使用write, read 函数进行通信.

下列实例将之前的uecho_client.c 程序改成基于已连接UDP 套机字的程序, 因此可以结合uecho_server.c 程序运行. 另外, 为便于说明, 未直接删除uecho_client.c 的I/O 函数, 而是添加了注释.

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

#define BUF_SIZE 30 

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    socklen_t adr_sz; /* 多余变量 */
    struct sockaddr_in serv_adr, from_adr; /* 不再需要from_adr! */

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

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {
        error_handling("socket() error");
    }

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

    connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

    while(1)
    {
        fputs("Insert message(q to Q): ", stdout);
        fgets(message, sizeof(message), stdin);
        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
        {
            break;
        }

        write(sock, message, strlen(message));
        str_len = read(sock, message, sizeof(message)-1);

        message[str_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}

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

没有运行结果和代码说明, 代码中用write read 函数代替sendto, recvfrom函数.

6.4 基于Windows 的实现

首先介绍Windows 平台下的sendto 函数好readfrom 函数, 实际上与 Linux 的函数没有太大区别, 但为了各位亲自确认这一点, 这里给出定义.
在这里插入图片描述
在这里插入图片描述
以上两个函数与 Linux 下的sendto, recvfrom 函数相比, 其参数个数, 顺序及含义完全相同, 故省略具体说明, 接下来实现Windows平台下的UDP 回声服务器端/客户端. 其中, 回声服务器端是利用已连接UDP 套接字实现的.
服务器端

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

#define BUF_SIZE 30

void ErrorHandling(const char* message);

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

	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");
	}

	servSock = socket(PF_INET, SOCK_DGRAM, 0);
	if (servSock == INVALID_SOCKET)
	{
		ErrorHandling("socket() error");
	}

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

	if (bind(servSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
	{
		ErrorHandling("bind() error");
	}

	while (1)
	{
		clntAdrSz = sizeof(clntAdr);
		strLen = recvfrom(servSock, message, BUF_SIZE, 0, (SOCKADDR*)&clntAdr, &clntAdrSz);
		sendto(servSock, message, strLen, 0, (SOCKADDR*)&clntAdr, sizeof(clntAdr));
	}

	closesocket(servSock);
	WSACleanup();
	return 0;
}

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

客户端:

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

#define BUF_SIZE 30

void ErrorHandling(const char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET sock;
	char message[BUF_SIZE];
	int strLen;

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

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

	sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (sock == INVALID_SOCKET)
	{
		ErrorHandling("socket() error");
	}

	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = inet_addr(argv[1]);
	servAdr.sin_port = htons(atoi(argv[2]));
	connect(sock, (SOCKADDR*)&servAdr, sizeof(servAdr));

	while (1)
	{
		fputs("Insert message(q to quit): ", stdout);
		fgets(message, sizeof(message), stdin);
		if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
		{
			break;
		}

		send(sock, message, strlen(message), 0);
		strLen = recv(sock, message, sizeof(message) - 1, 0);
		message[strLen] = 0;
		printf("Message from server: %s", message);
	}

	closesocket(sock);
	WSACleanup();
	return 0;
}

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

运行结果:
客户端:
在这里插入图片描述
上述客户端实例利用已连接UDP套接字进行输入输出, 因此用send, recv 函数替代sendto , recvfrom 函数. 此外也如实反映了已连接UDP 套接字的好处.

结语:

你可以在下面这个网站, 下载这本书
https://www.jiumodiary.com

时间: 2020-05-30

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些关于创建基于TCP服务器/客户的指导。 首先,您需要了解一些基本概念和步骤: 1. TCP/IP协议:TCP/IP协议是一种常用的网络通信协议,用于控制网络上的数据传输。它是由两个协议组成的:TCP(传输控制协议)和IP(网际协议)。 2. 服务器服务器是指在网络上提供服务的计算机程序。服务器通常需要创建一个socket,监听客户的连接请求,并接受来自客户的请求,处理请求并返回响应。 3. 客户客户是指连接到服务器的计算机程序。客户通常需要创建一个socket,连接到服务器,并发送请求,等待服务器响应。 接下来,我们可以按照以下步骤创建一个基于TCP服务器/客户,用于收发文件: 1. 创建服务器socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 绑定服务器socket:使用bind()函数将socket与服务器地址(IP地址和口号)绑定起来。 3. 监听客户连接请求:使用listen()函数启动服务器socket监听客户连接请求。 4. 接受客户连接请求:使用accept()函数接受来自客户的连接请求,并创建一个新的socket用于与客户通信。 5. 接收客户发送的文件:使用recv()函数从客户接收文件数据,并写入到服务器的文件中。 6. 发送响应给客户:使用send()函数向客户发送一个响应,表示文件传输完成。 7. 关闭服务器socket:使用close()函数关闭服务器socket。 客户的步骤如下: 1. 创建客户socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 连接服务器:使用connect()函数连接到服务器,指定服务器地址(IP地址和口号)。 3. 打开要发送的文件:使用fopen()函数打开要发送的文件。 4. 发送文件数据给服务器:使用send()函数将文件数据发送给服务器。 5. 接收服务器的响应:使用recv()函数接收服务器的响应,判断文件是否传输完成。 6. 关闭客户socket:使用close()函数关闭客户socket。 以上是基于TCP服务器/客户的基本步骤,您可以根据自己的需求进行修改和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值