Linux网络编程(4)TCP状态转换-select-poll

使用select实现IO多路转接

相关知识
TCP的状态转换
Linux网络编程(4)TCP状态转换-select-poll_第1张图片TCP的状态转换:在进行通信的时候进程所处的状态会随着通信的状态发生改变
在一个正常通信的模型中,服务器和客户端的状态变化如下L

客户端:CLOSED->(第一次握手后)SYN_SENT->(第二次握手)ESTABLISTHED->(第一次挥手)FIN_WAIT_2->(第二次挥手)FIN_WAIT_2->(第三四次挥手)TIME_WAIT->(延时2msl)CLOSED

服务器:CLOSED->(被动接受连接请求)LISTEN->(收到SYN,发送SYN,ACK)SYN_RCVD->(收到ACK)ESTABLISTHED->(收到FIN,发送ACK)CLOSED_WAIT->(关闭发送FIN)LAST_ACK->(收到ACK)->CLOSED

当发送和接受端的状态都变为ESTABLISTHED的时候两端才能够正常通信,在通信过程中,两端的状态不会发生改变,在挥手的过程中,谁先关闭连接,谁的状态先发生改变

一个MSL的时长大概在30s

半关闭状态和实现函数:
半关闭状态:
A给B发送是FIN(A调用了close函数), 但是B没有给A发送FIN(B没有调用close)
A断开了与B的连接, B没有断开与A的连接

特点:A不能给B发送数据, A可以收B发送的数据
B可以给A发送数据

实现函数:int shutdown(int sockfd, int how);
sockfd: 要半关闭的一方对应的文件描述符
通信的文件描述符
how:所要关闭的功能
SHUT_RD - 0 - 读
SHUT_WR - 1 - 写
SHUT_RDWR - 2 - 读写

查看网络相关状态信息

命令: netstat
参数:
-a (all)显示所有选项,默认不显示LISTEN相关
-p 显示建立相关链接的程序名
-n 拒绝显示别名,能显示数字的全部转化成数字。

-t (tcp)仅显示tcp相关选项
-u (udp)仅显示udp相关选项
-l 仅列出有在 Listen (监听) 的服务状态
在这里插入图片描述

端口占用问题:在客户端断开连接后再次进行连接时候会失败,原因是该端口在连接断开后需要等待2MSL才进行释放,为了解决该问题,需要进行端口重用

常用的用途:
防止服务器重启时之前绑定的端口还未释放
程序突然退出而系统没有释放端口
设置方法
int opt = 1;
setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR,(const void *)&opt, sizeof(opt));

I/O的多路转接

当一台服务器需要处理多个客户端的连接请求,为了优化效率提高速度,将一些连接和通信的请求处理交给内核去完成。

未使用I/O多路转接下的处理方法
1.多进程线程
主进程线程用于监听连接请求,处于阻塞状态,当接收到请求后创建新的进程或者线程处理连接

2非阻塞,忙轮寻
在已经建立连接的客户端中,每隔一定时间向客户端发送信号,确认客户端下一次将要传输数据的时间,根据时间来安排处理顺序
优点: 提高了程序的执行效率
缺点: 需要占用更多的cpu和系统资源

使用IO多路转接技术 select/poll/epoll
1.select/poll
在服务器程序中,委托内核调用select()函数检测已建立连接的客户端中,哪些将要与自身进行通信,选择这些将要通信的进行处理
select/poll的缺点:
select/poll只能计算出将要通信的个数,但具体是哪个客户端需要自己去遍历

2.epoll
实现的思路和select/poll大致类似,但能够显示通信请求的具体信息

I/O多路转接的大致实现思路
1.先构造一张有关文件描述符的列表,将要监听的文件描述符添加到该列表中
2.调用一个函数,监听该表中的文件描述符,知道这些描述符表中的一个进行I/O操作时,该函数才返回
–该函数为阻塞函数
–函数对文件件描述符的检测操作是由内核完成的
3.在返回时,他告诉进程有多少描述符要进行I/O操作

Linux网络编程(4)TCP状态转换-select-poll_第2张图片select()函数原型
int select(int nfds,fd_setwritefds,fd_setexceptfds,struct timeval*timeout);

函数参数
nfds: 要检测的文件描述中最大的fd+1 (最大值为1024)
readfds: 读集合
writefds: 写集合
exceptfds: 异常集合
timeout:
NULL: 永久阻塞
当检测到fd变化的时候返回
struct timeval a;
a.tv_sec = 10;
a.tv_usec = 0;

返回值
文件描述符集类型: fd_set rdset;

文件描述符操作函数:
全部清空
void FD_ZERO(fd_set *set);
从集合中删除某一项
void FD_CLR(int fd, fd_set *set);
将某个文件描述符添加到集合
void FD_SET(int fd, fd_set *set);
判断某个文件描述符是否在集合中
int FD_ISSET(int fd, fd_set *set);

工作过程分析
Linux网络编程(4)TCP状态转换-select-poll_第3张图片
实验代码:
server端:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int main(int argc, const char* argv[])
{
    if(argc < 2)
    {
        printf("eg: ./a.out port\n");
        exit(1);
    }
    struct sockaddr_in serv_addr;
    socklen_t serv_len = sizeof(serv_addr);
    int port = atoi(argv[1]);

    // 创建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    // 初始化服务器 sockaddr_in 
    memset(&serv_addr, 0, serv_len);
    serv_addr.sin_family = AF_INET;                   // 地址族 
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
    serv_addr.sin_port = htons(port);            // 设置端口 
    // 绑定IP和端口
    bind(lfd, (struct sockaddr*)&serv_addr, serv_len);

    // 设置同时监听的最大个数
    listen(lfd, 36);
    printf("Start accept ......\n");

    struct sockaddr_in client_addr;
    socklen_t cli_len = sizeof(client_addr);

    // 最大的文件描述符
    int maxfd = lfd;
    // 文件描述符读集合
    fd_set reads, temp;
    // init
    FD_ZERO(&reads);
    FD_SET(lfd, &reads);

    while(1)
    {
        // 委托内核做IO检测
        temp = reads;
        int ret = select(maxfd+1, &temp, NULL, NULL, NULL);
        if(ret == -1)
        {
            perror("select error");
            exit(1);
        }
        // 客户端发起了新的连接
        if(FD_ISSET(lfd, &temp))
        {
            // 接受连接请求 - accept不阻塞
            int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
            if(cfd == -1)
            {
                perror("accept error");
                exit(1);
            }
            char ip[64];
            printf("new client IP: %s, Port: %d\n", 
                   inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
                   ntohs(client_addr.sin_port));
            // 将cfd加入到待检测的读集合中 - 下一次就可以检测到了
            FD_SET(cfd, &reads);
            // 更新最大的文件描述符
            maxfd = maxfd < cfd ? cfd : maxfd;
        }
        // 已经连接的客户端有数据到达
        for(int i=lfd+1; i<=maxfd; ++i)
        {
            if(FD_ISSET(i, &temp))
            {
                char buf[1024] = {0};
                int len = recv(i, buf, sizeof(buf), 0);
                if(len == -1)
                {
                    perror("recv error");
                    exit(1);
                }
                else if(len == 0)
                {
                    printf("客户端已经断开了连接\n");
                    close(i);
                    // 从读集合中删除
                    FD_CLR(i, &reads);
                }
                else
                {
                    printf("recv buf: %s\n", buf);
                    send(i, buf, strlen(buf)+1, 0);
                }
            }
        }
    }

    close(lfd);
    return 0;
    }

IO多路转接poll实现:
函数原型:
int poll(struct pollfd*fd,nfds_t nfds,int timeout);
函数参数:
pollfd --数组地址
nfds --数组的最大长度,数组中最后一个使用的元素下标加一)
timeout:等待模式
-1:永久阻塞
0:调用完成立即返回
大于0:等待的时长 毫秒
返回值:io发送辩护的文件描述符的个数

pollfd结构体:
struct pollfd
{
int fd; //文件描述符
short events; //等待的事件
short revents; //实际发生的事件
}
Linux网络编程(4)TCP状态转换-select-poll_第4张图片

你可能感兴趣的:(学习笔记,基础知识)