ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

tcp shutdown

2022-07-06 18:32:46  阅读:181  来源: 互联网

标签:return addr int tcp fd shutdown close include


环境:centos8 x86_64 内核:4.18.0

1. close() 与 shutdown()

我们知道,tcp 有 4 次挥手过程,对于主动端来说:

  • 发送 fin 通知被动端连接即将关闭
  • 等待被动端发送 fin 过来以彻底结束连接

如果进程通过调用 close() 来结束连接,会让 socket 直接关闭成为孤儿连接,即不再绑定任何进程。
不再绑定任何进程有如下影响:

  • 接收缓存全部清空
  • 在等待对端 fin 的过程中,如果对端还在持续发送数据,会回一个 rst 过去
  • 主动端发送 rst 后,主动端 socket 就关闭了,不再经历 fin_wait2 和 time_wait
  • 被动端收到 rst 后,被动端 socket 也直接关闭了,不再经历 close_wait 和 last_ack
  • 被动端收到 rst 后,再次发送数据,会触发 sigpipe 信号(这里触发 sigpipe 信号明显是非正常四次挥手结束的连接才会触发)

调用 close() 的方式不太优雅:

  • 接收缓存可能还有数据没有来得及读取
  • 主动端等待 fin 的过程中,被动端可能还有数据需要发送,才能优雅的关闭 socket

基于此,shutdown() 函数出现了,其接口也非常简洁:

int shutdown(int socket, int how);

how 可以取值:

  • SHUT_WR,即只关闭写方向
  • SHUT_RD,即只关闭读方向
  • SHUT_RDWR,即同时关闭读方向和写方向

Q:先调用 SHUT_RD 会发生什么?
A:我测试的内核版本什么都不会发生(tcp 状态不会改变,缓冲区不会清空,对端后续发送的数据也不会丢弃)

Q:调用 SHUT_RDWR 与 close() 的区别
A:没有区别

Q:先调用 SHUT_WR 会发生什么?
A:发送 fin 给对端,主动端接收缓冲区依然有效,也可以继续收取对端发送的数据(回复 ack 和通知应用层)

2. SHUT_WR 示例

客户端:

#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>

int set_nonblock(int fd) {
  int old_flag = fcntl(fd, F_GETFL, 0);
  int new_flag = old_flag | O_NONBLOCK;
  if (fcntl(fd, F_SETFD, new_flag) < 0) {
    fprintf(stderr, "fcntl failed: %s\n", strerror(errno));
    return -1;
  }
  return 0;
}

int main (int argc, char* argv[]) {
  if (argc != 2) {
    printf("usage: ./mytest <port>\n");
    return 0;
  }

  const char* host = "0.0.0.0";
  int port = atoi(argv[1]);

  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(host);
  addr.sin_port = htons(port);

  int fd = socket(AF_INET, SOCK_STREAM, 0);
  int reuseaddr = 1;
  setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));

  if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
    printf("connect to %s:%d failed: %s\n", host, port, strerror(errno));
    close(fd);
    return 0;
  }
  printf("connect to %s:%d success\n", host, port);

  assert(shutdown(fd, SHUT_WR) == 0);

  int seconds = 2;
  sleep(seconds);

  assert(set_nonblock(fd) == 0);
  int read_len = 100;
  char read_buf[read_len];
  int n = read(fd, read_buf, 100);
  if (n > 0) {
    read_buf[n] = '\0';
    printf("read return: %s\n", read_buf);
  }

  return 0;
}

服务端:

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

int main(int argc, char** argv) {
  if (argc != 2) {
    fprintf(stderr, "usage: ./server 3000\n");
    return -1;
  }

  const char* host = "0.0.0.0";
  int port = atoi(argv[1]);

  int listenfd = socket(AF_INET, SOCK_STREAM, 0);
  if (listenfd == -1) {
    fprintf(stderr, "create listen fd failed\n");
    return -1;
  }

  int on = 1;
  setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));

  struct sockaddr_in bindaddr;
  bindaddr.sin_family = AF_INET;
  // bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  (void)inet_aton(host, &bindaddr.sin_addr);
  bindaddr.sin_port = htons(port);

  int ret = bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr));
  if (ret == -1) {
    fprintf(stderr, "bind listen fd failed: %s\n", strerror(errno));
    close(listenfd);
    return -1;
  }

  fprintf(stderr, "start listen on [%s:%d]\n", host, port);

  // block call
  ret = listen(listenfd, SOMAXCONN);
  if (ret == -1) {
    fprintf(stderr, "listen failed: %s\n", strerror(errno));
    close(listenfd);
    return -1;
  }

  struct sockaddr_in clientaddr;
  socklen_t clientaddr_len = sizeof(clientaddr);
  int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
  if (clientfd == -1) {
    fprintf(stderr, "accept failed: %s\n", strerror(errno));
    close(listenfd);
    return -1;
  }

  fprintf(stderr, "new client [%s, %d]\n",
      inet_ntoa(clientaddr.sin_addr), htons(clientaddr.sin_port));

  int seconds = 1;
  sleep(seconds);

  char cache[] = "hello, world\n";
  if (write(clientfd, cache, strlen(cache)) == -1) {
    printf("write to %s:%d failed: %s\n", host, port, strerror(errno));
  }

  seconds = 3;
  sleep(seconds);

  close(clientfd);
  close(listenfd);

  return 0;
}

日志如下:

服务端:
  [user@centos8 shutdown]$ ./server 3000
  start listen on [0.0.0.0:3000]
  new client [127.0.0.1, 50510]

客户端:
  [user@centos8 shutdown]$ ./client 3000
  connect to 0.0.0.0:3000 success
  read return: hello, world

抓包如下:

标签:return,addr,int,tcp,fd,shutdown,close,include
来源: https://www.cnblogs.com/moonwalk/p/16451969.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有