TCP状态时序图:
1.主动发起连接请求端: CLOSE --发送SYN--SEND_SYN --接受 ACK、SYN --SEND_SYN--发送 ACK --ESTABLISHED(数据通信状态)
2.主动关闭连接请求端; ESTABLISHED(数据通信状态) --发送FIN --FIN_WAIT_1 --接受 ACK --FIN_WAIT_2(半关闭)
--接受对端发送 FIN --FIN_WAIT_2(半关闭) --回发 ACK --TIME_WAIT(只有主动关闭连接方会经历该状态)
-- 等 2MLS时长(大约40s) --CLOSE
3.被动接受连接请求端: CLOSE -- LISTEN --接收 SYN -- LISTEN --发送 ACK SYN --SYN_RCVD --接收ACK --ESTABLISHED
4.被动关闭连接请求端: ESTABLISHED -- 接收 FIN --ESTABLISHED --发送ACK
--CLOSE_WAIT (说明对端【主动关闭连接请求端】处于半关闭状态)
--发送 FIN--LASK_ACK --接收ACK--CLOSE
重点记忆: ESTABLISHED 、FIN_WAIT_2 <->CLOSE_WAIT 、TIME_WAIT(2MLS)
2MLS时长:
一定出现在 主动关闭连接请求端 ---TIME_WAIT
保证,最后一个 ACK 能成功被对端收到。(等待期间,对端没收到我发的 ACK ,对端会再次发送 FIN 请求)
端口复用:
int opt =1 ; //设置端口复用。
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt))
半关闭:
通信双方中,只有一端关闭通信 --FIN_WAIT_2
close(lfd);
shutdown (int fd,int how)
how: SHUT_RD 关读端
SHUT_WR 关写端
SHUT_RDWR 关读写
shutdown 在关闭多个文件描述符对应的文件时(采用dup2后),采用全关闭方法。close 值关闭一个
select多路IO转接:
原理: 借助内核,select来监听,客户端连接、数据通信事件
void FD_CLR(int fd, fd_set *set); 将一个文件描述符从监听集合中 移除
FD_CLR(4,&rset)
int FD_ISSET(int fd, fd_set *set); 判断一个文件描述符是否在监听集合中
FD_ISSET(4,&rset)
void FD_SET(int fd, fd_set *set); 将待监听的文件描述符添加到 监听集合中
FD_SET(4,&rset)
void FD_ZERO(fd_set *set); 清空一个文件描述符集合
FD_ZERO(4,&rset)
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds: 监听的所有文件描述符中,最大的文件描述符 +1
readfds: 读 文件描述符监听集合 传入传出 参数
writefds: 写 文件描述符监听集合 传入传出 参数
exceptfds: 异常 文件描述符监听集合 传入传出 参数
timeout: 》0 : 设置监听超时时长
NULL 阻塞监听
0 非阻塞 轮训监听
返回值:
》0 所有监听集合(3个)中, 满足对应事件的总数
0 没有满足条件 的文件描述符
-1 : errno
思路分析:
lfd = socket(); 创建套接字
bind(); 绑定地址结构
listen() 设置监听上限
fd_set rset,allset; 创建监听集合
FD_ZERO(&allset) 将监听集合清空
FD_SET(lfd,&allset) 将lfd添加到读集合中
while(1){
rset = allset
ret = select(lfd+1,&rset,NULL,NULL,NULL) 监听文件描述符集合对应事件
if(ret>0){
if(FD_ISSET(lfd,&rset)){
cfd =accept()
FD_SET(cfd,&allset)
}
for(i = lfd+1;i<=最大文件描述符;i++){
FD_ISSET(I,&rset)
read()
小 -- 大
write();
}
}
}
select优缺点:
缺点: 监听上限受文件描述符限制 最大 1024
检测满足条件的 fd, 自己添加业务逻辑 提高可编码难度
返回满足监听条件的文件描述符个数 并没有直接告诉哪些文件描述符就绪了 需要从头遍历传出的事件集合
优点:跨平台。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#define SERV_PORT 6666
int main(){
int i,j,n,nready;
int maxfd =0 ;
int listenfd,connfd;
char buf[BUFSIZ];
struct sockaddr_in clie_addr,serv_addr;
socklen_t clie_addr_len;
listenfd = socket(AF_INET,SOCK_STREAM,0);
int opt =1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr= htonl(INADDR_ANY);
bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
listen(listenfd,128);
fd_set rset,allset; //定义读集合,备份集合allset
maxfd = listenfd; //最大文件描述符
FD_ZERO(&allset); //清空监听集合
FD_SET(listenfd,&allset); //将fd 添加到集合
while(1){
rset = allset;
nready = select (maxfd+1,&rset,NULL,NULL,NULL); //使用select监听
if(nready<0){
perror("select error");
exit(1);
}
if(FD_ISSET(listenfd,&rset)){ //listenfd 满足监听的 读事件
clie_addr_len = sizeof(clie_addr);
connfd = accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);//建立连接 不阻塞
FD_SET(connfd,&allset); //将产生的fd 添加到监听集合中,监听数据读事件
if(maxfd<connfd) //修改 maxfd
maxfd= connfd;
if(nready == 1) //说明select只 返回一个 ,并且是listenfd 后续执行无需执行
continue;
}
for(i=listenfd+1;i<=maxfd;++i){ //处理满足读事件的 fd
if(FD_ISSET(i,&rset)){ //找到满足读事件的那个fd
if((n=read(i,buf,sizeof(buf)))==0){ //检测到客户端已经关闭连接
close(i);
FD_CLR(i,&allset); //将关闭的fd 移除监听集合
}else if(n>0){
for(j=0;j<n;j++)
buf[j]=toupper(buf[j]);
write(i,buf,n);
write(STDOUT_FILENO,buf,n);
}
}
}
}
close(listenfd);
return 0;
}
// 优化后 select
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#define SERV_PORT 6666
int main(){
int i,j,n,nready,maxi;
int client[FD_SETSIZE]; //自定义数组client 防止遍历 1024个文件描述符 FD_SETSIZE 为1024
int maxfd =0 ;
int listenfd,connfd;
char buf[BUFSIZ];
struct sockaddr_in clie_addr,serv_addr;
socklen_t clie_addr_len;
listenfd = socket(AF_INET,SOCK_STREAM,0);
int opt =1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr= htonl(INADDR_ANY);
bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
listen(listenfd,128);
fd_set rset,allset; //定 读 集和,备份集和allset
maxfd = listenfd; //最大文件描述符
maxi = -1;
for(i=0;i<FD_SETSIZE;++i){ //用-1 初始化 client【】
client[i]=-1;
}
FD_ZERO(&allset); //清空监听集合
FD_SET(listenfd,&allset); //将fd 添加到集合
while(1){
rset = allset;
nready = select (maxfd+1,&rset,NULL,NULL,NULL); //使用select监听
if(nready<0){
perror("select error");
exit(1);
}
if(FD_ISSET(listenfd,&rset)){ //listenfd 满足监听的 读事件
clie_addr_len = sizeof(clie_addr);
connfd = accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);//建立连接 不阻塞
for(i=0;i<FD_SETSIZE;i++){
if(client[i]<0){
client[i]=connfd;
break;
}
}
if(i==FD_SETSIZE){
fputs("too many clients\n",stderr);//达到select能监控的 上限 1024
exit(1);
}
FD_SET(connfd,&allset); //将产生的fd 添加到监听集合中,监听数据读事件
if(maxfd<connfd) //修改 maxfd
maxfd= connfd;
if(i>maxi)
maxi =i; //保证maxi总是存的 client【】最后一个元素下表
if(nready == 1) //说明select只 返回一个 ,并且是listenfd 后续执行无需执行
continue;
}
for(i=0;i<=maxi;++i){ //处理满足读事件的 fd
if((listenfd=client[i])<0)
continue;
if(FD_ISSET(client[i],&rset)){ //找到满足读事件的那个fd
if((n=read(client[i],buf,sizeof(buf)))==0){ //检测到客户端已经关闭连接服务器也关闭
close(client[i]);
FD_CLR(client[i],&allset); //将关闭的fd 移除监听集合
client[i]=-1;
}else if(n>0){
for(j=0;j<n;j++)
buf[j]=toupper(buf[j]);
write(client[i],buf,n);
write(STDOUT_FILENO,buf,n);
}
}
}
}
close(listenfd);
return 0;
}
来源:CSDN
作者:我喜欢的人很优秀
链接:https://blog.csdn.net/weixin_44374280/article/details/104382831