网络编程close学习
1、概述
下面问题你会怎么回答?
希望发完一个数据后关闭连接,且希望对端的应用层尽量收到?代码可以这么写么?
send(fd, buf, .....);
close(fd);
2、分析
上述代码是不行的,send返回成功仅表示数据拷贝到内核tcp发送缓冲区,不代表数据发送到对端;直接调用close会导致内核缓冲区未发送的数据被丢弃,无法保证对端完整接收。
2.1、解决办法
2.1.1、Linger选项
Linger选项是控制socket关闭时的行为,当设置Linger选项后,调用close函数时若内核缓冲区还有数据,会等待指定时间,如果等待时间内数据未能成功发送,这些缓冲区数据会被丢弃。
Linger选项结构体,调用setsockopt设置,如下:
typedef struct linger {u_short l_onoff; //0关闭,1打开,默认是关闭的u_short l_linger;
} LINGER, *PLINGER, *LPLINGER;
struct linger ling;
ling.l_onoff = 1; // 0关闭,1打开
ling.l_linger = 30; // 等待 30 秒,超时强制关闭setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));// 阻塞直到 30 秒,或数据发完
int ret = close(sockfd);
这里会有俩种情况,
等待时间足够长,数据可以成功发送。(服务端一般不允许设置长时间等待,可能会耗尽资源)
等待时间短,可能出现数据部分发送成功或全部被丢弃的情况。
所以Linger选项可以是我们的备选方案。
2.1.2、shutdown(SH_WR)
socket工作是全双工的,读和写是分别独立的。
shutdown函数可以单独关闭读或写方向,close是关闭整个连接。
// how是SHUT_RD,本端读通道关闭,对端不能再写数据
// how是SHUT_WD,本端写通道关闭,对端不能再读数据
int shutdown(int sockfd, int how);
调用完send发送数据,可以调用shutdown(fd, SHUT_WD)告诉对端数据发送完毕,但接收通道仍开放。对端应用层收完数据后会回复ack,并调用close函数,此时可以安全调用close函数关闭连接。这是tcp半关闭问题!!!,感兴趣读者可再深入学习。
示例代码如下:
【服务端】
// 1. 发送数据
if (send(sockfd, data, len, 0) < 0) {perror("send failed");close(sockfd);return;
}// 2. 【关键】关闭写方向,通知对端“我发完了”
if (shutdown(sockfd, SHUT_WR) < 0) {perror("shutdown failed");close(sockfd);return;
}
// 此时本端不能 send() 了,但还能 recv()// 3. 等待对端的应用层确认(ACK)
char ack[4];
ssize_t n = recv(sockfd, ack, sizeof(ack), 0);
if (n > 0 && memcmp(ack, "ACK", 3) == 0) {printf("Received ACK from client, data processed.\n");
} else {printf("No ACK received.\n");
}// 4. 安全关闭
close(sockfd);
【客户端】
// 客户端 recv 到所有数据后
char buf[1];
while (recv(sockfd, buf, 1, 0) > 0) {// 处理数据
}// recv 返回 0,表示对端 shutdown(SHUT_WR),数据发送完毕
printf("Server finished sending data.\n");// 发送 ACK 确认
send(sockfd, "ACK", 3, 0);// 可以 close
close(sockfd);
学习链接:https://github.com/0voice