其他分享
首页 > 其他分享> > tcp shutdown

tcp shutdown

作者:互联网

环境:centos8 x86_64 内核:4.18.0

1. close() 与 shutdown()

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

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

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

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

int shutdown(int socket, int how);

how 可以取值:

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