6 网络

1、概念

计算机网络是实现资源共享和信息传递的计算机系统
ISO/OSI网络协议模型
在这里插入图片描述

TCP/IP协议
在这里插入图片描述

·应用程序负责组织的通常都是与业务相关的数据内容,而要想把这些数据内容通过网络发送出去,就要将其自上向下地压入协议栈,每经历一个协议层,就会对数据做一层封包,每一层输出的封包都是下一层输入的内容,消息包沿着协议栈的运动形成了消息流。
·当从网络上接收数据时,过程刚好相反,消息包自下向上地流经协议栈,每经历一个协议层,就会对输入的数据解一层封包,经过层层解包以后,应用程序最终得到的将只是与业务相关的数据内容

2 IP地址

  • 什么是P地址?
    IP地址,是IP协议提供的一种统一的地址格式,为互联网上的每个网络和每台主机分配一个逻辑地址,借以消除物理地址所带来的差异性
  • IP地址如何表示?
    在计算机内部,IP地址用一个32位无符号整数表示,如:0x01020304。
    人们更习惯使用点分十进制字符串表示,如:1.2.3.4。字符串形式的从左到右,对应整数形式的从高字节到低字节。注意这里所说的高低指的是数位高低而非地址高低
  • 什么是IP地址分级?
    A级地址:以0为首的8位网络地址+24位本地地址
    B级地址:以10为首的16位网络地址+16位本地地址
    C级地址:以110为首的24位网络地址+8位本地地址
    D级地址:以1110为首的32位多播地址
    例如:某台计算机的IP地址:192.168.182.48,写成整数形式:11000000 10101000 10110110 00110000
    代表的是C级地址,网络地址:192.168.182.0,本地地址:48
  • 子网掩码
    • 借助子网掩码可以快速帮助我们区定IP地址的网络地址和本地地址
      • 以IP地址:192.168.182.48,子网掩码:255.255.255.0为例
        网络地址=IP地址&子网掩码
        192.168.182.48 & 255.255.255.0 = 192.168.182.0
        本地地址=IP地址&~子网掩码
        192.168.182.48 & 0.0.0.255 = 0.0.0.48

3、套接字

在这里插入图片描述

套接字代表着主机的通信能力
套接字接口库规定在网络传输过程中采用网络字节序,也就是大端字节序,
而本机数据可能是小短字节序
- 小端字节序:数据的低位存放在低地址
- 大端字节序:数据的低位存放在高地址

4、TCP协议

TCP提供客户机与服务器的连接

4.1 TCP协议的基本特征

一个完整TCP通信过程需要依次经历三个阶段

  • 首先,客户机必须建立与服务器的连接
  • 然后,凭借已建立好的连接,通信双方相互交换数据
  • 最后,客户机与服务器双双终止连接,结束通信过程
    TCP保证数据传输的可靠性
  • TCP的协议栈底层在向另一端发送数据时,会要求对方在一个给定的时间窗口内返回确认。如果超过了这个时间窗口仍没有收到确认,则TCP会重传数据并等待更长的时间。只有在数次重传均告失败以后,TCP才会最终放弃。TCP含有用于动态估算数据往返时间(Round-Trip Time,RTT)的算法,因此它知道等待一个确认需要多长时间
    TCP保证数据传输的有序性
  • TCP的协议栈底层在向另一端发送数据时,会为所发送数据的每个字节指定一个序列号。即使这些数据字节没有能够按照发送时的顺序到达接收方,接收方的TCP也可以根据它们的序列号重新排序,再把最后的结果交给应用程序
    TCP是全双工的
  • 在给定的连接上,应用程序在任何时候都既可以发送数据也可以接收数据。因此,TCP必须跟踪每个方向上数据流的状态信息,如序列号和通告窗口的大小

4.2 建立连接

三路握手

  • 客户机的TCP协议栈向服务器发送一个SYN分节,告知对方自己将在连接中发送数据的初始序列号
  • 服务器的TCP协议栈向客户机发送一个单个分节,其中不仅包括对客户机SYN分节的ACK应答,还包含服务器自己的SYN分节,以告知对方自己在同一连接中发送数据的初始序列号
  • 客户机的TCP协议栈向服务返回ACK应答,以表示对服务器所发SYN的确认
    在这里插入图片描述

tcp包头结构,有20个字节,其中2个字节的源端口、2个字节的目的端口、4字节的序号、4字节的确认号等等共计20个字节的数据
在这里插入图片描述

三次握手的解释:
① 客户机首先向服务器发送数据包,数据包中的SYN的比特位是1,并且在序号位置指定一个数字,假定为100
② 服务器收到客户机发来的数据包,其中SYN的比特位是1,那么服务器就会应答一个数据包,数据包里的ACK比特位置1,代表一种应答,并且在确认号这里会将收到的序号+1,这里是101,代表服务器真的收到了客户机的数据包,然后将序号指定一个数字假定是200,并且将SYN比特位置1,一起发送给客户机
③ 客户机收到服务器发来的数据包,首先检查数据包中的ACK和确认号是否正确,然后会向服务器应答一个数据包,其中ACK比特位置1,代表是对服务器的应答,确认号填写收到的序号+1,这里是201,发送给服务器,代表客户机真的收到了服务器的数据包
④ 服务器收到数据包,检查ACK和确认号,至此连接建立

接口## 4.3 交换数据

  • 一旦连接建立,客户机即可构造请求包并发往服务器,服务器接收并处理来自客户机的请求包,构造响应包
  • 服务器向客户机发送响应包,同时捎带对客户机请求包的ACK应答。
  • 客户机接收来自服务器的响应包,同时向对方发送ACK应答
    在这里插入图片描述

4.4 终止连接

四次挥手

  • 客户机或者服务器主动关闭连接,TCP协议栈向对方发送FIN分节,表示数据通信结束。如果此时尚有数据滞留于发送缓冲区中,则FIN分节跟在所有未发送数据之后
  • 接收到FIN分节的另一端执行被动关闭,-方面通过TCP协议栈向对方发送ACK应答,另一方面向应用程序传递文件结束符
  • 一段时间以后,方才接收到FIN分节的进程关闭自己的连接,同时通过TCP协议栈向对方发送FIN分节
  • 对方在收到FIN分节后发送ACK应答
    四次挥手的解释
    ① 客户机关闭套接字之后,客户机首先向服务器发送一个数据包,其中FIN比特位置1
    ② 服务器收到客户机发来的数据包,其中FIN比特位为1,向客户机回传一个应答,其中ACK置1
    ③ 服务器关闭对应的套接字操作之后,服务器向客户机发送一个数据包,其中FIN比特位置1
    ④ 客户机收到服务器发来的数据包,其中的FIN比特位为1,向服务器回传一个应答,其中ACK置1
    ⑤ 至此连接断开,通信终止

4.5 编程模型

在这里插入图片描述

  • 相关函数
    1:socket 创建套接字
// 头文件 sys/socket.h
int socket(int domain,int type,int protocol);
- 功能:创建套接字
- 参数:
	- domain:通信域,协议族,可取以下值:
		PF_LOCAL/PF_UNIX - 本地套接字,进程间通信
		PF_INET - 基于IPV4的网络通信
		PF_INET6 - 基于IPv6的网络通信
		PF_PACKET - 基于底层包的网络通信
	- type:套接字类型,可取以下值:
		SOCK_STREAM - 流式套接字,基于TCP协议
		SOCK_DGRAM - 数据报套接字,基于UDP协议
		SOCK_RAW - 原始套接字,工作在传输层以下
	- protocol:特殊协议,对于流式和数据报套接字而言,只能取0
- 返回值:成功返回表示套接字对象的文件描述符,失败返回-1

2:相关结构体

/*套接字接口库通过地址结构定位一个通信主体,可以是一个文件,可以是一台远程主机,也可以是执行者自己 */
// 基本地址结构,本身没有实际意义,仅用于泛型化参数
struct sockaddr{
	sa_family_t sa_family; // 地址族
	char sa_data\[14\]; // 地址值
}
// 本地地址结构,用于AF_LOCAL/AF_UNIX域的本地通信
struct sockaddr_un{
	sa_family_t sun_family;// 地址族(AF_LOCAL/AF_UNIX)
	char sun_path\[\];// 本地套接字文件的路径
}
// 网络地址结构,用于AF_INET域的IPV4写径通信
struct sockaddr_in{
	sa_family_t sin_family;∥地址族(AF_INET)
	in_port_t sin_port; //端口号(0~65535) - unsigned short
	struct in_addr sin_addr;//IP地址 - unsigned int
}
// 网络地址结构,用于AF_INET域的IPV4网络通信
struct in_addr{
	in_addr_t s_addr;
}
typedef uint16_t in_port_t; //无符号16位整数
typedef uint32_t in_addr_t; //无符号32位整数

3:字节序转换函数

uint32_t htonl(uint32_t hostlong); //长整形主机字节序到网络字节序
uint32_t ntohl(uint32_t netllong); //长整形网络字节序到主机字节序
uint16_t htons(uint16_t hostshort); //短整形主机字节序到网络字节序
uint16_t ntohs(uint16_t netshort); //短整型网络字节序到主机字节序
in_addr_t inet_addr(char const* ip); // 点分十进制字符串地址 -> 网络字节序形式整数地址
int inet_aton(char const* ip,struct in_addr* nip); //点分十进制字符串地址 -> 网络字节序形式整数地址
char* inet_ntoa(struct in_addr nip); //网络字节序形式整数地址 -> 点分十进制字符串地址

4:bind 将套接字和本机的地址结构绑定在一起

// 头文件 sys/socket.h
int bind(int sockfd,struct sockaddr const* addr,socklen_t addrlen);
- 功能:将套接字和本机的地址结构绑定在一起
- 参数:
	- sockfd:套接字描述符。
	- addr:自己的地址结构。
	- addrlen:地址结构的字节数
- 返回值:成功返回0,失败返回-1

5:listen 启动侦听

// 头文件 sys/socket.h
int listen(int sockfd,int backlog)
- 功能:启动侦听:在指定套接字上启动对连接请求的侦听功能
- 参数:
	- sockfd:套接字描述符,在调用此函数之前是一个主动套接字,是不能感知连接请求的,在调用此函数并成功返回之后,是一个被动套接字,具有感知连接请求的能力。
	- backlog:未决连接请求队列的最大长度,一般取不小于1024的值。
- 返回值:成功返回0,失败返回-1

6:accept

// 头文件 sys/socket.h
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
- 功能:等待并接受连接请求,在指定套接字上阻塞,直到连接建立完成。
- 参数:
	- sockfd:侦听套接字描述符
	- addr:输出连接请求发起方的地址信息
	- addrlen:输出连接请求发起方的地址信息字节数
- 返回值:成功返回可用于后续通信的连接套接字描述符,失败返回-1
  • 案例
// tcp 服务器
#include <stdio.h>
#include <ctype.h> // toupper
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/socket.h> // 网络相关
#include <sys/types.h>// 网络相关
#include <arpa/inet.h>// 网络相关
#include <sys/wait.h>

// 收尸
void sigfun(int signum){
	printf("服务器:收尸\n");
	for(;;){
		pid_t pid = waitpid(-1,NULL,WNOHANG);
		if(pid == -1){
			if(errno == ECHILD){
				printf("没有子进程了\n");
				break;
			}else{
				perror("waitpid");
				return ;
			}
		}else if(pid == 0){
			printf("%d进程在运行\n",getpid());
			break;
		}else{
			printf("%d进程:回收了%d进程的僵尸\n",getpid(),pid);
		}
	}
}

int main(){
	printf("服务器设置信号量\n");
	if(signal(SIGCHLD,sigfun)==SIG_ERR){ // 对17号信号做捕获处理
		perror("signal");
		return -1;
	}
	printf("服务器:创建套接字\n");
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return -1;
	}
	printf("服务器:组织地址结构\n");
	struct sockaddr_in ser;// IPV4的网络地址结构
	ser.sin_family = AF_INET;
	ser.sin_port = htons(8980);// 端口号,这里需要做字节序转换,因为TCP协议栈需要这个端口
	ser.sin_addr.s_addr = inet_addr("192.168.174.152");
	printf("服务器:绑定套接字和地址结构\n");
	if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser)) == -1){
		perror("bind");
		return -1;
	}
	printf("服务器:启动侦听\n");
	if(listen(sockfd,1024)==-1){// 监听,用于接收客户端的连接请求(3次握手)
		perror("listen");
		return -1;
	}
	for(;;){
		printf("服务器:等待连接\n");
		struct sockaddr_in cli; //用于输出客户端的地址结构
		socklen_t len = sizeof(cli); // 用来输出地址结构的大小
		int conn = accept(sockfd,(struct sockaddr*)&cli,&len); // 用于完成与客户端的3次握手中的后两次
		if(conn == -1){ // conn 是用来和客户端通信用的,每接入一个客户端,就会生成一个新的conn
			perror("accept");
			return -1;
		}
		printf("服务器:接收到%s:%hu的客户端的连接\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
		printf("服务器:业务处理\n");
		pid_t pid = fork();
		if(pid == -1){
			perror("fork");
			return -1;
		}
		if(pid == 0){
			close(sockfd);// 关掉从父进程复制过来的通信套接字
			for(;;){
				// 接收客户端发送的小写的串
				char buf[64]={};
				ssize_t size = read(conn,buf,sizeof(buf)-1);
				if(size == -1){
					perror("read");
					close(conn);
					return -1;
				}
				//客户端断开连接时
				if(size == 0){break;}
				// 转大写
				for(int i=0;i<strlen(buf);i++){
					buf[i]=toupper(buf[i]);
				}
				// 将大写的串回传给客户端
				if(write(conn,buf,strlen(buf))==-1){
					perror("write");
					close(conn);
					return -1;
				}
			}
			printf("服务器:关闭通信套接字\n");
			close(conn);
			return 0;
		}
		close(conn);// 子进程已经复制了这个套接字,父进程自己的直接关掉即可
	}
	printf("关闭服务器\n");
	close(sockfd);
	return 0;
}

7:connect 将套接字和对方的地址结构连接在一起

// 头文件 sys/socket.h
int connect(int sockfd,struct sockaddr const* addr,socklen_t addrlen);
- 功能:将套接字和对方的地址结构连接在一起
- 参数:
	- sockfd:套接字描述符
	- addr:对方的地址结构
	- addrlen:地址结构的字节数
- 返回值:成功返回0,失败返回-1

8:send 发送数据

// 头文件 sys/socket.h
ssize_t send(int sockfd,void const* buf,size_t count,int flags);
- 功能:发送数据
- 参数:
	- 若flags取0则与write函数完全等价,另外也可取以下值:
		MSG_DONTWAIT - 以非阻塞方式接收数据。
		MSG_OOB - 接收带外数据。
		MSG_DONTROUTE - 不查路由表,直接在本地网络中寻找目的主机
- 返回值:成功返回实际发送的字节数,失败返回-1

9:recv 接收数据

// 头文件 sys/socket.h
ssize_t recv(int sockfd,void* buf,size_t count,int flags);
- 功能:接收数据
- 参数:
	- 若flags取0则与read函数完全等价,另外也可取以下值:
		MSG_DONTWAIT - 以非阻塞方式接收数据。
		MSG_OOB - 接收带外数据。
		MSG_WAITALL - 等待所有数据,即不接收到count字节就不返回。
- 返回值:成功返回实际接收到的字节数,失败返回-1
  • 案例
// 基于tcp客户端的连接
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(){
	printf("客户端:创建套接字\n");
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return -1;
	}
	printf("客户端:组织服务器的地址结构\n");
	struct sockaddr_in ser;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(8980);
	ser.sin_addr.s_addr = inet_addr("192.168.174.152");
	printf("客户端:发起连接\n");
	if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser))==-1){
		perror("connect");
		return -1;
	}
	printf("客户端:业务处理\n");
	for(;;){
		// 发送小写的串
		char buf[64]={};
		fgets(buf,sizeof(buf),stdin);
		if(strcmp(buf,"!\n")==0){
			break;
		}
		if(send(sockfd,buf,strlen(buf),0)==-1){
			perror("send");
			return -1;
		}
		// 接收大写的串
		if(recv(sockfd,buf,sizeof(buf)-1,0)==-1){
			perror("recv");
			return -1;
		}
		// 显示
		printf("%s",buf);
	}
	printf("客户端:关闭套接字\n");
	close(sockfd);
	return 0;
}

5、UDP协议

5.1 UDP协议的基本特性

  • UDP不提供客户机与服务器的连接
    • UDP的客户机与服务器不必存在长期关系。一个UDP的客户机在通过一个套接字向一个UDP服务器发送了一个数据报之后,马上可以通过同一个套接字向另一个UDP服务器发送另一个数据报。同样,一个UDP服务器也可以通过同一个套接字接收来自不同客户机的数据报
  • UDP不保证数据传输的可靠性和有序性
    • UDP的协议栈底层不提供诸如确认、超时重传、RTT估算以及序列号等机制。因此UDP数据报在网络传输的过程中,可能丢失,也可能重复,甚至重新排序。应用程序必须自己处理这些情况
  • UDP不提供流量控制
    • UDP的协议栈底层只是一味地按照发送方的速率发送数据,全然不顾接收方的缓冲区是否装得下
  • UDP是全双工的
    • 在一个UDP套接字上,应用程序在任何时候都既可以发送数据也可以接收数据

5.2 常用函数

1:recvfrom 从哪里接收数据

// 头文件 sys/socket.h
ssize_t recvfrom(int sockfd,void* buf,size_t count,int flags,struct sockaddr* src_addr,socklen_t* addrlen);
- 功能:从哪里接收数据
- 参数:
	- 前四个参数和函数recv相同
	- src_addr:输出源主机的地址信息
	- addrlen:输入输出源主机的地址信息的字节数。
- 返回值:成功返回实际接收的字节数,失败返回-1

2:sendto 发送数据到哪里

// 头文件 sys/socket.h
ssize_t sendto(int sockfd,void const* buf,size_t count,int flags,struct sockaddr const* dest_addr,socklen_t addrlen);
- 功能:发送数据到哪里
- 参数:
	- 前四个参数和函数send相同
	- dest_addr:目的主机的地址信息。
	- addrlen:目的主机的地址信息的字节数。
- 返回值:成功返回实际发送的字节数,失败返回-1

5.3 UDP通信模型

在这里插入图片描述

  • 服务器
// udp 服务器
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys//types.h>
#include <arpa/inet.h>

int main(){
	printf("服务器:创建套接字\n");
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd == -1){
		perror("socket");
		return -1;
	}
	printf("服务器:组织地址结构\n");
	struct sockaddr_in ser;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(8090);
	ser.sin_addr.s_addr=inet_addr("192.168.174.152");
	printf("服务器:绑定套接字和地址结构\n");
	if(bind(sockfd,(struct sockaddr*)&ser,sizeof(ser))==-1){
		perror("bind");
		return -1;
	}
	printf("服务器:业务处理\n");
	for(;;){
		char buf[64]={};
		struct sockaddr_in cli;
		socklen_t len = sizeof(cli);
		if(recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&cli,&len)==-1){
			perror("recvfrom");
			return -1;
		}
		// 转大写
		for(int i=0;i<strlen(buf);i++){
			buf[i] = toupper(buf[i]);
		}
		// 将大写的串回传到客户端
		if(sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*) &cli,len) == -1){
			perror("sendto");
			return -1;
		}
	}
	printf("服务器关闭套接字\n");
	close(sockfd);
	return 0;
}
  • 客户端
// UDP客户端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
int main(){
	printf("客户端:创建套接字\n");
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd == -1){
		perror("socker");
		return -1;
	}        
	printf("客户端:组织服务器地址结构\n");
	struct sockaddr_in ser;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(8090);
	ser.sin_addr.s_addr = inet_addr("192.168.174.152");
	printf("客户端:业务处理\n");
	for(;;){
		// 向客户端发送小写的串
		char buf[64] = {};
		fgets(buf,sizeof(buf),stdin);
		if(strcmp(buf,"!\n")==0){
				break;
		}
		if(sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&ser,sizeof(ser))==-1){
				perror("sendto");
				return -1;
		}
		// 接收服务器回传
		if(recv(sockfd,buf,sizeof(buf)-1,0)==-1){
				perror("recv");
				return -1;
		}
		// 显示
		printf("客户端:收到%s",buf);
	}
	// 关闭套接字
	close(sockfd);
	return 0;
}

6、域名解析

  • IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替1P地址标识站点地址
  • 域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成
  • 当应用过程需要将一个主机域名映P地址时,就调用域名解析函数,解析函数将待转换的域名放在DNS请求中,以UDP报文方式发给本地域名服务器。本地的域名服务器查到域名后,将对应的IP地址放在应答报文中返回。
    相关函数
    gethostbyname 获取主机信息
// 头文件 netdb.h
struct hostent* gethostbyname(char const* host_name);
- 功能:通过参数所传的主机域名,获取主机信息
- 参数:
	- host_name 主机域名
- 返回值:函数执行成功返回表示主机信息的结构体指针,失败返回NULL
- 注意,该函数需要再联网情况下使用

结构

struct hostent{
	char *h_name; //主机官方名
	char **h_aliases; //主机别名表
	int h_addrtype; //地址类型
	int h_length; //地址长度
	char **h_addr_list; //IP地址表
};
  • 案例
#include <stdio.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc,char* argv[]){
	struct hostent* h = gethostbyname(argv[1]);
	if(h==NULL){
		perror("gethostbyname");
		return -1;
	}
	printf("主机官方名\n");
	printf("\t%s\n",h->h_name);
	printf("主机别名\n");
	for(char **pp = h->h_aliases;*pp;pp++){
		printf("\t%s\n",*pp);
	}
	printf("IP地址表\n");
	for(struct in_addr **pp=(struct in_addr**)h->h_addr_list;*pp;pp++){
		printf("\t%s\n",inet_ntoa(**pp));
	}
	return 0;
}
  • 创建http请求发送
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(){
	// 创建套接字
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd == -1){
		perror("socket");
		return -1;
	}
	// 组织百度服务器的地址结构
	struct sockaddr_in ser;
	ser.sin_family = AF_INET;
	ser.sin_port = htons(80);
	ser.sin_addr.s_addr = inet_addr("36.155.132.76");
	// 向百度服务器发起连接
	if(connect(sockfd,(struct sockaddr*)&ser,sizeof(ser))==-1){
		perror("connect");
		return -1;
	}
	// 组织http请求并发送给百度服务器
	char request[1024] ={};
	sprintf(request,"GET / HTTP/1.1\r\n"
					"Host: www.baidu.com\r\n"
					"Accept: */*\r\n"
					"Connection: close\r\n\r\n");
	if(send(sockfd,request,strlen(request),0)==-1){
		perror("send");
		return -1;
	}
	// 接收百度服务器回传的响应
	for(;;){
		char respond[1024] = {};
		ssize_t size = recv(sockfd,respond,sizeof(respond)-1,0);
		if(size == -1){
			perror("recv");
			return -1;
		}
		if(size == 0){
			break;
		}
		printf("%s",respond);
	}
	printf("\n");
	close(sockfd);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

启航zpyl

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

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

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

打赏作者

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

抵扣说明:

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

余额充值