winsocket tcp 非阻塞实例

2023-05-16

 在 Winsocket 一:单线程阻塞server&client程序(tcp) 和 Winsocket 二:多线程阻塞服务器程序(tcp)介绍了阻塞tcp程序,阻塞式tcp程序服务器程序会因为建立连接和关闭连接而频繁的创建和关闭线程会产生大量的内存碎片,从而导致服务端程序不能保证长时间的稳定运行,本文简单介绍非阻塞式tcp程序的编写。

一、非阻塞
        阻塞是指在进行一个操作的时候,如服务器接收客户端的连接(accept),客户端程序执行connect操作,服务器或者客户端读写数据(read、write),如果该操作没有执行完成(成功或者失败都算是执行完成),则程序会一直阻塞在操作执行的地方,直到该操作返回一个明确的结果。非阻塞式程序会在产生阻塞操作的地方阻塞一定的时间(该时间可以由程序员自己设置)。如果操作没有完成,在到达所设置的时间之后,无论该操作成功与否,都结束该操作而执行程序后面的操作。

二、非阻塞相关的系统函数
1、ioctlsocket()
        为了执行非阻塞操作,需要在创建了一个套接口后,调用ioctlsocket函数将套接口设置为非阻塞的套接口:int ioctlsocket( SOCKET s, long cmd, u_long FAR *argp ),其作用是控制套接口的I/O模式。
        s        要设置的套接口;
        cmd   要对该套接口设置的命令,为了要将套接口设置成为非阻塞的,我们应该填写FIONBIO;
        argp 设置为1表示非阻塞,0表示阻塞。
2、select()
        为了进行非阻塞的操作,我们需要在进行非阻塞操作之前调用select函数:int select(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout ),其作用是设定一个或多个套接口的状态,并进行必要的等待,以便执行异步I/0(非阻塞)操作。
        nfds        可忽略,作用仅仅是为了与伯克利套接口相兼容;
        readfds   要检测的可读套接字集合(可设置为NULL);在三种情况下有信号出现:一、如果集合中有套接字处于监听状态,并且该套接字上有来自客户端的连接请求;二、如果集合中的套接字收到了send操作发送过来的数据;三、如果集合中的套接字被关闭、重置或者中断。
        writefds  要检测的可写套接字集合(可设置为NULL);在两种情况下有信号出现:一、集合中的套接字经过connect操作后,连接成功;二、可以用send操作向集合中的套接字写数据。
        exceptfds要检测的错误套接字集合(可设置为NULL);在两种情况下有信号出现:一、集合中的套接字经过connect操作后,连接失败;二、有带外数据到来。
        timeout  执行该函数时需要等待的时间,如果为NULL则表示阻塞操作,为0则表示立即返回。
        其中fd_set表示套接字集合。在使用select函数时,我们需要将相应的套接字加入到相应的集合中。如果集合中的套接字有信号,select函数的返回值即为集合中有信号的套接字数量。
3、与fd_set相关的宏
        FD_ZERO(*set)将集合set清空;
        FD_SET(s, *set)将套接字s加入到集合set;
        FD_CLR(s, *set)将套接字s移除出集合set;
        FD_ISSET(s, *set)判断套接字s是否在集合中有信号。
三、非阻塞TCP程序实例
1、服务器程序


// server_2.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#include <iostream>   
#include <cassert>   
#include <list>   
#include <WinSock2.h>   
#pragma comment(lib, "ws2_32.lib")   
#define ASSERT assert 
using namespace std;
      
static const int c_iPort = 10001;   
bool GraceClose(SOCKET *ps); 
 
int _tmain(int argc, _TCHAR* argv[])
{
    int iRet = SOCKET_ERROR;
 
    // 初始化Winsocket   
    WSADATA data;   
    ZeroMemory(&data, sizeof(WSADATA));   
    iRet = WSAStartup(MAKEWORD(2, 0), &data);   
    ASSERT(SOCKET_ERROR != iRet);  
 
    // 建立服务端程序的监听套接字
    SOCKET skListen = INVALID_SOCKET;   
    skListen = socket(AF_INET, SOCK_STREAM, 0);   
    ASSERT(INVALID_SOCKET != skListen);  
 
    // 初始化监听套接字地址信息   
    sockaddr_in adrServ;  
    ZeroMemory(&adrServ, sizeof(sockaddr_in));   
    adrServ.sin_family      = AF_INET;           
    adrServ.sin_port        = htons(c_iPort);     
    adrServ.sin_addr.s_addr = INADDR_ANY; 
 
    // 绑定监听套接字到本地   
    iRet = bind(skListen, (sockaddr*)&adrServ, sizeof(sockaddr_in));   
    ASSERT(SOCKET_ERROR != iRet);   
 
    // 使用监听套接字进行监听   
    iRet = listen(skListen, FD_SETSIZE);  
    ASSERT(SOCKET_ERROR != iRet);
 
    // 将套接口从阻塞状态设置到费阻塞状态   
    unsigned long ulMode = 1;   
    iRet = ioctlsocket(skListen, FIONBIO, &ulMode);   
    ASSERT(SOCKET_ERROR != iRet); 
 
    fd_set fsListen;   //fd_set为套字集合
    FD_ZERO(&fsListen); //FD_ZERO(*set)将集合set清空  
    fd_set fsRead;   
    FD_ZERO(&fsRead); 
 
    timeval tv;   
    tv.tv_sec  = 1;   
    tv.tv_usec = 0; 
 
    list<SOCKET> sl;   
    int i = 2;
    for(;;)   
    {  
        // 接收来自客户端的连接, 并将新建的套接字加入套接字列表中   
        FD_SET(skListen, &fsListen);//FD_SET(s, *set)将套接字s加入到集合set中
        iRet = select(1, &fsListen, NULL, NULL, &tv); 
        if(iRet > 0)   
        {   
            sockaddr_in adrClit;   
            int iLen = sizeof(sockaddr_in);   
            ZeroMemory(&adrClit, iLen);   
            SOCKET skAccept = accept(skListen, (sockaddr*)&adrClit,&iLen);   
            ASSERT(INVALID_SOCKET != skAccept);   
            sl.push_back(skAccept); 
            cout << "New connection " << skAccept << ", c_ip: " 
                << inet_ntoa(adrClit.sin_addr) << ", c_port: " << ntohs(adrClit.sin_port) << endl; 
        } 
 
        // 将套接字列表中的套接字加入到可读套接字集合中,以便在可以检测集合中的套接字是否有数据可读   
        FD_ZERO(&fsRead);   
        for(list<SOCKET>::iterator iter = sl.begin(); iter != sl.end(); ++iter)   
        {   
            FD_SET(*iter, &fsRead);   
        } 
 
        // 检测集合中的套接字是否有数据可读   
        iRet = select(sl.size(), &fsRead, NULL, NULL, &tv);   
        if(iRet > 0)   
        {   
            for(list<SOCKET>::iterator iter = sl.begin(); iter != sl.end(); ++iter)   
            {   
                // 如果有数据可读, 则遍历套接字列表中的所有套接字,检测出有数据可读的套接字   
                iRet = FD_ISSET(*iter, &fsRead);// FD_ISSET(s, *set)判断套接字s是否在集合中有信号
                if(iRet > 0)   
                {   
                    // 读取套接字上的数据   
                    const int c_iBufLen = 512;   
                    char recvBuf[c_iBufLen + 1] = {'\0'};   
                    int iRecv = SOCKET_ERROR;   
                    iRecv = recv(*iter, recvBuf, c_iBufLen, 0);
 
                    if (iRecv <= 0 )// 读取出现错误或者对方关闭连接   
                    {   
                       iRecv == 0 ? cout << "Connection shutdown at socket " << *iter << endl ://优雅关闭
                            cout << "Connection recv error at socket " << *iter << endl;// 粗暴关闭
                        iRet = GraceClose(&(*iter));   
                        ASSERT(iRet);   
                    }   
                    else  
                    {   
                        recvBuf[iRecv] = '\0';   
                        cout << "Server recved message from socket " << *iter << ": " << recvBuf << endl;   
                        
                        // 创建可写集合   
                        FD_SET fsWrite;   
                        FD_ZERO(&fsWrite);   
                        FD_SET(*iter, &fsWrite);   
                        // 如果可可写套接字, 则向客户端发送数据   
                        iRet = select(1, NULL, &fsWrite, NULL, &tv);   
                        if (iRet <= 0)   
                        {   
                            int iWrite = SOCKET_ERROR;   
                            iWrite = send(*iter, recvBuf, iRecv, 0);   
                            if (SOCKET_ERROR == iWrite)   
                            {   
                                cout << "Send message error at socket " << *iter << endl;   
                                iRet = GraceClose(&(*iter));   
                                ASSERT(iRet);   
                            }   
                        }   
                    }   
                }   
            }  //for 
            sl.remove(INVALID_SOCKET); // 删除无效的套接字, 套接字在关闭后被设置为无效   
        }  //if 
    }  //for (;;) 
 
    // 将套接字设置回阻塞状态   
    ulMode = 0;   
    iRet = ioctlsocket(skListen, FIONBIO, &ulMode);   
    ASSERT(SOCKET_ERROR != iRet);  
 
    // 关闭监听套接字   
    iRet = GraceClose(&skListen);   
    ASSERT(iRet);  
 
    // 清理Winsocket资源   
    iRet = WSACleanup();   
    ASSERT(SOCKET_ERROR != iRet); 
 
    system("pause");   
    return 0;   
}   
bool GraceClose(SOCKET *ps)   
{   
    const int c_iBufLen = 512;   
    char szBuf[c_iBufLen + 1] = {'\0'};  
 
    // 关闭该套接字的连接   
    int iRet = shutdown(*ps, SD_SEND);   
    while(recv(*ps, szBuf, c_iBufLen, 0) > 0);   
    if (SOCKET_ERROR == iRet)   
    {   
        return false;   
    }   
 
    // 清理该套接字的资源   
    iRet = closesocket(*ps);   
    if (SOCKET_ERROR == iRet)   
    {   
        return false;   
    }   
 
    *ps = INVALID_SOCKET;   
    return true;   
}  


代码解释:
if(iRet > 0)   
{   
    sockaddr_in adrClit;   
    int iLen = sizeof(sockaddr_in);   
    ZeroMemory(&adrClit, iLen);   
    SOCKET skAccept = accept(skListen, (sockaddr*)&adrClit,&iLen);   
    ASSERT(INVALID_SOCKET != skAccept);   
    sl.push_back(skAccept); 
    cout << "New connection " << skAccept << ", c_ip: " 
        << inet_ntoa(adrClit.sin_addr) << ", c_port: " << ntohs(adrClit.sin_port) << endl; 

将接受自客户端程序的套接字加入到list中,以方便管理。

if (iRecv <= 0 )// 读取出现错误或者对方关闭连接   
{   
    iRecv == 0 ? cout << "Connection shutdown at socket " << *iter << endl ://优雅关闭
        cout << "Connection recv error at socket " << *iter << endl;// 粗暴关闭
iRet = GraceClose(&(*iter));   
ASSERT(iRet);   

在套接字被客户端关闭或者异常中断后,需要将list中的套接字设置为无效套接字,并在操作完所有的套接字一次后,将无效的套接字从集合中移除。
2、客户端程序


// client.cpp : 定义控制台应用程序的入口点。
//
 
#include "stdafx.h"
 
#include <iostream>   
#include <cassert>   
#include <WinSock2.h>   
#pragma comment(lib, "ws2_32.lib")   
using namespace std;
 
#define ASSERT assert 
 
static const char c_szIP[] = "127.0.0.1";   
static const int  c_iPort  = 10001; 
 
bool GraceClose(SOCKET *ps);   
int main()   
{   
    int iRet = SOCKET_ERROR; 
 
    // 初始化Winsocket   
    WSADATA data;   
    ZeroMemory(&data, sizeof(WSADATA));   
    iRet = WSAStartup(MAKEWORD(2, 0), &data);   
    ASSERT(SOCKET_ERROR != iRet); 
 
    // 建立连接套接字   
    SOCKET skClient = INVALID_SOCKET;   
    skClient = socket(AF_INET, SOCK_STREAM, 0);   
    ASSERT(INVALID_SOCKET != skClient); 
 
    // 初始化连接套接字地址信息   
    sockaddr_in adrServ;                         
    ZeroMemory(&adrServ, sizeof(sockaddr_in));         
    adrServ.sin_family      = AF_INET;                
    adrServ.sin_port        = htons(c_iPort);         
    adrServ.sin_addr.s_addr = inet_addr(c_szIP); 
 
    // 将套接口从阻塞状态设置到非阻塞状态   
    unsigned long ulEnable = 1;   
    iRet = ioctlsocket(skClient, FIONBIO, &ulEnable);   
    ASSERT(SOCKET_ERROR != iRet); 
 
    fd_set fsWrite;   
    TIMEVAL tv;
 
    tv.tv_sec  = 1;   
    tv.tv_usec = 0;  
 
    cout << "Client began to connect to the server..." << endl;   
    for (;;)   
    {   
        // 使用非阻塞方式连接服务器,请注意connect操作的返回值总是为SOCKET_ERROR   
        iRet = connect(skClient, (sockaddr*)&adrServ, sizeof(sockaddr_in));   
        int iErrorNo = SOCKET_ERROR;   
        int iLen = sizeof(int);   
        // 如果getsockopt返回值不为0,则说明有错误出现   
        if (SOCKET_ERROR == iRet &&    0 != getsockopt(skClient, SOL_SOCKET, SO_ERROR, (char*)&iErrorNo, &iLen))   
        {   
            cout << "An error happened on connecting to server. The error no is " << iErrorNo   
                << ". The program will exit now." << endl;   
            exit(-1);   
        }   
 
        FD_ZERO(&fsWrite);   
        FD_SET(skClient, &fsWrite);   
        // 如果集合fsWrite中的套接字有信号,则说明连接成功,此时iRet的返回值大于0   
        iRet = select(1, NULL, &fsWrite, NULL, &tv);   
        if (iRet > 0)   
        {   
            cout << "Successed connect to the server..." << endl;   
            break;   
        }  
        cout << "retrying" << endl;
    }  //for
 
    for(;;)   
    {   
        const int c_iBufLen =512;   
        char szBuf[c_iBufLen + 1] = {'\0'};   
        cout << "What you will say:";   
        cin  >> szBuf;   
        if(0 == strcmp("exit", szBuf))   
        {   
            break;   
        }   
 
        FD_ZERO(&fsWrite);   
        FD_SET(skClient, &fsWrite);   
 
        // 如果集合fsWrite中的套接字有信号, 则可以用send操作发数据   
        iRet = select(1, NULL, &fsWrite, NULL, &tv);   
        if (0 < iRet)   
        {   
            iRet = send(skClient, szBuf, strlen(szBuf), 0);    
            if(SOCKET_ERROR == iRet)   
            {   
                cout << "send error." << endl;   
                break;   
            }   
 
            fd_set fsRead;   
            FD_ZERO(&fsRead);   
            FD_SET(skClient, &fsRead);  
 
            // 如果集合fsRead中的套接字有信号, 则可以用recv操作读数据   
            iRet = select(1, &fsRead, NULL, NULL, &tv);   
            if (0 < iRet)   
            {   
                iRet = recv(skClient, szBuf, c_iBufLen, 0);   
                if(0 == iRet)   
                {   
                    cout << "connection shutdown." << endl;   
                    break;   
                }   
                else if(SOCKET_ERROR == iRet)   
                {   
                    cout << "recv error." << endl;   
                    break;   
                }   
                szBuf[iRet] = '\0';   
                cout << szBuf << endl;   
            }   
        }  //if 
    }  //for 
 
    // 将套接字设置回阻塞状态   
    ulEnable = 0;   
    iRet = ioctlsocket(skClient, FIONBIO, &ulEnable);   
    ASSERT(SOCKET_ERROR != iRet); 
 
    // 关闭监听套接字   
    iRet = GraceClose(&skClient);   
    ASSERT(iRet);   
 
    // 清理Winsocket资源   
    iRet = WSACleanup();   
    ASSERT(SOCKET_ERROR != iRet);   
 
    system("pause");   
    return 0;   
}   
bool GraceClose(SOCKET *ps)   
{   
    const int c_iBufLen = 512;   
    char szBuf[c_iBufLen + 1] = {'\0'};   
    // 关闭该套接字的连接   
    int iRet = shutdown(*ps, SD_SEND);   
    while(recv(*ps, szBuf, c_iBufLen, 0) > 0);   
    if (SOCKET_ERROR == iRet)   
    {   
        return false;   
    }   
    // 清理该套接字的资源   
    iRet = closesocket(*ps);   
    if (SOCKET_ERROR == iRet)   
    {   
        return false;   
    }   
    *ps = INVALID_SOCKET;   
    return true;   
}  


————————————————
版权声明:本文为CSDN博主「u013071074」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013071074/article/details/26354781

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

winsocket tcp 非阻塞实例 的相关文章

  • Tomcat 的接受计数

    我对Tomcat有以下问题acceptCount 它说 当所有传入连接请求的最大队列长度 可能的请求处理线程正在使用中 收到的任何请求 当队列已满时将被拒绝 默认值为 10 我不确定这是如何运作的 我的意思是我知道有一个单独的 TCP 队列
  • 套接字术语 - “阻塞”是什么意思?

    当谈论 C 中的套接字编程时 术语 阻塞 是什么意思 我需要构建一个服务器组件 可能是 Windows 服务 来接收数据 进行一些处理并将数据返回给调用者 呼叫者可以等待回复 但我需要确保多个客户端可以同时呼叫 如果客户端 1 连接并且我花
  • 指示远程主机已关闭连接的 NetworkStream.Read 的替代方案?

    关于使用以下命令处理 TCP IP 连接TcpClient类 除了等待之外 还有其他方法可以检查远程主机是否已关闭连接吗 NetworkStream Read返回0的方法 您可以使用IOControlCode KeepAliveValues
  • Spring 集成超时客户端

    我的 Spring 集成场景是 使用自定义协议发送数据的数十个生产者 大小和内容 我必须解码这个自定义协议 然后处理结果 所以我尝试了很多配置 目前最好的配置如下
  • 定义新的套接字选项以在 TCP 内核代码中使用

    我正在尝试向 TCP 内核代码添加一些功能 在tcp input c 我希望我实现的代码仅在某些情况下运行 我想添加一个控制标志 可以从用户空间应用程序设置它 我 认为我 需要添加一个新的套接字选项 以便我可以完成以下操作setsockop
  • IPv4 允许的最大 TCP/IP 网络端口号是多少?

    可以使用的最大端口号是多少 端口号是一个无符号 16 位整数 即 65535
  • Python。从 6 字节字符串中打印 mac 地址

    我有 6 字节字符串的 mac 地址 您将如何以 人类 可读的格式打印它 Thanks import struct x x x x x x struct unpack BBBBBB your variable with mac
  • 很难理解带有 async_read 和 async_write 的 Boost ASIO TCP 的一些概念

    我很难理解使用 async read 和 async write 时构建 tcp 客户端的正确方法 这examples http www boost org doc libs 1 38 0 doc html boost asio examp
  • 是什么导致 MSSQL 中出现“非阻塞套接字上的操作将阻塞”错误?

    错误 异常查询为 CREATE NONCLUSTERED INDEX I1 ON AllAccounts BAK Master received Day ASC 出现异常 发生一个或多个错误 错误 异常内部异常无法从传输连接读取数据 非阻塞
  • Node.js 找不到模块“tcp”

    节点在以下行崩溃 var tcp require tcp 错误文本 node js 201 throw e process nextTick error or error event on first tick Error Cannot f
  • 我的代码中某处存在无限循环

    我有这个 Java 游戏服务器 最多可处理 3 000 个 tcp 连接 每个玩家或每个 tcp 连接都有自己的线程 每个线程的运行情况如下 public void run try String packet char charCur ne
  • 如何使用 kotlin 通过 TCP 连接发送和接收字符串

    我在 Windows 上有一个 TCP 服务器 我想在服务器和我的 Android 设备之间发送和接收文本字符串 我花了很多时间搜索使用 Kotlin 的示例 但没有找到任何有用的代码 所以我现在只能创建套接字并连接 fun connect
  • 如何模拟 TCP/IP 错误?

    在多层应用程序上 我需要模拟各种 TCP IP 错误来测试一些重新连接代码 有谁知道我可以使用什么工具 基于 Windows 来实现此目的 谢谢 Scapy http secdev org projects scapy 允许您控制数据包的各
  • 两个http请求可以合并在一起吗?如果可以的话,nodeJS服务器如何处理呢?

    昨天我做了一些关于 NodeJS 的演讲 有人问我以下问题 我们知道nodeJS是一个单线程服务器 多个请求是 到达服务器并将所有请求推送到事件循环 如果什么 两个请求同时到达服务器 服务器将如何处理 处理这种情况 我猜到了一个想法并回复如
  • 发起TCP连接关闭后如何接收数据?

    TCP 允许一侧发出 FIN 并让另一侧在结束其一侧的连接之前响应一些数据 我如何使用 NET 来实现这一点TcpClient 看来我必须使用Close发出FIN 但之后我不能再打电话Client Receive since Client被
  • 自动打开命名管道和 tcp\ip

    我正在安装一个需要修改 SQL Server 的新产品 具体来说 启用 tcp ip 并打开命名管道 我知道如何手动完成 我想要的是一种通过 SQL 或 C 代码为新客户自动化执行此操作的方法 我希望有任何关于正确方向的建议 您可以使用 C
  • 我应该害怕使用 UDP 进行客户端/服务器广播通话吗?

    我在过去的两天里阅读了每一篇StackOverflow问题和答案 以及googling当然 关于印地TCP and UDP协议 以便决定在我的用户应用程序和 Windows 服务之间的通信方法中应该使用哪一种 从我目前所看到的来看 UDP是
  • 无法分配请求的地址 - 可能的原因?

    我有一个由主服务器和分布式从服务器组成的程序 从属服务器向服务器发送状态更新 如果服务器在固定时间内没有收到特定从属服务器的消息 则会将该从属服务器标记为关闭 这种情况一直在发生 通过检查日志 我发现从站只能向服务器发送一个状态更新 然后永
  • 为什么 UDP 服务器中只有一个套接字?

    我正在准备考试 发现了这个问题 典型的 UDP 服务器可以使用单个套接字来实现 解释一下为什么 对于 TCP 驱动的服务器 我发现创建了两个套接字 一个用于所有客户端访问服务器 另一个用于每个客户端的特定 套接字 用于服务器和客户端之间的进
  • TcpClient 在异步读取期间断开连接

    我有几个关于完成 tcp 连接的问题 客户端使用 Tcp 连接到我的服务器 在接受客户端后listener BeginAcceptTcpClient ConnectionEstabilishedCallback null 我开始阅读netw

随机推荐