首页 > 其他分享 >socket 的阻塞模式和非阻塞模式

socket 的阻塞模式和非阻塞模式

时间:2023-11-23 11:47:01浏览次数:39  
标签:函数 int 阻塞 send 模式 recv socket

1.socket 的阻塞模式和非阻塞模式

在阻塞和非阻塞模式下,常讨论的具有不同行为表现的 socket 函数一般有 connect、accept、send 和 recv。在 Linux 上对 socket 进行操作时也包括 write 函数和 read 函数。

在 Linux 上, 可以使用 fcntl 函数或 ioctl 函数给创建的 socket 增加 O NONBLOCK 标志来将 socket 设置为非阻塞模式。代码如下:

int oldSocketFlag = fcntl(sockfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
fcntl(sockfd, F_SETFL, newSocketFlag);

ioctl 函数与 fcntl 函数的使用方式基本一致。

Linux 上的 socket 函数也可以直接在创建时将 socket 设置为非阻塞模式,socket 函数签名如下:

int socket(int domain, int type, int protocol);

//给 type 参数增加一个 SOCK_NONBLOCK 标志即可,如:
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

同时,在 Linux 上利用 accept 函数返回的代表与客户端通信的 socket 也提供了一个扩展 accept4,直接将 accept 函数返回的 socket 设置为非阻塞的:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
//只要将 accept4 函数的最后一个参数 flags 设置为 SOCK_NONBLOCK 即可

2.send 和 recv 函数在阻塞和非阻塞模式下的表现

  • 当 socket 是阻塞模式时,继续调用 send/recv 函数,程序会阻塞在 send/recv 调用处;
  • 当 socket 时非阻塞模式时,继续调用 send/recv 函数,send/recv 函数不会阻塞程序执行流,而是立即出错并返回,我们会得到一个相关的错误码,在 Linux 上该错误码为 EWOULDBLOCK 或 EAGAIN。
返回值 返回值的含义
大于0 成功发送(send)或接收(recv)n 字节
0 对端关闭连接
小于0(-1) 出错、被信号中断、对端 TCP 窗口太小导致数据发送不出去或者当前网卡缓冲区已经无数据可接收
返回值和错误码 send函数 recv函数 操作系统
返回-1,错误码时EWOUDBLOCK或EAGAN TCP窗口太小,数据暂时发不出去 在当前内核缓冲区无可读数据 Linux
返回-1,错误码时EINTR 被信号中断,需要重试 被信号中断,需要重试 Linux
返回-1,错误码不是以上3种 出错 出错 Linux

非阻塞模式一般用于支持高并发多 QPS 的场景(如服务器程序),但是这种模式让程序的执行流和控制逻辑变得复杂。

使 send 函数的返回值为 0 的情况:

  • 对端关闭连接时,我们正好尝试调用 send 函数发送数据
  • 本端尝试调用 send 函数发送 0 字节数据。

recv 函数只有在对端关闭连接时才会返回 0,对端发送 0 字节数据,本段的 recv 函数时不会收到 0 字节数据的,即对端操作系统协议栈不会把 0 字节数据发送过来。

3.connect 函数在阻塞和非阻塞模式下的行为

在实际项目中,一般倾向于使用异步 connect 技术(非阻塞 connect),一般有如下步骤:

  • 1.创建 socket,将 socket 设置为非阻塞模式。
  • 2.调用 connect 函数,此时无论 connect 函数是否连接成功,都会返回;如果返回-1,则并不一定表示连接出错,如果此时错误码时 EINPROGRESS,则表示正在尝试连接。
  • 3.调用 select 函数,在指定的时间内判断该 socket 是否可写,如果可写,则说明连接成功,反之认为连接失败。

按上述流程代码编写如下:

点击查看代码
/**
 * 异步的 connect 写法,nonblocking_connect.cpp
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>

#define SERVER_ADDRESS "127.0.0.1"
#define SERVER_PORT 3000
#define SEND_DATA "helloworld"

int main(int argc, char *argv[])
{
    //1.创建一个socket
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    if(clientfd == -1){
        std::cout << "create client socket error." << std::endl;
        return -1;
    }

    //将clientfd设置为非阻塞模式
    int oldSocketFlag = fcntl(clientfd,F_GETFL,0);
    int newSocketFlag = oldSocketFlag | O_NONBLOCK;
    if(fcntl(clientfd, F_SETFL, newSocketFlag) == -1){
        close(clientfd);
        std::cout << "set socket to nonblock error." << std::endl;
        return -1;
    }

    //2.连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);
    serveraddr.sin_port = htons(SERVER_PORT);
    for(;;)
    {
        int ret = connect(clientfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
        if(ret == 0)
        {
            std::cout << "connect to server seccessfully." << std::endl;
            close(clientfd);
            return 0;
        }
        else if(ret == -1)
        {
            if(errno == EINTR)
            {
                //connect 动作被信号中断,重试connect
                std::cout << "connecting interrupted by signal, try again." << std::endl;
                continue;
            }
            else if(errno == EINPROGRESS)
            {
                //正在尝试连接
                break;
            }
            else
            {
                //真的出错了
                close(clientfd);
                return -1;
            }
        }
    }

    fd_set writeset;
    FD_ZERO(&writeset);
    FD_SET(clientfd, &writeset);
    struct timeval tv;
    tv.tv_sec = 3;
    tv.tv_usec = 0;
    //3.调用select函数判断socket是否可写
    if(select(clientfd+1,NULL,&writeset,NULL,&tv) != 1)
    {
        std::cout << "[select] connect to server error." << std::endl;
    }

    int err;
    socklen_t len = static_cast<socklen_t>(sizeof err);
    //4.调用getsockopt检测此时socket是否出错
    if(::getsockopt(clientfd, SOL_SOCKET,SO_ERROR, &err, &len) < 0)
    {
        close(clientfd);
        return -1;
    }

    if(err == 0)
    {
        std::cout << "connect to server successfully." << std::endl;
    }
    else{
        std::cout << "connect to server error." << std::endl;
    }


    close(clientfd);

    return 0;
}

在 Linux 上,一个 socket 在没有建立连接之前,用 select 函数检测其是否可写,我们也会得到可写的结果,所以,在 connect 之后,不仅要用 select 检测是否可写,还要调用 getsockopt 检测此时 socket 是否出错,通过错误码来检测和确定是否连接上,错误码为 0 时表示连接上,反之表示未连接上。可以使用 poll 函数代替 select 函数判断 socket 是否可写;

标签:函数,int,阻塞,send,模式,recv,socket
From: https://www.cnblogs.com/ljx-0122/p/17849768.html

相关文章

  • 设计模式
    美团弟弟快看工厂模式(https://www.ddkk.com/zhuanlan/design/java/5.html)策略模式(https://www.ddkk.com/zhuanlan/design/java/27.html)......
  • 学习随笔(设计模式:状态模式)
    内容今天学习了设计模式中的状态模式。1.状态模式,当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。收获1.如果软件中需要用很多枚举表示不同的状态,那么就可以采用状态模式,可以让代码变得更简单,新增状态时扩展性也会更好。2.先抽象出所有状态的基类......
  • 设计模式(十四)命令
    一、定义将一个请求封装为一个对象,从而可以用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事物(Transaction)模式。二、描述命令模式的本质是对请求进行封装,一个请求对应一个命令,将发出命......
  • 中介者模式--Java实现
    具体代码//AbstractChatroom.javapackageorg.example.test017;importjavax.swing.plaf.basic.BasicTreeUI;publicabstractclassAbstractChatroom{publicabstractvoidregister(Membermember);publicabstractvoidsendText(Stringfrom,Stringto,Str......
  • 备忘录模式--Java实现
    具体代码//Memento.javapackageorg.example.test018;publicclassMemento{publicStringgetAccount(){returnaccount;}publicvoidsetAccount(Stringaccount){this.account=account;}publicStringgetPassword(){......
  • 学习随笔(设计模式:抽象工厂模式)
    内容今天学习了抽象工厂模式。1.抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。2.听起来有一些复杂,主要实现的功能就是在尽可能保证开放-封闭的原则下兼容具体的抽象动作。3.最终可以采用配置文件+反射+抽象工厂来实现对抽象产品的设......
  • 【流畅的Python】2.6 序列模式匹配
    2.6序列模式匹配这一小节围绕Python3.10推出的模式匹配功能展开,其实就是新增的match/case语句。因为本小节属于第二章“丰富的序列”,所以这里只介绍了关于序列的模式匹配。在其他章节还有关于模式匹配更多的内容:2.6序列模式匹配3.3使用模式匹配处理映射5.8模式匹配类实......
  • Java——设计模式
    一、概述设计模式是历代程序员总结出的经验二、分类创建型模式:简单工厂模式工厂方法模式单例模式:饿汉式(开发)懒汉式(面试)行为型模式结构型模式三、简单工厂模式一个工厂中可以创建很多各种各样的对象缺陷:如果有新......
  • 关于阻塞多线程
    关于阻塞多线程同步方式理解:一个循环循环100次。多线程方式理解:开10个循环同时执行循环,每个循环循环10次。......
  • 设计模式学习每日总结-第十天
    第十天装饰模式:动态地给一个对象增加额外职责。有点:更灵活地增加子类缺点:小子类多,占资源  ......