6、网络编程——非阻塞

一、socket的四种IO模型

1、阻塞型

最常用/最简单/效率低

函数本身不具备阻塞属性,而是由于文件描述符本身导致函数阻塞。

在默认情况下Linux建立的socket套接都是阻塞的

2、非阻塞

可以设置进程不阻塞在IO操作上,需要轮询

占用CPU资源较大

3、多路复用

同时对多个IO进行操作

可以设置在规定的时间内检测数据是否到达

4、信号驱动型IO

属于一步通信方式

当socket中有数据到达时,通过发送信号告知用户

 二、阻塞性IO

1、读阻塞

当套接字接收缓冲区中没有数据可以读取时调用 如 read/recv/recvfrom就会导致阻塞

当有数据到达时,内核便会去唤醒进程,通过read等函数来访问数据

如果进程阻塞过程中意外,那么进程将永远阻塞下去。

2、写阻塞

发生写阻塞的机会比较少,一般出现在写缓冲区无法写入即将写入的数据时

当无法写入数据时便会进入阻塞等待

一旦发送的缓冲区拥有足够的空间,则内核会唤醒对应的进程进行写入操作

而UDP协议中并不存在发送缓冲区满的情况,UDP套接字执行写操作时永远不会发生阻塞。

三、非阻塞IO

如果有一个IO操作不能马上完成则系统则会让我们的进程进入睡眠状态等待

当我们将一个套接字设置为非阻塞模式时,则系统不会让我们的进程进入睡眠等待而是直接返回错误

当一个程序使用非阻塞模式的套接字,他需要使用循环来不断检查文件描述如是否有数据可读

应用程序不停循环判断将会消耗非常大的CPU资源,一般不推荐使用

 四、非阻塞的实现方法

        当我们一开始建立一个套接字描述符时,系统内核会默认设置为阻塞型IO,我们可以使用函数 来设置套接字为非阻塞状态。

1、fcntl(文件描述词操作)

fcntl ( 文件描述词操作 )
    头文件:
        #include <unistd.h>
        #include <fcntl.h>
    定义函数 :
        int fcntl(int fd, int cmd);
        int fcntl(int fd, int cmd, long arg);
        int fcntl(int fd, int cmd, struct flock * lock);
参数分析:
        fd --> 需要设置的文件描述符
        cmd --> 设置的功能
        F_DUPFD 用来查找大于或等于参数 arg 的最小且仍未使用的文件描述词, 并且复制参数 fd 的
        F_GETFD 取得 close-on-exec 旗标. 若此旗标的 FD_CLOEXEC 位为 0, 代表在调用 exec(
        F_SETFD 设置 close-on-exec 旗标. 该旗标以参数 arg 的 FD_CLOEXEC 位决定.
        F_GETFL 取得文件描述词状态旗标, 此旗标为 open()的参数 flags.
        F_SETFL 设置文件描述词状态旗标, 参数 arg 为新旗标, 但只允许 O_APPEND、O_NONBLOCK
        F_GETLK 取得文件锁定的状态.
        F_SETLK 设置文件锁定的状态. 此时 flcok 结构的 l_type 值必须是 F_RDLCK、F_WRLCK 或
        F_UNLCK. 如果无法建立锁定, 则返回-1, 错误代码为 EACCES 或 EAGAIN.
        F_SETLKW 同 F_SETLK 作用相同, 但是无法建立锁定时, 此调用会一直等到锁定动作成功为止

返回值:成功返回0,失败返回-1

2、结构体的定义

struct flcok
{
    short int l_type; //锁定的状态
    short int l_whence; //决定 l_start 位置
    off_t l_start; //锁定区域的开头位置
    off_t l_len; //锁定区域的大小
    pid_t l_pid; //锁定动作的进程
};
    l_type 有三种状态:
    F_RDLCK 建立一个供读取用的锁定
    F_WRLCK 建立一个供写入用的锁定
    F_UNLCK 删除之前建立的锁定
    l_whence 也有三种方式:
    SEEK_SET 以文件开头为锁定的起始位置.
    SEEK_CUR 以目前文件读写位置为锁定的起始位置
    SEEK_END 以文件结尾为锁定的起始位置.

3、操作的例子

int socket_fd = socket(...........); // 创建一个套接字描述符
int state = fcntl(socket_fd , F_GETFL , 0) ; // 获得当前描述符的旗标
state |= O_NONBLOCK ; // 在原基础上增加非阻塞属性
fcntl(scoket_fd , F_SETFL , state ); // 把配置的好的旗标重新设置回描述符中

五、用非阻塞的形式写一个服务器

使用非阻塞的IO来实现一个服务器,可以接收都哦个客户端的连接(最多20个客户端),并在收到消息后打印消息内容。

非阻塞的代码

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <fcntl.h>


// 设置一个结构体
typedef struct clinet
{
    int fd[20] ; // 用来保存每一个在线的用户的描述符
    int num ; // 记录当前在线人数
}Clinet ;


int add_cli(Clinet * P_cli ,int connect_fd)
{
    if ( P_cli->num >= 20 )
    {
        return -1 ;
    }
    
    // 把连接的套接字先设置为非阻塞状态
    int state = fcntl(connect_fd , F_GETFL); // 获取 描述符connect_fd 的属性 
    state |= O_NONBLOCK ; // 设置属性信息, 添加上非阻塞状态
    fcntl(connect_fd , F_SETFL ,state  ); // 把配置好的属性,设置回到 connect_fd 描述符中

    (P_cli->fd)[P_cli->num] = connect_fd ; // 把新连接上来的客户端的描述符保存下来
    P_cli->num ++ ; // 数组下标自加

    return 0 ;
}


int main(int argc, char const *argv[])
{
    // 创建一个套接字 (购买一台手机)
    int sock_fd = socket( AF_INET , SOCK_STREAM , 0 ); // 使用IPV4协议簇, 流式套接字(TCP)
    if (-1 == sock_fd)
    {
        perror("socket rror");
        return -1 ;
    }
    

    // 配置自己的IP和端口号等信息
    // struct sockaddr_in   // IPV4地址结构体
    // {
    //     u_short sin_family;// 地址族
    //     u_short sin_port;// 端口
    //     struct in_addr sin_addr;// IPV4 地址
    //     char sin_zero[8];
    // };
    int addrlen = sizeof(struct sockaddr_in);
    struct sockaddr_in my_addr = {
        .sin_addr.s_addr = htonl(INADDR_ANY) , // 设置服务器的地址, INADDR_ANY 指本机中任何一个地址
        .sin_family = AF_INET , // 使用 IPV4 协议簇
        .sin_port = htons(65000) // 设置服务器的端口号为  65000 
    };

    // 绑定地址信息 (IP + 端口号 + 协议簇)
    int ret_val =  bind(  sock_fd,  (struct sockaddr *)&my_addr, addrlen);
    if (ret_val == -1 )
    {
        perror("bind error");
        return -1 ;
    }else{
        printf(" bind succeed !!\n") ;
    }

    // 设置监听 (打开铃声)
    if(listen( sock_fd,  5 )) // 设置sock_fd 为监听套接字, 同时可以接收连接请求数为  5 + 4(默认值) 
    {
        perror("listen error");
        return -1 ;
    }

    // 设置监听套接字为非阻塞属性
    int state ;
    state = fcntl(sock_fd , F_GETFL); // 获取 描述符sock_fd 的属性 
    state |= O_NONBLOCK ; // 设置属性信息, 添加上非阻塞状态
    fcntl(sock_fd , F_SETFL ,state  ); // 把配置好的属性,设置回到  sock_fd 描述符中

    int connect_fd = -1 ;
    Clinet * P_cli = calloc(1, sizeof(Clinet));
    char * msg = calloc(1,128);
    while(1)
    {
        // 等待连接
        struct sockaddr_in from_addr;  // 由于前面已经 把 监听套接字sock_fd 设置为非阻塞状态
        connect_fd = accept( sock_fd , (struct sockaddr *)&from_addr, &addrlen);
        //由于是非阻塞状态,所以一开始为-1是正常的,-1后要重新来
        if ( connect_fd > 0) // 如果描述符大于0 则表示有客户端连接
        {
            printf("新客户端:%d 连接成功!!\n" , connect_fd);
            add_cli(P_cli , connect_fd);
        }
        
        // 轮询是否有数据到达
        for (int i = 0; i < P_cli->num ; i++)
        {
            // printf("LINE:%d\n" , __LINE__);
            bzero(msg , 128) ;
            // recv 的返回值与 read 返回值一致, 成功则返回实际读取的字节数
            ret_val = recv((P_cli->fd)[i] , msg , 128 , 0 );
            if (ret_val > 0)
            {
                printf("from %d , msg : %s" , P_cli->fd[i] , msg );
            }       
        }
    }
    return 0 ;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值