精华内容
下载资源
问答
  • 2021-05-21 07:00:59

    如果客户端想连接一个服务器端,但是不能肯定服务器端是否存在,如果存在了是否能连上,怎么判断呢?

    connect函数的默认行为是阻塞的,会一直等待在那里。为了判断各种情况,以及遇到错误时结束连接,我们需要使用非阻塞的socket。一个例子程序:

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    int main(void){

    int s,r,ret,len,error;

    char buf[20];

    struct sockaddr_in sock;

    fd_set rset,wset;

    struct timeval tv;

    tv.tv_sec=10;

    s=socket(AF_INET,SOCK_STREAM,0);

    if(-1==s){ printf("socket() failed/n"); return 1;

    }

    if(0>fcntl(s,F_SETFL,fcntl(s,F_GETFL,0)|O_NONBLOCK)){

    printf("fcntl failed/n");

    return 1; }

    sock.sin_family=PF_INET;

    sock.sin_port =htons(10080);

    sock.sin_addr.s_addr=inet_addr("197.0.0.9");

    if(-1==(ret=connect(s,(struct

    sockaddr*)&sock,sizeof(struct

    sockaddr_in)))){

    if(EINPROGRESS!=errno){

    printf("connect()

    failed:%s/n",strerror(errno)); return 1; }

    }

    if(ret==0){ printf("connection ok at once/n");

    close(s); return 0; }

    FD_ZERO(&rset);

    FD_SET(s,&rset);

    wset=rset;

    if(-1==(ret=select(s+1,&rset,&wset,NULL,&tv))){

    close(s);

    printf("select

    error/n");

    return 1; }

    if(0==ret){ close(s); printf("timeout/n"); return

    1; }

    if(FD_ISSET(s,&rset)||FD_ISSET(s,&rset)){

    len=sizeof(error);

    errno=0;

    if(0>(ret=getsockopt(s,SOL_SOCKET,SO_ERROR,(void*)&error,&len))){

    printf("getsockopt

    error:%s/n",strerror(error));

    close(s);

    return 1;

    }

    if(error!=0){ printf("getsockopt set

    error:%s/n",strerror(error)); close(s); return 1;

    }

    printf("getsockopt success/n");

    }else{ close(s); printf("FD_ISSET error/n");

    return 1; }

    if(-1==(ret=read(s,buf,sizeof(buf)))){

    printf("read() failed/n"); return 1; }

    printf("ret=%d/n",ret);

    buf[ret-1]='/0';

    printf("read:%s/n",buf);

    close(s);

    return 0;

    }

    解释:

    1. socket()得到的句柄使用fcntl函数设置了非阻塞的特性。

    程序中的ip地址197.0.0.9并不存在,connect失败,select会等待(10秒),一直到timeval超时结束。

    2.

    如果把ip地址改为一个存在的地址,例如127.0.0,1这样,那么connect函数返回的错误值就是ECONNREFUSED。并且这种情况下select函数会立刻返回,getsockopt函数设置&error,这个可以被strerror()函数打印。----tcp协议是这么规定的。如果连接一个存在的ip,但是这个ip的机器没有监听你要连接的端口,它会返回rst报文。(当然首先是那个ip的机器支持tcp/ip协议,不过这点现在机器都支持)如果发生连接错误,

    也会让socket可读和可写.

    服务器没有启动, 所以肯定会发生连接错误,这时,

    连接已可读可写, select没有必要等待超时.

    3. connect函数失败以后,socket句柄的状态是未定义的,应该close(s)再生成一个。

    select的作用是检查描述符的状态,是否有数据过来,比如

    要查读描述符,那就检查读描述符的状态,有数据来,就会返回,否则,一直等待,知道你设定的超时时间退出。

    而你上面的connect的问题,不是这个。

    对于tcp/ip,每个机器只要支持tcp/ip协议,那么它一启动,就相当于有个负责tcp/ip的进程在后台运行着。再说connect,connect的过程

    就是 发包收包的过程,用抓包工具可以看到。

    connect的过程是:比如a去连b,以下是正常连接的情况。

    a:首先发送一个数据包给b

    b:收到a的包后给a回一个包。

    a:收到b回的包后再给b回一个包。

    这三次完成后,就是connect成功连接上,返回了。

    上面的程序改为连一个存在的ip(127.0.0.1),但是对方没有在你要连得端口监听会像下面这样:

    a:发一个包给b

    b:发现没有监听,回rst给a

    然后connect没连上结束了。

    4. 其他的分析:

    最初的地址如果用 INADDR_ANY,会报地址错误。

    如果用合法地址,对方没有侦听,报连接被拒绝。Connection refused

    如果对方在听了,但没有向你发送消息,select 认为文件描述符是不可读的。

    如果三路握手完成,则 select 认为文件描述符是可写的。

    写一个服务去听,客户在 select 时把 *wset 参数 设

    NULL 就可以观察到 timeout。

    更多相关内容
  • NULL 博文链接:https://daojin.iteye.com/blog/720402
  • 非阻塞socket编程

    千次阅读 2018-10-02 16:31:53
    一. 阻塞、非阻塞、异步 阻塞:阻塞调用是指调用结果返回之前,当前...非阻塞非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。epoll工作在非阻塞模式时,才会发挥作...

    一. 阻塞、非阻塞、异步

    阻塞:阻塞调用是指调用结果返回之前,当前线程会被挂起。该进程被标记为睡眠状态并被调度出去。函数只有在得到结果之后才会返回。当socket工作在阻塞模式的时候, 如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。

    非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。epoll工作在非阻塞模式时,才会发挥作用。

    一般有三种操作IO的方式:

    blocking IO: 发起IO操作后阻塞当前线程直到IO结束,标准的同步IO,如默认行为的posix readwrite

    non-blocking IO: 发起IO操作后不阻塞,用户可阻塞等待多个IO操作同时结束。non-blocking也是一种同步IO:“批量的同步”。如linux下的poll,selectepoll,BSD下的kqueue

    asynchronous IO: 发起IO操作后不阻塞,用户得递一个回调待IO结束后被调用。如windows下的OVERLAPPEDIOCP。linux的native AIO只对文件有效。

    二. 非阻塞Socket

    正常情况下,socket工作在阻塞模式下,在调用accept,connect,read,write等函数时,都是阻塞方式,直到读到数据才会返回。但是,如果将socket设置为非阻塞状态,那么这么些函数就会立即返回,不会阻塞当前线程。
    设置非阻塞socket的方法是:

    int SetNonBlock(int iSock)
    {
        int iFlags;
    
        iFlags = fcntl(iSock, F_GETFL, 0);
        iFlags |= O_NONBLOCK;
        iFlags |= O_NDELAY;
        int ret = fcntl(iSock, F_SETFL, iFlags);
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    三. 非阻塞accept

    tcp的socket一旦通过listen()设置为server后,就只能通过accept()函数,被动地接受来自客户端的connect请求。进程对accept()的调用是阻塞的,就是说如果没有连接请求就会进入睡眠等待,直到有请求连接,接受了请求(或者超过了预定的等待时间)才会返回。
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    返回值是一个新的套接字描述符,它代表的是和客户端的新的连接,可以把它理解成是一个客户端的socket,这个socket包含的是客户端的ip和port信息 。失败返回-1, 错误原因存于errno 中。
    之后的read和write函数中的fd都是指这个 new_fd。

    阻塞模式下调用accept()函数,而且没有新连接时,进程会进入睡眠状态
    非阻塞模式下调用accept()函数,而且没有新连接时,将返回EWOULDBLOCK(11)错误

    可以用以下代码来测试:

    int SetNonBlock(int iSock)
    {
        int iFlags;
    
        iFlags = fcntl(iSock, F_GETFL, 0);
        iFlags |= O_NONBLOCK;
        iFlags |= O_NDELAY;
        int ret = fcntl(iSock, F_SETFL, iFlags);
        return ret;
    }
    
    int main(int argc, char* argv[])
    {
        int listenfd, connfd;
       
        struct sockaddr_in serveraddr;
        struct sockaddr_in clientaddr;
        socklen_t clilen;
    
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
        SetNonBlock(listenfd);
    
        //listenfd绑定ip地址
        bzero(&serveraddr, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        char local_addr[20]="127.0.0.1";
        inet_aton(local_addr,&(serveraddr.sin_addr));
        serveraddr.sin_port=htons(8000);
        
        //bind和listen不是阻塞函数
        bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
        listen(listenfd, 20);
    
        cout << "server listening ..."  << endl;
    
        int ret = -1;
    
        while(1)
        {
            connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);//以后读写都用这个返回的fd
            cout<<"connfd = "<<connfd<<", errno = "<<errno<<endl;
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    如果设置为非阻塞,accept会立即返回,打印出错误信息,errno=11,有新的连接时,连接会成功;
    如果不设置非阻塞,进程就阻塞在accept那里,直到有新的连接到来。

    非阻塞模式下,accept函数可以与epoll结合,实现等待。具体可以见另一篇博文:
    http://blog.csdn.net/okiwilldoit/article/details/50469515

    四. 非阻塞connect

    在阻塞模式下,客户端调用connect()函数将激发TCP的三路握手过程,但仅在连接建立成功或出错时才返回。
    非阻塞工作模式,调用connect()函数会立刻返回EINPROCESS错误,但TCP通信的三路握手过程正在进行,所以可以使用select函数来检查这个连接是否建立成功。
    源自Berkeley的实现有两条与select函数和非阻塞相关的规则:
    1>.当连接成功建立时,描述字变成可写。
    2>.当连接建立出错时,描述字变成即可读又可写。getsockopt()函数的errno == 0表示只可写。

    处理非阻塞 connect 的步骤:
    (1) 创建socket,并利用fcntl将其设置为非阻塞
    (2) 调用connect函数,如果返回0,则连接建立;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立。
    (3) 为了控制连接建立时间,将该socket描述符加入到select的可写集合中,采用select函数设定超时。
    (4) 如果规定时间内成功建立,则描述符变为可写;否则,采用getsockopt函数捕获错误信息。当errno == 0表示只可写。

    实例:
    Redis客户端CLI (command line interface),位于源代码的src/deps/hiredis下面。
    实际上,不仅是Redis客户端,其他类似的client/server架构中,client均可采用非阻塞式connect实现。
    https://github.com/redis/hiredis/blob/master/net.c
    参考函数:_redisContextConnectTcp()

    当然,也可以用poll或epoll来代替select。
    非阻塞模式 connect() + select()代码:

    int RouterNode::Connect()
    {
    	sockaddr_in servaddr = {0};
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, ip_.c_str(), &servaddr.sin_addr);
        servaddr.sin_port = htons(port_);
    
    	int ret = ::connect(fd_, (struct sockaddr *)&servaddr, sizeof(servaddr));
    	
    	if(ret == 0)
    	{
    		is_connected_ = true;
    		return 0;
    	}
    	
    	int error = 0;
    	socklen_t len = sizeof (error);
    	
    	if(errno != EINPROGRESS)
    	{
    		goto __fail;
    	}
    	
    	fd_set wset;//写集合
    	FD_ZERO(&wset);
    	FD_SET(fd_, &wset);
    
    	struct timeval tval;
    	tval.tv_sec = 3;//3s
    	tval.tv_usec = 0; 
    
    	if (select(fd_ + 1, NULL, &wset, NULL, &tval) == -1) //出错、超时,连接失败
    	{
    		goto __fail; 
    	}
    	
    	if(!FD_ISSET(fd_, &wset))//不可写
    	{
    		goto __fail;
    	}
    
    	if (getsockopt(fd_, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
    	{
    		goto __fail;
    	}
    
    	if(error)
    	{
    		goto __fail;
    	}
    	
    	is_connected_ = true;
    	return 0;
    
    __fail:
    	close(fd_);
    	return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    五. 非阻塞write

    对于写操作write,非阻塞socket在发送缓冲区没有空间时会直接返回-1,错误号EWOULDBLOCK或EAGAIN,表示没有空间可写数据,如果错误号是别的值,则表明发送失败。
    如果发送缓冲区中有足够空间或者是不足以拷贝所有待发送数据的空间的话,则拷贝前面N个能够容纳的数据,返回实际拷贝的字节数。
    而对于阻塞Socket而言,如果发送缓冲区没有空间或者空间不足的话,write操作会直接阻塞住,如果有足够空间,则拷贝所有数据到发送缓冲区,然后返回。
    实现代码:

    /**
    * 返回-1:失败
    * 返回>0: 成功
    */
    int WriteNonBlock(int fd, const char* send_buf, size_t send_len)
    {
        int sentlen = 0;//已经发送的长度
    
        while(sentlen < send_len)
        {
            int ret = write(fd, send_buf+sentlen, send_len-sentlen);
            if(ret <= 0)
            {
                if(ret < 0 && errno == EINTR)
                {
                    continue;
                }
                else//遇到EAGAIN直接退出
                {
                    break;
                }
            }
            sentlen += ret;
        }
        return sentlen;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    六. 非阻塞read

    对于阻塞的socket,当socket的接收缓冲区中没有数据时,read调用会一直阻塞住,直到有数据到来才返回。
    当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的字节数。
    当sockt的接收缓冲区中的数据大于期望读取的字节数时,读取期望读取的字节数,返回实际读取的长度。

    对于非阻塞socket而言,socket的接收缓冲区中有没有数据,read调用都会立刻返回。
    接收缓冲区中有数据时,与阻塞socket有数据的情况是一样的,如果接收缓冲区中没有数据,则返回-1,
    错误号为EWOULDBLOCK或EAGAIN,表示该操作本来应该阻塞的,但是由于本socket为非阻塞的socket,
    因此立刻返回,遇到这样的情况,可以在下次接着去尝试读取。如果返回值是其它负值,则表明读取错误。
    实现代码:

    /**
    * 返回-1:失败
    * 返回>0: 成功
    */
    int ReadNonBlock(int fd, char* recv_buf, size_t recv_len)
    {
        int readlen = 0;//已经读到的长度
        while(readlen < recv_len)
        {
            int ret = read(fd, recv_buf+readlen, recv_len-readlen);
            if(ret == 0)//已到达文件末尾
            {
                return readlen;
            }
            else if(ret > 0)
            {
                readlen += ret;
            }       
            else if(errno == EINTR)
            {
                continue;
            }
            else//遇到EAGAIN直接退出
            {
                break;
            }
        }
    
        return readlen;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    recvfrom,sendto等函数也是同样类似的方法。

    --------------------- 本文来自 okiwilldoit 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/okiwilldoit/article/details/51015444?utm_source=copy 

    展开全文
  • 在用户无法直接套用简单的 socket demo 时,RT-Thread 提供基于多线程的非阻塞 socket 编程示例,方便用户进行应用程序开发。 在 RT-Thread 使用 socket 网络编程时,当一个任务调用 socket的 recv()函数接收数据时...

    多线程非阻塞网络编程
    简介:
    在实际开发中,往往要求网络传输时不能阻塞当前线程,以致无法及时处理其他消息。在用户无法直接套用简单的 socket demo 时,RT-Thread 提供基于多线程的非阻塞 socket 编程示例,方便用户进行应用程序开发。

    在 RT-Thread 使用 socket 网络编程时,当一个任务调用 socket的 recv()函数接收数据时,如果 socket 上并没有接收到数据,这个任务将阻塞在这个 recv() 函数里。这个时候,这个任务想要处理一些其他事情,例如进行一些数据采集,发送一些额外数据到网络上等,将变得不可能了。与此同时,其他线程也需要将数据上传同一个服务器,如果直接多个线程共同使用一个 socket 操作,这将会破坏底层 lwip 的消息事件模型。

    在这里插入图片描述

    客户端使用流程:

    socket() 创建一个 socket,返回套接字的描述符,并为其分配系统资源。
    connect() 向服务器发出连接请求。
    send()/recv() 与服务器进行通信。
    closesocket() 关闭 socket,回收资源。
    服务器使用流程:

    socket() 创建一个 socket,返回套接字的描述符,并为其分配系统资源。
    bind() 将套接字绑定到一个本地地址和端口上。
    listen() 将套接字设为监听模式并设置监听数量,准备接收客户端请求。
    accept() 等待监听的客户端发起连接,并返回已接受连接的新套接字描述符。
    recv()/send() 用新套接字与客户端进行通信。
    closesocket() 关闭 socket,回收资源。

    非阻塞 socket 编程简介
    在 RT-Thread 中,自 v3.0.0 以来更标准化,支持更多的 POSIX API。这其中就包括 poll / select 接口实现,并且可以进行 socket 和设备文件的联合 poll / select。

    在这里插入图片描述

    图中存在有三个线程:应用线程 thread1、thread2 和客户端线程 thread client,其中 thread client 完成 select 功能。

    数据发送过程:
    应用线程通过 pipe 往 thread client 发送数据 data1,select 探测到 pipe 有数据可读,thread client 被唤醒,然后读取 pipe 中的数据并通过 TCP socket 发送到 server
    数据接收过程:
    server 通过 TCP socket 发送数据 data2 到 thread client,select 探测到 socket 有数据可读,thread client 被唤醒,thread client 可以获得接收到的数据

    select

    select() 可以阻塞地同时探测一组支持非阻塞的 I / O 设备是否有事件发生(如可读,可写,出现异常等等),直至某一个设备触发了事件或者超过了指定的等待时间。此时我们可以把需要的数据源通道放到 select 的探测范围内,只要相应的数据源准备好 select 就会返回,这时就能无阻塞地读取到数据。

    select() 主要用来处理 I / O 多路复用的情况,适用如下场合:

    客户端处理多个描述符时(一般是交互式输入和网络套接口)
    服务器既要处理监听套接口,又要处理已连接套接口
    服务器既要处理 TCP,又要处理 UDP
    服务器要处理多个服务或多个协议

    select()函数原型及介绍如下所示:

    int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);
    参数 描述
    nfds 集合中所有文件描述符的范围,即所有文件描述符的最大值加1
    readfds 需要监视读变化的文件描述符集合
    writefds 需要监视写变化的文件描述符集合
    errorfds 需要监视出现异常的文件描述符集合
    timeout select 的超时时间
    返回 –
    正值 监视的文件集合出现可读写事件或异常事件
    0 等待超时,没有可读写或异常的事件
    负值 select 出现错误

    pipe

    pipe 是一个基于文件描述符的单向数据通道,可用于线程间的通信。

    在 RT-Thread 里面,pipe 支持文件描述符的形式操作,而且 pipe 不需要控制协议,操作简单。
    在 msh />中,输入 list_fd 可查看当前打开的文件描述符,详情如下:
    msh />list_fd
    fd type ref magic path


    0 file 1 fdfd /uart0
    1 socket 1 fdfd
    2 file 1 fdfd /pipe0
    3 file 1 fdfd /pipe0
    msh />

    tcpclient 示例

    tcpclient.c 是上文提出的 select、pipe 方案的具体实现代码,该源码采用面向对象的思想实现,提供 TCP 连接、发送、关闭以及注册接收回调四个 API 提供用户使用。

    下面的序列图为 tcpclient.c的运行流程:
    在这里插入图片描述

    各流程详细解释如下所示:

    调用 rt_tcpclient_start() 设置服务器 ip 地址 & 端口号,以及完成 pipe、socket 初始化和 TCP 连接、select 配置等工作。
    注册接收回调函数 rt_tc_rx_cb()。
    调用 rt_tcpclient_send() 通过 pipe 发送数据(图中绿线表示 select 探测到 pipe 可读事件)。
    图中绿线表示 select 探测到 pipe 可读事件, tcpclient 被唤醒并读取 pipe 的数据。
    tcpclient 通过 socket 发送数据给 server。
    server 通过 socket 发送数据给 tcpclient。
    图中蓝线表示 select 探测到 socket 可读事件,tcpclient 被唤醒并读取 socket 的数据。
    app 通过 rt_tc_rx_cb() 获得 tcpclient 读取到的数据。
    通信完毕,app 调用 rt_tcpclient_close() 关闭 pipe、socket,并清理相关资源。

    源码详解

    下面代码的核心代码:

    static void select_handle(rt_tcpclient_t *thiz, char *pipe_buff, char *sock_buff)
    {
        fd_set fds;
    
        rt_int32_t max_fd = 0, res = 0;
        max_fd = MAX_VAL(thiz->sock_fd, thiz->pipe_read_fd) + 1;
    
        /* 清空可读事件描述符列表 */
        FD_ZERO(&fds);
    
        while (1)
        {
            /* 将需要监听可读事件的描述符加入列表 */
            FD_SET(thiz->sock_fd, &fds);
            FD_SET(thiz->pipe_read_fd, &fds);
    
            /* 等待设定的网络描述符有事件发生 */
            res = select(max_fd, &fds, RT_NULL, RT_NULL, RT_NULL);
    
            /* select 返回错误及超时处理 */
            EXCEPTION_HANDLE(res, "select handle", "error", "timeout");
    
             /* 查看 sock 描述符上有没有发生可读事件 */
            if (FD_ISSET(thiz->sock_fd, &fds))
            {
                /* 从 sock 连接中接收最大BUFSZ - 1字节数据 */
                res = recv(thiz->sock_fd, sock_buff, BUFF_SIZE, 0);
    
                /* recv 返回异常 */
                EXCEPTION_HANDLE(res, "socket recv handle", "error", "TCP disconnected");
    
                /* 有接收到数据,把末端清零 */
                sock_buff[res] = '\0';
    
                /* 通过回调函数的方式,数据发给 thread1 */
                RX_CB_HANDLE(sock_buff, res);
    
                /* 如果接收的是exit,关闭这个连接 */
                EXIT_HANDLE(sock_buff);
            }
    
            /* 查看 pipe 描述符上有没有发生可读事件 */
            if (FD_ISSET(thiz->pipe_read_fd, &fds))
            {
                /* 从 pipe 连接中接收最大BUFSZ - 1字节数据 */
                res = read(thiz->pipe_read_fd, pipe_buff, BUFF_SIZE);
    
                /* recv 返回异常 */
                EXCEPTION_HANDLE(res, "pipe recv handle", "error", RT_NULL);
    
                /* 有接收到数据,把末端清零 */
                pipe_buff[res] = '\0';
    
                /* 读取 pipe 的数据,转发给 server */
                send(thiz->sock_fd, pipe_buff, res, 0);
    
                /* recv 返回异常 */
                EXCEPTION_HANDLE(res, "socket write handle", "error", "warning");
    
                /* 如果接收的是 exit,关闭这个连接 */
                EXIT_HANDLE(pipe_buff);
            }
        }
    
    exit:
        /* 释放接收缓冲 */
        free(pipe_buff);
        free(sock_buff);
    }
    

    这段代码是 tcpclient 线程的核心部分,按照例程配置 select,根据 FD_ISSET() 宏检查描述符。

    假如 socket 有数据可读,采用回调函数的方式把数据发送给应用线程。
    假如 pipe 有数据可读,处理数据,通过 socket 发送到服务器。

    准备工作

    首先在 github 上拉取 tcpclient.c 的源码,然后将tcpclient 文件夹放在 rt-thread\bsp\qemu-vexpress-a9目录下,详情如下:
    在这里插入图片描述
    在 Env 里使用 scons 命令编译 QEMU 工程,详情如下:
    在这里插入图片描述
    在 Env 里使用 .\qemu.bat 命令启动,详情如下:

    在这里插入图片描述
    QEMU 成功启动,下面来介绍代码运行情况。

    设置网络调试助手端口号,详情如下:
    在这里插入图片描述

    在 cmd 命令行输入 ipconfig 查看本机 ip 地址,详情如下:

    ipconfig

    IPv4 Address. . . . . . . . . . . : 192.168.12.53

    example 代码中通过 rt_tcpclient_start() API 设置服务器 IP 地址和端口号,详情如下:

    rt_tcpclient_start(“192.168.12.53”, 9008);
    注意:
    这里需要根据自己的环境设置 ip 地址和端口号!!!

    运行效果

    在 example.c 里建立两个线程,一个是 thread1,另一个是 thread2,两个线程交替给服务端发送数据。服务端每秒钟往客户端发送数据。
    在这里插入图片描述
    网络助手发送 i am server ,thread1 接收并且打印出来,详情如下:
    msh />D/tc_rx_cb [-30-01-01 00:00:00 tcpc] (packages\tcpclient\examples\tcpclient_example.c:52)recv data: i am server

    总结

    select() 也是阻塞模式,它的好处在于可以同时选择多个数据源通道:只要通道里数据有效时,就可以进行操作;在没有数据需要处理时,则操作线程会被挂起。
    通过使用 pipe / select 的方式,让 tcpclient 网络任务实现了在等待网络数据的同时额外处理其他消息的目的。

    展开全文
  • 所以在数据库编程之前需要掌握基础的SQL 命令语句。  数据库驱动层  相关联的类包含了QSqlDriver ,QSqlDriverCreatpor ,QSqlDriverCreatorBase,QSqlDriverPlugin与QSqlResult 。  数据库的应用程序接口层 ...
  • Linux下基于C/C++的Socket阻塞和异步编程实例
  • NIO实现非阻塞Socket编程

    千次阅读 2017-07-14 16:41:10
    JAVA的NIO为非阻塞Socket通信提供了下面几个特殊类: Selector:所有希望采用通过非阻塞式方式通信的Channel都应该注册到Selector对象,可以通过调用此类的Open()静态方法来创建Selector实例。 Selector...

    前言
    基于阿里面试时,面试官问我,我做的聊天项目里,考虑过性能没有,是怎么解决程序卡顿现象的,针对客户端,当在发送文件时,如果卡顿,怎么办,同时想聊天,当时程序我是基于多线程实现的,在客户端里,聊天时启动一个线程,传送文件时,启动另一个线程,所以在客户端并不会影响,面试官就说知道了,但是,针对服务器的性能优化,我做的不好,针对每一个客户端开启线程,造成服务器压力过大,因为现在没有上线,所以不会面对这个问题。
    博文地址:http://blog.csdn.net/u011958281/article/details/74556377

    问题
    网络通信是基于阻塞式API的——即当程序执行输入,输出操作以后,在这些操作返回之前会一直阻塞线程,所以服务器端必须为每个客户端提供一个独立的线程来进行处理。当服务器端需要同时处理大量客户端时,这种做法会导致性能下降,使用NIO API则可以让服务器端使用一个或者有限的几个线程来同时处理连接到服务器端的所有客户端。

    解决
    补充知识:新IO和传统IO有相同的目的,都是用于进行输入/输出,但是新的IO使用了不同的方式来处理输入流,新的IO采用内存映射的方式来处理输入输出,新IO将文件或文件的一部分区域映射到内存中,这样就可以此昂访问内存一样来访问文件了,通过这种方式来进行输入输出比传统的输入输出要快很多。

    Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象,与传统的InputStream和OutputStream最大的区别就是。它提供一个map()方法,可以直接将一块数据映射到内存中,如果说传统的IO是针对于面向流的处理,则新IO是面向块的处理。

    • Buffer
      从内部结构来看,Buffer就像一个数组,它可以保存多个类型相同的数据,Buffer是一个抽象类,最常用的子类就是ByteBuffer,它可以在底层字节数组上进行get/set操作,还有CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer.
    • Channel
      程序不能直接访问Channel中的数据,包括读取,写入都不行,Channel只能与Buffer进行交互,也就是说,如果要从Channel取一些数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据。

      1. Pipe.SkinChannel和Pipe.SourceChannel用于支持线程之间通信的管道
      2. ServerSocketChannel,SocketChannel是用于支持TCP通信的Channel
      3. DatagramChannel则是用于支持UDP网络通信的

    JAVA的NIO为非阻塞式Socket通信提供了下面几个特殊类:

    • Selector:所有希望采用通过非阻塞式方式通信的Channel都应该注册到Selector对象,可以通过调用此类的Open()静态方法来创建Selector实例。
      Selector可以同时监听多个SelectorChannel的IO状态,是非阻塞式IO的核心

    仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。

    Selector的创建
    通过调用Selector.open()方法创建一个Selector,如下:

    Selector selector = Selector.open();

    向Selector注册通道

    为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现,如下:

    channel.configureBlocking(false);
    SelectionKey key = channel.register(selector,
    Selectionkey.OP_READ);

    与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。

    注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:

    Connect
    Accept
    Read
    Write
    

    通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

    这四种事件用SelectionKey的四个常量来表示:

    SelectionKey.OP_CONNECT
    SelectionKey.OP_ACCEPT
    SelectionKey.OP_READ
    SelectionKey.OP_WRITE
    

    如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:
    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

    这里写图片描述

    服务器上所有的Channel(包括ServerSocketChannel和SocketChannel)都需要向Selector注册,而该Selector则负责监视其中的Socket的IO状态,当其中任意一个或几个Channel具有可用的IO操作时,该Selector的select()方法将会返回大于0整数,该整数就表示有几个Channel具有IO操作,并提供了secletorKeys()返回selectionKeys集合,正是通过这些Selcetor的实例的select()方法,可以知道当前有多少channel是否有需要的IO操作。

    展开全文
  • 主要介绍了详解socket阻塞与非阻塞,同步与异步、I/O模型,socket网络编程中的同步,异步,阻塞式,非阻塞式,有何联系与区别,本文将详细讲诉。
  • Windows平台提供了5种非阻塞Socket编程模型: Select模型(集合管理多个Socket,集合中有64个元素,可以管理1024个socket) WSAAsyncSelect模型(消息通知应用程序) WSAEventSelect模型(事件通知应用程序) 每次只能等待...
  • 在linux进行非阻塞的socket接收数据时经常出现Resource temporarily unavailable,errno代码为11(EAGAIN),这表明你在非阻塞模式下调用了阻塞... 对非阻塞socket而言,EAGAIN不是一种错误。在VxWorks和Windows上,EAGA
  • 所以在数据库编程之前需要掌握基础的SQL 命令语句。  数据库驱动层  相关联的类包含了QSqlDriver ,QSqlDriverCreatpor ,QSqlDriverCreatorBase,QSqlDriverPlugin与QSqlResult 。  数据库的应用程序接口层  ...
  • 主要介绍了Linux UDP socket 设置为的非阻塞模式与阻塞模式区别的相关资料,需要的朋友可以参考下
  • 非阻塞需要多线程编程 服务端 方式1: 使用threading库实现多线程 基本方法和单进程基本写法一致, 将收发部分封装为函数以便开启其他线程: import socket import time import threading def handle_socket(conn, ...
  • 本文将在C#中Socket同步通信的基础上,分析和研究Socket异步编程的实现方法,目的是深入了解Socket编程的基本原理,增强对网络游戏开发相关内容的认识。 什么是Socket编程的异步是实现 所谓Socket编程的异步实现是指...
  • linux 阻塞socket与非阻塞socket

    千次阅读 2018-03-23 13:28:01
    阻塞socket和非阻塞socket的区别读操作对于阻塞的socket/recv,当socket的接收缓冲区中没有数据时,read调用会一直阻塞住,直到有数据到来才返回。当socket缓冲区中的数据量小于期望读取的数据量时,返回实际读取的...
  • C++非阻塞模式Socket编程

    千次阅读 2014-04-16 10:28:00
    //无法立即完成非阻塞Socket上的操作 if(err==WSAEWOULDBLOCK) { Sleep(5000); printf("waiting back msg!\n"); continue; } else if(err==WSAETIMEDOUT||err==WSAENETDOWN)//已建立连接 { printf("recv failed!"); ...
  • 非阻塞select 实现 socket服务器使用select 实现 非阻塞的方式 来实现 服务端
  • 深入理解阻塞socket和非阻塞socket

    千次阅读 2018-08-22 21:39:28
    什么是阻塞socket,什么是非阻塞socket。对于这个问题,我们要先弄清什么是阻塞/非阻塞。阻塞与非阻塞是对一个文件描述符指定的文件或设备的两种工作方式。 阻塞的意思是指,当试图对该文件描述符进行读写时,如果...
  • linux下: nt flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); windows下: ...上面 为不同平台下设置非阻塞Socket函数 #pragma once #include <iostream>..
  • 对于许多初学者来说,网络通信程序的开发,普遍的一个...许多概念,诸如:同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)等,初学者往往迷惑不清,只知其所以而不知起所以然。  异步方式指的是发送
  • Linux socket编程实战(非阻塞

    千次阅读 2019-05-13 22:15:36
    socket在创建的时候默认是阻塞的,要想设置为非阻塞的有以下几种方法: 1.ioctl 和 fcntl 设置已经创建socket的fd ioctl用于设备控制 #include <sys/ioctl.h> int ioctl(int fd, unsigned long request...
  • Windows Socket 异步编程(非阻塞)

    千次阅读 2016-08-03 09:53:55
    使用Select异步模式来实现返送示例。服务器启动并监听9999端口...当客户端发送recv函数时,这个socket便成为可写状态,服务器端便知道这个客户端可写,然后根据自己的定义发送给客户端内容。如果客户端不发送recv函数,
  • linux客户端Socket非阻塞connect编程收集.pdf
  • 非阻塞式I/O包括非阻塞输入操作,非阻塞输出操作,非阻塞接收外来连接,非阻塞发起外出连接。包括的函数有:read, ... 将socket 设置为非阻塞模式有三总方法: (1)创建socket的时候,指定socket是异步的,在ty...
  • socket的阻塞与非阻塞

    2021-04-11 12:35:37
    非阻塞模式3. 优缺点对比 阻塞(blocking)、非阻塞(non-blocking): Windows套接字在阻塞和非阻塞两种模式下执行I/O操作。在阻塞模式下,在I/O操作完成前,执行的操作函数一直等候而不会立即返回,该函数所在的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 69,092
精华内容 27,636
关键字:

非阻塞socket编程