【TCP/IP和socket编程常见问题】

tcp连接是什么?
双方都分配空间,为此次连接生成一个fd(文件表述符)
三次握手
因为建立的是虚拟连接,所以为了确保客户端和服务端之间的通信而进行的一系列互相确认的过程
三个方面分析三次握手的原因:
  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费
😁原因一:避免历史连接
如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:
如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;
如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;
所以,TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。
😁原因二:同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:
接收方可以去除重复的数据;
接收方可以根据数据包的序列号按序接收;
可以标识发送出去的数据包中, 哪些是已经被对方收到的;
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
😁原因三:避免资源浪费
果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
😁seq和ack,以及ACK与SYN的标志位
SYN=1,ACK=0表示发起连接,即第一次握手
SYN=1,ACK=1表示回复确认收到连接请求,即第二次握手
SYN=0,ACK=1表示建立连接,即第三次握手
如果已经建立了连接,但是客户端突然出现了故障,怎么办?
TP保活机制,一般都会通过心跳来试探对方是否存活。
什么是TCP半连接?
处于SYN_RECV状态的服务端,完成了第二次握手,正在等待客户端回复ACK确认。
什么是SYN攻击?
利用三次握手的漏洞,客户端大量的向服务端发送连接请求,但在第三次握手时却不回复服务端ACK,导致服务端中存在大量的半连接请求,耗费CPU和内存资源。
防护手段
降低SYN超时时间,使得主机可以快速丢弃半连接请求
增大半连接队列
启动syn cookies
怎么检测SYN攻击?
查看半连接请求:netstat -natp | grep SYN_RECV
网络服务器端通信模型理解:
一个酒店(服务器),酒店门口有个迎宾的人(最开始创建的socket,bind绑定就是告诉他站在哪个门口去迎宾),每来一个顾客(连接请求),这个迎宾的人就把他带到里边,将顾客交给一个服务员(accept返回的socket)提供服务,迎宾的人继续站在门口等待下一个顾客
调用connect:三次握手开始,三次握手是由connect函数发起的,由主动方的connect函数向对方发送一个SYN的同步头过来
三次握手的成功与失败与上述通信模型中的8个函数都没有关系。
这里三次握手对于被动方来说,是由操作系统内部的协议栈来实现的。
在accept之前,三次握手就已经成功了
socket() bind() listen() connect() accept() listen() recvd() close()
八大函数--简略
socket() --得到fd!
功能:指定了协议族(IPv4、IPv6或unix)和套接口类型(字节流、数据报或原始套接口)。但并没有指定本地协议地址或远程协议地址。
定义:
int socket(int family, int type, int protocol);
//返回:出错:-1 //     成功:套接口描述字 (socket file descriptor)(套接字)sockfd
bind() --我在哪个端口?
功能:给套接口分配一个本地协议地址。
定义:
int bind(int sockfd, const struct sockaddr *my_addr, int addrlen);
connect() --Hello!
功能:建立与TCP服务器的连接
定义:
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
listen() --有人给我打电话吗?
功能:将未连接主动套接口的转换为被动套接口,指示内核接受对该套接口的连接请求。
定义:
int listen(int sockfd, int backlog);
参数:
- sockfd调用socket函数返回的文件描述符(套接字).
- 未完成连接队列和已完成连接队列的上限.
- 未完成连接队列 : 服务端还未完成三次握手全部过程的一个队列.
- 已完成连接队列 : 服务端已经完成三次握手全部过程的一个队列, 等待accept函数从这个队列中返回下一个(返回其实是取出, 该套接字不在已完成队列中了)套接字.
accept() --"Thank you for calling port 3490."
功能:accept函数从listen的已完成连接队列中返回下一个已完成连接, 也就是对端的套接字, 一个新的套接字. 当已完成连接队列的下一个完成
连接是空, 那么accept函数将被阻塞.
定义:
int accept(int sockfd, struct sockaddr *cliaddr, int* addrlen);
// 返回:调用成功时返回: 1. cliaddr: 客户进程的协议地址和地址大小 2. 新套接口描述字 send() 和 recv() --Talk to me, baby!
close() --滚开!
socket()和accept()返回的fd 举个栗子:
一个客户端和一个服务端连接,双方socket产生各自的c_sock_fd和s_sock_fd;
s_sock_fd进行bind和listen后,accept准备接受客户端的连接请求;c_sock_fd调用connect请求连接服务端;
服务端接到请求产生accept_fd,届时accept_fd和c_sock_fd两个套接字可以通讯,而s_sock_fd则可以关闭;
客户端关闭close(c_sock_fd)后,服务端关闭所有未关闭的fd,通讯彻底断开。
ps:服务端的socket产生的套接字只是用来监听的,不能直接用于发送接收数据。
八大函数--文字详细
😊accept()函数
当有连接发起时(即connect发起时),第一次握手发过来SYN的包,回完ACK后,将它加入到SYN队列(有可能有多个连接同时发起),用来存储给它发SYN的同步头
收到一个ACK后,从SYN队列中拿一个同步头出来找到之前的源端口号和目的端口号,完成三次握手,再入队到Accept队列中(已经完成三次握手的连接在队列中排队)
accept函数就是从Accept队列中拿一个数据出来,创建一个新的套接字并返回一个与其相关联的文件描述符,这个套接字与原来的套接字具有相同的属性
然后服务器可以派生一个进程或线程来处理这个套接字上的连接了,而服务器自身又回到原来的套接字上等待下一个连接请求的到来
😊listen()
在listen中主要是创建SYN和Accept这两个队列
listen有两个参数:fd和backlog      backlog就是SYN和Accept队列的长度(两个长度一样)
😊connect()
主要实现三次握手
connect主动发起连接请求,同时会阻塞调用方,当接收到ACK,客户端进程解除阻塞
connect也会维护一个队列
在开始会创建流,然后该流入队
当有SYN、ACK返回时,将流出队,发送ACK
定期检查流的状态,是established状态或closed状态就返回,是sent状态的话不返回
😊socket()
Linux的虚拟文件系统
所有的索引都在fdtable中,索引后包含一个指针
这个指针指向一个文件,文件包含inode(对于socket:IO和socket相关的数据;对于其他文件:就是磁盘相对应的数据)和File_operation
分配send和recive的buffer
找一个没有被使用的fd(去文件系统中查找)
返回fd
设置fd
设置socket相关的参数
返回fd
😊send和recv
内部实现通过滑动窗口来实现传输的控制
😊close()四次挥手
TIME_WAIT一定是在主动关闭的这一方,CLOSE_WAIT一定是在被动关闭的这一方
TIME_WAIT防止对方未收到最后一个ACK,TIME_WAIT之后如果ACK丢失,被动方会再次发送一个FIN,主动方又重发ACK,此即TIME_WAIT的作用
😊😊
在网络程序中,一个进程同时处理多个文件描述符是很常见的情况。select()系统调用可以使进程检测同时等待的多个I/O设备,当没有设备准备好时,select()阻塞,其中任一设备准备好时,select()就返回。select在异步(非阻塞)connect中的应用,刚开始搞socket编程的时候我一直都用阻塞式的connect,非阻塞connect的问题是由于当时搞proxy scan
而提出的呵呵,通过在网上与网友们的交流及查找相关FAQ,总算知道了怎么解决这一问题.同样 用select可以很好地解决这一问题.


#学习路径#
全部评论

相关推荐

评论
11
74
分享
牛客网
牛客企业服务