socket自连接

本文详细介绍了TCP协议中的同时打开机制,这是一种罕见但可能发生的连接建立方式,涉及两端几乎同时发送SYN包。还讨论了自连接问题,即客户端由于端口巧合导致的误连接,并提出了防止自连接的策略,包括避免监听特定端口范围和通过检查连接端点来识别自连接。最后提供了一个测试代码示例,用于演示连接过程。
摘要由CSDN通过智能技术生成

同时打开

两个应用程序同时彼此执行主动打开的情况是可能的,但是发生的可能性极小。每一方必须发送一个SYN,且这些SYN必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开。两端必须几乎在同时启动,以便收到彼此的SYN。只要两端有较长的往返时间就能保证这一点。
TCP是特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接(其他的协议族,最突出的是OSI运输层,在这种情况下将建立两条连接而不是一条连接)出现同时打开的情况时,两端几乎在同时发送SYN,并进入 SYN_SENT状态。当每一端收到SYN时,状态变为SYN_RCVD,同时它们都再发SYN并对收到的SYN进行确认。当双方都收到SYN及相应的ACK时,状态都变迁为ESTABLISHED。
在同时打开的情况下,即使客户端没有开启监听依然会建立连接。
在这里插入图片描述

自连接原因

看完同时打开应该明白了为什么会出现子连接,当客户端调用connect连接自身ip时,此时内核随机分配一个端口给客户端(这里并不是真正的随机而是通过计数器来实现的)(其范围可通过sys -A|grep range来查看),若此时分配的端口号等于目的端口号,则会出现自连接。
客户端发送syn后等待服务器返回ack,而此时客户端发的syn报文从网卡绕了一圈又回到了内核被自己收到,而此时客户端只会认为这个报文是另一台机器发过来的,触发‘同时打开’,发送ack报文,ack报文发送回来后 ,客户端认为是自己发送的第一条请求发回来了,就成功建立起连接,而以后的传输报文都被客户端看成是与服务器同时发的情况,所以ack报文还是能正常收发。

解决办法

1、监听端口设置在客户端ip_local_port范围外
2、建立连接后判断是否是自连接(getsockname、getpeername)

测试代码

#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<unistd.h>
#include<errno.h>
#include<sys/types.h>
#include<netdb.h>
#include<string.h>
int main(int args,char*argv[])
{
        if(args<3){printf("using ip and port\n");return -1;}
        struct hostent* h;
        struct sockaddr_in clientaddr,servaddr;
        if((h = gethostbyname(argv[1]))==NULL)
        {
        	printf("gethostbyname failed:%s\n",strerror(errno));
        	return -1;
        }
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(atoi(argv[2]));
        memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
        int socklen = sizeof(struct sockaddr_in);
        int sockfd;
        int n = 65536;
        int min,max;
        min = max = 50000;
        while(n--)
        {
	        memset(&clientaddr,0x00,sizeof(clientaddr));
	        sockfd = socket(AF_INET,SOCK_STREAM,6);
	        if(sockfd<0){printf("socket failed\n");continue;}
	        if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(clientaddr))!=0)
	        {
	        	if(getsockname(sockfd,(struct sockaddr*)&clientaddr,(socklen_t*)&socklen)!=0)	
	        	{printf("%s\n",strerror(errno)); close(sockfd);return -1;}
	        	min = min<htons(clientaddr.sin_port)?min:htons(clientaddr.sin_port);
	        	max = max>htons(clientaddr.sin_port)?max:htons(clientaddr.sin_port);
	        	printf("connect failed(%d)\n",ntohs(clientaddr.sin_port));
	        	close(sockfd);
	        	continue;
	        }
	        if(getsockname(sockfd,(struct sockaddr*)&clientaddr,(socklen_t*)&socklen)!=0)
	        {printf("%s\n",strerror(errno)); close(sockfd);return -1;}
	        printf("connect success(%d)\n",ntohs(clientaddr.sin_port));
	        break;
        }
        printf("min=%d,max=%d\n",min,max);
        if(n<0)return -1;
        char buffer[10];
        for(int i =0;i<10;++i)
        {buffer[i] = "0123456789ABCDEF"[i%16];}
        int iret;
        for(int i = 0;i<2;++i)
        {
                iret = send(sockfd,buffer,sizeof(buffer),0);
                if(iret<0){printf("send failed:%s\n",strerror(errno));close(sockfd);return -1;}
                if(iret==0){printf("close connect\n");close(sockfd);}
                printf("发送:%s\n",buffer);
                iret = recv(sockfd,buffer,sizeof(buffer),0);
                if(iret<0){printf("recv failed:%s\n",strerror(errno));close(sockfd);return -1;}
                if(iret==0){printf("close connect\n");close(sockfd);}
                printf("接收:%s\n",buffer);
        }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值