day-03 基于TCP的服务器端/客户端

一.理解TCP和UDP

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种常见的传输层协议,用于在计算机网络中提供可靠的数据传输。

1.TCP:

  • 连接导向:TCP是一种面向连接的协议,通信双方在数据传输前需要先建立可靠的连接。
  • 可靠性:TCP提供可靠的数据传输,通过使用序列号、确认应答、超时重传等机制来确保数据的完整性和可靠性。
  • 流量控制与拥塞控制:TCP通过流量控制和拥塞控制来调节发送端的数据发送速率,以避免网络拥堵或丢包现象。
  • 有序性:TCP保证数据按照发送顺序到达目标端,不会发生乱序问题。
  • 面向字节流:TCP将数据视为连续的字节流进行传输,没有消息边界的概念。

TCP适用于对数据完整性和可靠性要求较高的应用场景,如文件传输、电子邮件、网页浏览等。

2.UDP:

  • 无连接:UDP是一种无连接的协议,通信双方在数据传输时不需要先建立连接。
  • 不可靠性:相比TCP,UDP不提供数据的可靠性保证,数据可能会丢失、乱序或重复。
  • 无拥塞控制:UDP不对数据发送速率进行调节,因此可能会造成网络拥堵和丢包现象。
  • 无序性:UDP没有保证数据按照发送顺序到达目标端的机制,数据可能会乱序到达。
  • 面向报文:UDP将数据视为独立的报文进行传输,保留了消息的边界。

UDP适用于对实时性要求较高、数据丢失可容忍或应用自身提供可靠性机制的应用场景,如音频/视频流传输、在线游戏等。

需要根据具体应用需求选择使用TCP还是UDP。TCP提供可靠的有序数据传输,适合对数据完整性要求较高的场景;而UDP提供了更低的延迟和更高的实时性,适用于对实时性要求较高、数据丢失可容忍的场景。

TCP/IP协议栈

二.实现基于TCP的服务器端/客户端

1.TCP服务器端的默认函数调用顺序

 2.TCP客户端的默认函数调用顺序

3.基于TCP的服务器端/客户端函数调用关系

 4.实现迭代回声服务器端/客户端

(VM ubuntu编译Cpp:g++ -std=c++11  文件名 -o  生成文件名)

(1)echo_server.cpp
  • g++ -std=c++11 echo_server.cpp -o eserver
  • ./eserver 9190
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024

void error_handling(const char *message);

int main(int argc, char *argv[]) {
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];
    int str_len, i;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " <port>" << std::endl;
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_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 = 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");

    if (listen(serv_sock, 5) == -1)
        error_handling("listen() error");

    clnt_adr_sz = sizeof(clnt_adr);

    for (i = 0; i < 5; i++) {
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        if (clnt_sock == -1)
            error_handling("accept() error");
        else
            std::cout << "Connected client " << i+1 << std::endl;

        while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
            write(clnt_sock, message, str_len);
        
        close(clnt_sock);
    }

    close(serv_sock);
    return 0;
}

void error_handling(const char *message) {
    std::cout << message << std::endl;
    exit(1);
}
(2)echo_client.cpp
  • g++ -std=c++11 echo_client.cpp -o eclient
  • ./eclient 127.0.0.1 9190
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024

void error_handling(const char *message);

int main(int argc, char *argv[]) {
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;

    if (argc != 3) {
        std::cout << "Usage: " << argv[0] << " <IP> <port>" << std::endl;
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 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]));

    if (connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)//调用conndect函数
        error_handling("connect() error!");
    else
        std::cout << "connected..........." << std::endl;

    while (true) {
        std::cout << "Input message (Q to quit): ";
        std::cin.getline(message, BUF_SIZE);

        if (strcmp(message, "q") == 0 || strcmp(message, "Q") == 0)
            break;

        write(sock, message, strlen(message));
        str_len = read(sock, message, BUF_SIZE-1);
        message[str_len] = '\0';
        std::cout << "Message from server: " << message << std::endl;
    }

    close(sock);//调用close函数向相应套接字发送EOF(EOF意味着中断连接)
    return 0;
}

void error_handling(const char *message) {
    std::cerr << message << std::endl;
    exit(1);
}
存在问题:
  • 如果数据量太大,操作系统可能把数据分成多个数据包发送到客户端(但是服务器端每次接收读取之后只调用一次 write() 函数。
  • 另外,客户端有可能在尚未收到全部数据包时就调用 read()函数,导致回声不完整。

三.总结(一)

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

TCP/IP协议栈是一个网络通信的基本模型,它由四层协议组成,分别是应用层、传输层、网络层和链路层。

  1. 应用层:应用层提供了用户与网络之间的接口,负责处理特定的应用程序数据。常见的应用层协议有HTTP、FTP、SMTP等。

  2. 传输层:传输层主要负责在网络中传输数据。其中最重要的两个协议是TCP(传输控制协议)和UDP(用户数据报协议)。

    • TCP套接字经过传输层。TCP是一种面向连接的协议,提供可靠的数据传输。它通过建立连接、数据分段、流量控制、拥塞控制等机制来保证数据的可靠性和顺序性。TCP套接字在传输层使用IP地址和端口号进行标识。

    • UDP套接字也经过传输层。UDP是一种无连接的协议,不提供可靠性保证。它将数据打包成数据报发送,不关心数据是否能够到达目标地址。UDP套接字同样使用IP地址和端口号进行标识。

  3. 网络层(IP层):网络层负责将数据从源主机传输到目标主机,它使用IP协议进行数据包的路由选择和转发。IP协议为数据包分配唯一的IP地址。

  4. 链路层:链路层是网络通信的物理层和数据链路层接口,负责将网络层传输的数据转换成比特流进行物理传输。它包括了物理寻址、帧封装和错误检测等功能。

  • IP层和链路层之间的关系是协同工作。IP层将数据封装成IP数据包,在传输过程中会添加源IP地址和目标IP地址,然后将数据包交给链路层进行物理传输。链路层收到IP数据包后,会添加自己的帧头和帧尾信息,并将数据包转换成比特流发送。接收端的链路层会将接收到的比特流重新组装成帧,并去除链路层的头部和尾部信息,然后将数据包交给IP层进行进一步处理。
  • 总结来说,IP层负责寻址、路由选择和数据包的传输控制,而链路层负责物理传输和错误检测。它们共同协作,完成了数据从源主机到目标主机的传输过程。

所以,TCP和UDP套接字在协议栈的层级结构上并无差异,都经过传输层。它们的主要区别在于TCP提供面向连接的可靠传输,而UDP提供无连接的不可靠传输。根据应用场景的需要,可以选择使用TCP或UDP来进行数据传输。

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

TCP/IP协议栈被分成多层的设计是出于以下几个原因,其中也与开放式系统相关:

  1. 模块化设计:将TCP/IP协议栈分成多层可以实现模块化的设计,每一层都有明确定义的功能和责任。这样可以提高系统的可维护性和可扩展性。如果需要对某一层进行修改或替换,只需关注特定层而无需改动其他层。

  2. 逻辑分离:将网络通信过程划分为不同的层级,可以实现逻辑上的分离。每一层专注于自身的任务,不需要关心其他层的具体实现。这种分离使得各层之间的接口定义清晰,降低了系统的复杂性。

  3. 标准化和互操作性:将TCP/IP协议栈分成多层使得每一层的功能和协议得到标准化。这样不同厂商、不同操作系统的设备都可以按照相同的标准实现对应的层,从而实现互操作性。这种开放式的设计使得不同设备和系统能够无缝地进行通信。

  4. 灵活性和可定制性:通过分层设计,可以根据具体需求选择性地使用不同层提供的功能。例如,某些应用场景可能只需要传输层和网络层的功能,可以选择性地使用这些层,而不需要实现其他层。这种灵活性和可定制性使得TCP/IP协议栈适应各种不同的网络和应用需求。

综上所述,将TCP/IP协议栈分成多层结构是为了实现模块化设计、逻辑分离、标准化和互操作性,以及提供灵活性和可定制性。这样的设计使得开放式系统中的不同设备和系统能够相互通信,并且能够根据具体需求进行定制和扩展。

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

服务器端调用 listen 函数后,客户端可以调用 connect 函数。
在TCP通信中,服务器端需要先调用listen函数来监听指定的端口,以便接受客户端的连接请求。listen函数使得服务器端处于监听状态,等待客户端连接。
一旦服务器端调用了listen函数,客户端就可以通过调用connect函数向服务器端发送连接请求。connect函数会建立与服务器端的连接,并进行三次握手来确保连接的可靠性。
所以,客户端需要在服务器端调用listen函数之后才能调用connect函数,以发起与服务器的连接。

4.什么时候创建连接请求等待队列,有何作用,和accept有什么关系?

        连接请求等待队列是在服务器端调用listen函数后创建的它的作用是用于存储尚未被服务器端accept函数接受的客户端连接请求。

        当服务器端调用listen函数后,它会开始监听指定的端口,并将该端口设置为监听状态,等待客户端的连接请求。如果有多个客户端同时向服务器端发送连接请求,而服务器端只能逐个处理这些请求,这时就需要使用连接请求等待队列

        连接请求等待队列允许服务器端在处理一个连接请求时,将其他连接请求暂时保存起来,以便后续处理。通过使用连接请求等待队列,可以确保当服务器端正在处理一个连接请求时,其他连接请求不会被丢失。

        当服务器端调用accept函数时,它从连接请求等待队列中取出一个连接请求进行处理。accept函数会建立与客户端的连接,并返回一个新的套接字,服务器端可通过这个套接字与对应的客户端进行通信。

        因此,连接请求等待队列与accept函数密切相关。连接请求等待队列存储了尚未被接受的客户端连接请求,而accept函数则负责从队列中取出连接请求并建立连接。通过连接请求等待队列和accept函数的配合使用,服务器端能够管理和处理多个客户端的连接请求。

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

        在客户端中,不需要调用bind()函数来分配函数地址,是因为客户端一般不需要绑定特定的IP地址和端口号。客户端的套接字可以通过操作系统自动分配可用的临时端口,并自动关联客户端的IP地址。

        当客户端创建套接字时,操作系统会自动选择一个可用的本地端口,并将其与套接字进行关联。这个过程通常称为"隐式绑定"(implicit binding)。

        隐式绑定可以确保客户端使用的本地端口是唯一的,并且不会与其他应用程序冲突。对于大多数客户端应用程序而言,这种自动分配是足够的,无需显式调用bind()函数。

        需要注意的是,如果你想要在客户端使用特定的本地IP地址和端口号,或者需要进行端口重用等特殊操作,那么你可能需要调用bind()函数来手动指定这些参数。但一般情况下,客户端并不需要手动调用bind()函数来分配函数地址。

四.回声客户端的功能实现(加减乘)----应用层协议设计

        要求,服务器端从客户端获得多个数字和运算符信息,然后把结果传回客户端。

1.op_server.cpp

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

#define BUF_SIZE 1024
#define OPSZ 4

void error_handling(const char *message);
int calculate(int opnum,int opnds[],char oprator);

int main(int argc,char *argv[]){
	int serv_sock,clnt_sock;
	char opinfo[BUF_SIZE];
	int result,opnd_cnt,i;
	int recv_cnt,recv_len;
	struct sockaddr_in serv_adr,clnt_adr;
	socklen_t clnt_adr_sz;

	if(argc!=2){
		std::cout<<"Usage :"<<argv[0]<<"<port>"<<std::endl;
		exit(1);
	}

	serv_sock=socket(PF_INET,SOCK_STREAM,0);
	if(serv_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=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");

	if(listen(serv_sock,5)==-1)
		error_handling("listen() error");

	clnt_adr_sz=sizeof(clnt_adr);

	for(i=0;i<5;i++){
		opnd_cnt=0;//运算个数

		clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
		read(clnt_sock,&opnd_cnt,1);

		recv_len=0;
		while((opnd_cnt*OPSZ+1)>recv_len){//读取运算值
			recv_cnt=read(clnt_sock,&opinfo[recv_len],BUF_SIZE-1);
			recv_len+=recv_cnt;
		}
		result+=calculate(opnd_cnt,(int*)opinfo,opinfo[recv_len-1]);
		write(clnt_sock,(char*)&result,sizeof(result));
		close(clnt_sock);//当前客户端结束后关闭
	}
	close(serv_sock);
	return 0;
}

int calculate(int opnum,int opnds[],char op){//功能函数
	int result=opnds[0],i;
	switch(op){
		case '+':
			for(i=1;i<opnum;i++) result+=opnds[i];
			break;
		case '-':
			for(i=1;i<opnum;i++) result-=opnds[i];
			break;
		case '*':
			for(i=1;i<opnum;i++) result*=opnds[i];
			break;
	}
	return result;
}


void error_handling(const char*message){
	std::cout<<message<<std::endl;
	exit(1);
}

2.op_client.cpp

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

#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4

void error_handling(const char *message);

int main(int argc,char *argv[]){
	int sock;
	char opmsg[BUF_SIZE];
	int result,opnd_cnt,i;
	struct sockaddr_in serv_adr;
	if(argc!=3){
		std::cout<<"Usage:"<<argv[0]<<"<IP> <port>"<<std::endl;
		exit(1);
	}

	sock=socket(PF_INET,SOCK_STREAM,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]));

	if(connect(sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr))==-1)
		error_handling("connect() error");
	else
		std::cout<<"connect ......"<<std::endl;

	std::cout<<"Operand count: ";
	std::cin>>opnd_cnt;//获取运算个数

	opmsg[0]=(char)opnd_cnt;

	for(i=0;i<opnd_cnt;i++){
		std::cout<<"Operand "<<i+1<<": ";
		std::cin>>*(int*)&opmsg[i*OPSZ+1];
		std::cin.ignore();//相当于换行
	}
	std::cout<<"Opreator: ";
	std::cin>>opmsg[opnd_cnt*OPSZ+1];//输入运算符
	
	write(sock,opmsg,opnd_cnt*OPSZ+2);//传输给服务器端
	read(sock,&result,RLT_SIZE);

	std::cout<<"Operation result: "<<result<<std::endl;
	close(sock);
	return 0;
}

void error_handling(const char *message){
	std::cout<<message<<std::endl;
	exit(1);
}

五.TCP原理

(一)TCP套接字中的 I/O 缓冲

        TCP套接字的数据收发无边界。为什么这么说呢?因为 write() 函数调用后并非立即传输数据,read() 函数调用后也并非马上接收数据。如下图所示:

 缓冲特性:
  • I/O 缓冲在每个TCP套接字中单独存在
  • I/O 缓冲在创建套接字时自动生成
  • 即使关闭套接字也会继续传递输出缓冲中遗留的数据
  • 关闭套接字将丢失输入缓冲中的数据

注意:TCP有流量控制机制,所以不存在缓冲溢出

(二)TCP内部工作原理(套接字创建到关闭)

1.与对方套接字的连接(三次握手)

        套接字是全双工(Full-duplex)方式工作的,也就是可以双向地传递数据。因此,收发数据之前需要做一些准备。

  • 第一次握手:服务端知道客户端发送能力没问题
  • 第二次握手:客户端知道服务端的接收和发送能力没问题
  • 第三次握手:服务端知道客户端的接收能力没问题

2.与对方主机的数据交换

        通过三次握手过程完成了数据交换准备,下面开始正式收发数据。

         在数据包消失情况中,过了一段时间后,主机A没有收到对于SEQ:1301的ACK确认,因此在启动计时器等待ACK应答,若相应计时器发生超时(Time-out!)则重传。

3.断开与套接字的连接(四次挥手)

        TCP套接字结束的过程也非常优雅。如果对方还有数据需要传输,直接打断连接会出问题。所以断开连接需要双方协商。

         数据包内的 FIN 表示断开连接。也就是说,双方各发送一次 FIN 消息后断开连接。此过程经历四个阶段。图中主机B向主机A发送了两次ACK:501,其实第二次 FIN 数据包中的 ACK:5001 只是因为收到(主机A的)ACK消息后未接收数据而重传的。

六.总结(二)

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

TCP套接字连接的三次握手过程如下:

第一次握手:

  • 客户端向服务器发送一个SYN(同步)报文,其中包含一个随机生成的初始序列号(ISN)。
  • 这个SYN报文的目的是告诉服务器,客户端希望建立连接,并且客户端的初始序列号是多少。

第二次握手:

  • 服务器收到客户端的SYN报文后,会发送一个SYN-ACK(同步-确认)报文给客户端。
  • 这个SYN-ACK报文中,服务器会确认收到了客户端的连接请求,并且也会生成一个自己的初始序列号(ISN)。
  • 同时,服务器也会将自己期望接收到的下一个序列号(Next Sequence Number)设置为客户端的初始序列号加一。

第三次握手:

  • 客户端收到服务器的SYN-ACK报文后,会发送一个ACK(确认)报文给服务器。
  • 这个ACK报文中,客户端会确认收到了服务器的确认,并且会将自己期望接收到的下一个序列号设置为服务器的初始序列号加一。

在这个三次握手的过程中,每次收发的数据内容如下:

第一次握手:

  • 客户端发送的数据内容是一个SYN报文,其中包含客户端的初始序列号(ISN)。

第二次握手:

  • 服务器发送的数据内容是一个SYN-ACK报文,其中包含服务器的初始序列号(ISN)和期望接收到的下一个序列号(Next Sequence Number)。

第三次握手:

  • 客户端发送的数据内容是一个ACK报文,其中确认了服务器的确认,并且设置了客户端期望接收到的下一个序列号。

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

        SEQ顺序标识符是给信息编号。ACK是用于回复带有编号的信息。也就是说,每次传输信息时,都同时发送SEQ标识,而受到信息的主机应以SEQ信息为基础回复发送信息的主机。通过这种机制,传输数据的主机就可以确认数据是否被正确接收。在传输失败时,可以重新传送。

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

        当write函数被调用时,数据就会向端口的输出缓冲区移动。然后经过网络传输传输到对方主机套接字的输入缓冲。这样,输入缓冲中存储的数据通过read函数的响应来读取。

​​​​​​​​​​​​​​

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

        对方主机会把输入缓冲中可存储的数据大小传送给要传输数据的数据(本方)。因此,在剩余空间为50字节的情况,即使要求传送70字节的数据,也不能传输50字节以上,剩余的部分保存在传输方的输出缓冲中,等待对方主机的输入缓冲出现空间。而且,这种交换缓冲多余空间信息的协议被称为滑动窗口协议。

5.创建收发文件的服务器端/客户端。

  • 客户端接收用户输入的传输文件名
  • 客户端请求服务器端传输该文件名所指文件
  • 如果文件存在,服务器端九江其发给客户端,反之,则断开连接。
file_server.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

void error_handling(const char *message);

int main(int argc, char *argv[]) {
    int serv_sd, clnt_sd;
    FILE *fp;
    char buf[BUF_SIZE];
    char file_name[BUF_SIZE];
    int read_cnt;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " <port>" << std::endl;
        exit(1);
    }

    serv_sd = socket(PF_INET, SOCK_STREAM, 0);

    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]));

    bind(serv_sd, (struct sockaddr *) &serv_adr, sizeof(serv_adr));
    listen(serv_sd, 5);

    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sd = accept(serv_sd, (struct sockaddr *) &clnt_adr, &clnt_adr_sz);

    read(clnt_sd, file_name, BUF_SIZE);
    fp = fopen(file_name, "rb");
    if (fp != NULL) {
        while (1) {
            read_cnt = fread((void *) buf, 1, BUF_SIZE, fp);
            if (read_cnt < BUF_SIZE) {
                write(clnt_sd, buf, read_cnt);
                break;
            }
            write(clnt_sd, buf, BUF_SIZE);
        }
    }

    fclose(fp);
    close(clnt_sd);
    close(serv_sd);
    return 0;
}

void error_handling(const char *message) {
    std::cerr << message << std::endl;
    exit(1);
}
file_client.cpp
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

void error_handling(const char *message);

int main(int argc, char *argv[]) {
    int sd;
    FILE *fp;

    char buf[BUF_SIZE];
    char file_name[BUF_SIZE];
    int read_cnt;
    struct sockaddr_in serv_adr;
    if (argc != 3) {
        std::cout << "Usage: " << argv[0] << " <IP> <port>" << std::endl;
        exit(1);
    }

    std::cout << "Input file name: ";
    std::cin >> file_name;
    fp = fopen(file_name, "wb");

    sd = socket(PF_INET, SOCK_STREAM, 0);
    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(sd, (struct sockaddr *) &serv_adr, sizeof(serv_adr));
    write(sd, file_name, strlen(file_name) + 1);

    while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
        fwrite((void *) buf, 1, read_cnt, fp);

    fclose(fp);
    close(sd);
    return 0;
}

void error_handling(const char *message) {
    std::cerr << message << std::endl;
    exit(1);
}

问题:无法真正的传输文件数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/97854.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(笔记六)利用opencv进行图像滤波

&#xff08;1&#xff09;自定义卷积核图像滤波 import numpy as np import matplotlib.pyplot as plt import cv2 as cvimg_path r"D:\data\test6-6.png" img cv.imread(img_path)# 图像滤波 ker np.ones((6, 6), np.float32)/36 # 构建滤波器&#xff08;卷积…

每天刷题五道RHCSA/1-5道(RedHat8.2)

1.第一题配置网络 nmcli con show #查看⽹卡信息 ​ nmcli con mod Wired connection 1 ipv4.method manual ipv4.addresses 172.25.250.100/24 ipv4.gateway 172.25.250.254 ipv4.dns 172.25.250.254 #配置ip信息&#xff0c;中间⽤空格隔开 ​ nmcli con up Wired connection…

HCIP学习--扩展知识点

端口镜像-SPAN 抓包软件只能抓取经过本地的网卡的流量 也就是抓取流量只能抓取本设备的流量 [r1]observe-port interface GigabitEthernet 0/0/2 定义一个SPAN的会话&#xff0c;然后定义监控接口&#xff08;也就是你要用的接口&#xff0c;你连接这个接口来对其他接口抓包…

PXE批量装机

目录 前言 一、交互式 &#xff08;一&#xff09;、搭建环境 &#xff08;二&#xff09;、配置dhcp服务 &#xff08;三&#xff09;、FTP服务 &#xff08;四&#xff09;、配置TFTP服务 &#xff08;五&#xff09;、准备pxelinx.0文件、引导文件、内核文件 &#…

Java版电子招投标管理系统源码-电子招投标认证服务平台-权威认证

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

汽车售后接待vr虚拟仿真实操演练作为岗位培训的重要工具和手段

汽车虚拟仿真教学软件是一种基于虚拟现实技术的教学辅助工具。它能够模拟真实的汽车环境和操作场景&#xff0c;让学生能够通过虚拟仿真来学习和实践汽车相关知识和技能。与传统的教学方式相比&#xff0c;汽车虚拟仿真教学软件具有更高的视觉沉浸感和互动性&#xff0c;能够更…

YOLOv5 如何计算并打印 FPS

文章用于学习记录 YOLO v5 FPS计算方法修改对应自己数据集的 yaml 文件以及训练好的 pt 文件以及batch-size1, FPS 1000ms/(0.311.91.0)pre-process&#xff1a;图像预处理时间&#xff0c;包括图像保持长宽比缩放和padding填充&#xff0c;通道变换&#xff08;HWC->CHW&a…

软技能的重要性:在面试中展示团队合作与沟通能力

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【base64】JavaScriptuniapp 将图片转为base64并展示

Base64是一种用于编码二进制数据的方法&#xff0c;它将二进制数据转换为文本字符串。它的主要目的是在网络传输或存储过程中&#xff0c;通过将二进制数据转换为可打印字符的形式进行传输 JavaScript 压缩图片 <html><body><script src"https://code.j…

C#---第二十:不同类型方法的执行顺序(new / virtual / common / override)

本文介绍不同类型的方法&#xff0c;在代码中的执行顺序问题&#xff1a; 构造方法普通方法&#xff08;暂用common代替&#xff09;、虚方法&#xff08;Virtual修饰&#xff09;、New方法&#xff08;new修饰&#xff09;三个优先级相同overide方法&#xff08;会替换virtual…

Crontab定时任务运行Docker容器(Ubuntu 20)

对于一些离线预测任务&#xff0c;或者D1天的预测任务&#xff0c;可以简单地采用Crontab做定时调用项目代码运行项目 Crontab简介&#xff1a; Linux crontab命令常见于Unix和类Unix的操作系统之中&#xff0c;用于设置周期性被执行的指令。该命令从标准输入设备读取指令&…

重磅!GPT-3.5 Turbo推出微调功能,可以打造专属ChatGPT啦!

昨天的追友套路竟然没人看&#xff0c;太可惜了。虽然我知道大家都想快速成功&#xff0c;而且快速成功的秘诀很简单&#xff1a;MONEY&#xff01;&#xff08;钱&#xff09; 可是大伙缺的反而正是这个。 大清早&#xff0c;刷X&#xff0c;看到了一则推送。 OpenAI宣布推出G…

1.网络空间搜素引擎

网络空间搜素引擎 https://cybermap.kaspersky.com/cn 世界所以带有ip的网络设备互联组成的空间叫做网络空间 地址 &#xff1a;shodan.io 简介 &#xff1a; 这句话还是有点东西得 。 区别&#xff1a; 平常得搜素引擎主要搜网页&#xff0c;shadan可以搜所以带有ip地址…

Jmter生成MD5 jmter使用md5 jmter使用自定义参数 jmter生成自定义参数 jmter编写java代码

Jmter生成MD5 jmter使用md5 jmter使用自定义参数 jmter生成自定义参数 jmter编写java代码 1、创建一个线程组2、创建线程组 http请求3、在 http请求添加前置处理器(BeanShell)4、请求测试 是否生效4.1 GET请求4.2 POST请求 1、创建一个线程组 2、创建线程组 http请求 在线程组…

在外SSH远程连接macOS服务器【cpolar内网穿透】

文章目录 前言1. macOS打开远程登录2. 局域网内测试ssh远程3. 公网ssh远程连接macOS3.1 macOS安装配置cpolar3.2 获取ssh隧道公网地址3.3 测试公网ssh远程连接macOS 4. 配置公网固定TCP地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址 5. 使用固定TCP端口地址ssh远程 …

Approaching (Almost) Any Machine Learning Problem中译版

前言 Abhishek Thakur&#xff0c;很多kaggler对他都非常熟悉&#xff0c;2017年&#xff0c;他在 Linkedin 发表了一篇名为Approaching (Almost) Any Machine Learning Problem的文章&#xff0c;介绍他建立的一个自动的机器学习框架&#xff0c;几乎可以解决任何机器学习问题…

AI 绘画Stable Diffusion 研究(十五)SD Embedding详解

大家好&#xff0c;我是风雨无阻。 本期内容&#xff1a; Embedding是什么&#xff1f;Embedding有什么作用&#xff1f;Embedding如何下载安装&#xff1f;如何使用Embedding&#xff1f; 大家还记得 AI 绘画Stable Diffusion 研究&#xff08;七&#xff09; 一文读懂 Stab…

5分钟看懂物料清单(BOM)的用途、类型及管理

管理物料可以提高制造和供应链流程的效率&#xff0c;例如生产、物流、调度、产品成本核算和库存计划。企业通常使用物料清单作为制造产品的组件、材料和流程的中央记录。 物料清单&#xff08;BOM&#xff09;是构建、制造或维修产品或服务所需的原材料、组件和说明的详细列表…

Linux线程控制

目录 一、线程的简单控制 1.多线程并行 2.线程结束 3.线程等待 &#xff08;1&#xff09;系统调用 &#xff08;2&#xff09;返回值 4.线程取消 5.线程分离 二、C多线程小组件 三、线程库TCB 1.tid 2.局部储存 一、线程的简单控制 1.多线程并行 我们之前学过pt…

代码随想录笔记--哈希表篇

目录 1--有效的字母异位词 2--两个数组的交集 3--两数之和 4--四数相加II 5--三数之和 6--四数之和 1--有效的字母异位词 利用哈希表存储每个字母的出现次数&#xff0c;比较两个字符串各个字母出现次数是否相等即可&#xff1b; #include <iostream> #include <…