博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
10-在accept之前中止连接(连接异常)
阅读量:2044 次
发布时间:2019-04-28

本文共 2982 字,大约阅读时间需要 9 分钟。

   相信大家对基于TCP的客户端/服务端整个网络通信流程已经非常清楚了,现在假设这么一种情况,当客户端和服务端完成三次握手建立连接后,客户端tcp在服务端调用accept之前,马上又发送了一个RST中止连接,这将会发生什么?

这里写图片描述

  如上图所示,为了模拟这种出错情况需要在服务端调用accept之前先休眠一段时间,并在服务端进程休眠期间,启动客户端调用connect发起连接,一旦connect返回就设置SO_LINGER套接字选项并发送RST,然后中止连接。

//客户端connect连接返回,通过SO_LINGER选项立刻发送RSTlgr.l_onoff = 1;lgr.l_linger = 0;ret = setsockopt(sfd, SOL_SOCKET, SO_LINGER, &lgr, sizeof lgr);

  但是不同的实现对于这种情况的处理方式也不一样,例如Berkeley实现在内核就中止了这条连接,服务器进程根本就看不到,而大多数SVR4实现则会返回一个错误给服务器进程,不过具体怎么处理要取决于具体实现。因为对于服务器来说,当它调用accept时并不知道之前有一个已完成的连接已经从已完成队列中删除掉了。

  如果有些实现的服务器accept返回的是ECONNABORTED错误,那么服务器可以忽略这个错误,再次调用accept即可。

客户端主要代码:

#include 
#include
#include
#include
#include
#include
#define SERV_IP "192.168.0.107"#define SERV_PORT 10001int main(void) { int sfd, len,ret; struct sockaddr_in serv_addr; struct linger lgr; char buf[BUFSIZ]; sfd = socket(AF_INET, SOCK_STREAM, 0); if(ret < 0){ perror("set SO_LINGER Error"); } bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); serv_addr.sin_port = htons(SERV_PORT); //客户端connect连接返回,通过SO_LINGER选项立刻发送RST lgr.l_onoff = 1; lgr.l_linger = 0; ret = setsockopt(sfd, SOL_SOCKET, SO_LINGER, &lgr, sizeof lgr); ret = connect(sfd, (struct sockaddr *)&serv_addr , sizeof(serv_addr)); if(ret == 0) { printf("client connect successful\n"); } //关闭链接 close(sfd); return 0;}

服务端代码:

#include 
#include
#include
#include
#include
#include
#include
#define SERV_PORT 10001#define SERV_IP "192.168.0.107"int main(void) { int sfd, cfd; int len, i; //BUFSIZ是系统内嵌的一个宏,用来指定buf大小 char buf[BUFSIZ], clie_IP[BUFSIZ]; struct sockaddr_in serv_addr, clie_addr; socklen_t clie_addr_len; sfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; inet_pton(AF_INET , SERV_IP , &serv_addr.sin_addr.s_addr); serv_addr.sin_port = htons(SERV_PORT); //绑定套接字 bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); listen(sfd, 64); printf("wait for client connect ...\n"); clie_addr_len = sizeof(clie_addr); while(1) { //在accept之前休眠10s sleep(10); //阻塞等待客户端发起连接 cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len); //打印accept后的信息和客户端的连接 if(cfd > 0) { printf("accept cfd = %d\n" , cfd); } printf("client IP:%s\tport:%d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), ntohs(clie_addr.sin_port)); //服务端读取数据 len = read(cfd, buf, sizeof(buf)); //read返回0说明对端已经关闭 if(len < 0) { //进一步判断是否收到RST if(errno == ECONNRESET) { printf("read reset by peer\n"); break; } } } close(sfd); close(cfd); return 0;}

  执行./server命令先启动服务端,然后服务器等待客户端连接,此时服务器在调用accept之前休眠了10秒钟。

这里写图片描述

然后执行./client命令再启动客户端

这里写图片描述

tcpdump抓到的数据包来看,确实验证了这一点:

这里写图片描述

  客户端和服务端建立tcp连接后,然后客户端又马上发送了RST断开连接,但是此时服务端的accept并不知道这条完成“三次握手”的未决连接已经从未决连接队列中删除掉了,所以accept调用最后会返回成功。不过当服务器收到RST时,read调用返回了一个ECONNRESET 错误,打印read reset by peer。

你可能感兴趣的文章
BlockQueue 生产消费 不需要判断阻塞唤醒条件
查看>>
ExecutorService 线程池 newFixedThreadPool newSingleThreadExecutor newCachedThreadPool
查看>>
强引用 软引用 弱引用 虚引用
查看>>
java去除字符串中的空格\t、回车\n、换行符\r、制表符\t
查看>>
数据类型 java转换
查看>>
常用的正则表达式
查看>>
"NetworkError: 400 Bad Request - http://172.16.47.117:8088/rhip/**/####t/approval?date=976
查看>>
ie8 加载不到js 报SCRIPT1028: 缺少标识符、字符串或数字 ;SCRIPT5009: “anorectaSearch”未定义
查看>>
mybatis 根据 数据库表 自动生成 实体
查看>>
win10将IE11兼容ie10
查看>>
Kettle WebService组件无法传参问题解决
查看>>
checkbox设置字体颜色
查看>>
统计:分组统计后只加合计,不加小计 group by rollup
查看>>
第一篇 HelloWorld.java重新学起
查看>>
ORACLE表空间扩张
查看>>
oracl 锁表 解锁 杀死进程
查看>>
orcal 循环执行sql
查看>>
web.xml配置监听器,加载数据库信息配置文件ServletContextListener
查看>>
ORACLEL临时表空间扩张
查看>>
java 构造方法
查看>>