Windows C++ TCP开发(使用select函数以及设置非阻塞/Reuse属性)

2024-03-14 20:20

本文主要是介绍Windows C++ TCP开发(使用select函数以及设置非阻塞/Reuse属性),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、select官方函数说明:

语法

C++

int WSAAPI select([in]      int           nfds,[in, out] fd_set        *readfds,[in, out] fd_set        *writefds,[in, out] fd_set        *exceptfds,[in]      const timeval *timeout
);

参数

[in] nfds

已忽略。 包含 nfds 参数只是为了与 Berkeley 套接字兼容。

[in, out] readfds

一个可选指针,指向要检查的一组套接字的可读性。

[in, out] writefds

指向要检查可写性的一组套接字的可选指针。

[in, out] exceptfds

指向要检查错误的一组套接字的可选指针。

[in] timeout

选择等待的最长时间,以 TIMEVAL 结构的形式提供。 将阻止操作的 超时 参数设置为 null 。

返回值

select 函数返回fd_set结构中已就绪并包含的套接字句柄总数;如果时间限制过期,则返回 0;如果发生错误,则返回SOCKET_ERROR。 如果返回值SOCKET_ERROR,则 WSAGetLastError 可用于检索特定的错误代码。

 2、阻塞设置

    unsigned   long cmd = 1;
    nRet = ioctlsocket(sclient, FIONBIO, &cmd);
    if (SOCKET_ERROR == nRet)
    {
        printf("Failed to set FIONBIOsocket!\n");
        WSACleanup();
        return 0;
    }

3、设置端口 Reuse属性

    bool bReuseAddr = true;
    //设置端口SO_REUSEADDR属性,端口释放后可以立即被使用。
    nRet = setsockopt(sclient, SOL_SOCKET, SO_REUSEADDR, (char *)&bReuseAddr, sizeof(bReuseAddr));
    if (SOCKET_ERROR == nRet)
    {
        printf("Failed to set resueaddr socket!\n");
        WSACleanup();
        return 0;
    }

 什么是resuse属性:端口复用最常用的用途应该是防止服务器重启时之前绑定的端口还未释放或者程序突然退出而系统没有释放端口。这种情况下如果设定了端口复用,则新启动的服务器进程可以直接绑定端口。如果没有设定端口复用,绑定会失败,提示ADDR已经在使用中,必须等超时时间大概是2分钟。

4、TCP代码+select
4.1 TCP 服务端代码

#include <iostream>
#include <stdio.h>  
#include <string.h>  
#include <WINSOCK2.H>  

#pragma comment(lib,"ws2_32.lib")  

#define INT_SERVER_PORT 8888  
#define STR_SERVER_IP "127.0.0.1"  
#define INT_DATABUFFER_SIZE 100  

void main(void)
{
    //与WSACleanup函数配对使用
    WORD dwVersion = MAKEWORD(2, 2);
    WSAData wsaData;
    WSAStartup(WINSOCK_VERSION, &wsaData);

    //创建套接字
    SOCKET sockServer = socket(AF_INET, SOCK_STREAM, 0);
    if (INVALID_SOCKET == sockServer)
    {
        printf("Failed to create socket!\n");
        WSACleanup();
        return;
    }

    //本地地址初始化
    sockaddr_in addrServer;
    memset(&addrServer, 0, sizeof(sockaddr_in));
    addrServer.sin_family = AF_INET;
    addrServer.sin_port = htons(INT_SERVER_PORT);
    addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    int nRet = 0;
    bool bReuseAddr = true;
    //设置端口SO_REUSEADDR属性,端口释放后可以立即被使用。
    nRet = setsockopt(sockServer, SOL_SOCKET, SO_REUSEADDR, (char *)&bReuseAddr, sizeof(bReuseAddr));
    if (SOCKET_ERROR == nRet)
    {
        printf("Failed to set resueaddr socket!\n");
        WSACleanup();
        return;
    }

    //设置端口非阻塞
    unsigned   long cmd = 1;
    nRet = ioctlsocket(sockServer,FIONBIO,&cmd);
    if (SOCKET_ERROR == nRet)
    {
        printf("Failed to set resueaddr socket!\n");
        WSACleanup();
        return;
    }

    nRet = bind(sockServer, (sockaddr *)&addrServer, sizeof(addrServer));
    if (SOCKET_ERROR == nRet)
    {
        printf("Failed to bind address!\n");
        WSACleanup();
        return;
    }

    if (0 != listen(sockServer, 5))
    {
        printf("Failed to listen client!\n");
        WSACleanup();
        return;
    }

    UINT i = 0;
    fd_set fd;
    FD_ZERO(&fd);
    FD_SET(sockServer, &fd);

    /*
    timeval tm;
    tm.tv_sec = 0;
    tm.tv_usec = 1000;
    */
    while (1)
    {
        fd_set fdOld = fd;
        nRet = select(0, &fdOld, NULL, NULL,/*&tm*/NULL);
        if (0 <= nRet)
        {
            for (i = 0; i < fd.fd_count; i++)
            {
                if (FD_ISSET(fd.fd_array[i], &fdOld))
                {
                    if (fd.fd_array[i] == sockServer)//接收到新的连接
                    {
                        SOCKET sockAccept;
                        sockaddr_in addrAccept;
                        int iAcceptLen = sizeof(addrAccept);
                        memset(&addrAccept, 0, sizeof(addrAccept));
                        sockAccept = accept(sockServer, (sockaddr *)&addrAccept, &iAcceptLen);
                        if (INVALID_SOCKET != sockAccept)
                        {
                            send(sockAccept, "Hello Client", strlen("Hello Client"), 0);
                            FD_SET(sockAccept, &fd);
                            printf("%s:%d has connected server!\n", inet_ntoa(addrAccept.sin_addr),
                                ntohs(addrAccept.sin_port));
                        }
                    }
                    else //非服务器,接收数据(因为fd是读数据集)  
                    {
                        char szDataBuff[INT_DATABUFFER_SIZE];
                        int iRecvSize;
                        memset(szDataBuff, 0, INT_DATABUFFER_SIZE);
                        iRecvSize = recv(fd.fd_array[i], szDataBuff, INT_DATABUFFER_SIZE, 0);
                        if (SOCKET_ERROR == iRecvSize)
                        {
                            closesocket(fd.fd_array[i]);
                            FD_CLR(fd.fd_array[i], &fd);
                            i--;
                            printf("Failed to recv data ,or Client has disconnected, errorcode:%d.\n", WSAGetLastError());
                            continue;
                        }

                        if (0 == iRecvSize)
                        {
                            //客户socket关闭  
                            closesocket(fd.fd_array[i]);
                            FD_CLR(fd.fd_array[i], &fd);
                            i--;
                        }

                        if (0 < iRecvSize)
                        {
                            //打印接收的数据  
                            printf("recv data:%s\n",szDataBuff);
                        }
                    }
                }
            }
        }
        else if (SOCKET_ERROR == nRet)
        {
            printf("Faild to select sockt in server! Error Code[%d]\n", WSAGetLastError());
            Sleep(100);
        }
    }
    WSACleanup();
}
 

4.2 TCP客户端代码 

#include <iostream>
#include <stdio.h>  
#include <string.h>  
#include <WINSOCK2.H>  
using namespace std;
#pragma comment(lib, "ws2_32.lib")

int main()
{
    //与WSACleanup函数配对使用
    WORD sockVersion = MAKEWORD(2, 2);
    WSADATA data;
    if (WSAStartup(sockVersion, &data) != 0)
    {
        return 0;
    }

    //创建客户端套接字
    SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sclient == INVALID_SOCKET)
    {
        printf("invalid socket!");
        return 0;
    }

    //初始化SOCKET结构体
    sockaddr_in serAddr;
    serAddr.sin_family = AF_INET;
    serAddr.sin_port = htons(8888);
    serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
    {  //连接失败 
        printf("connect error !");
        closesocket(sclient);
        return 0;
    }

    int nRet = 0;
    bool bReuseAddr = true;
    //设置端口SO_REUSEADDR属性,端口释放后可以立即被使用。
    nRet = setsockopt(sclient, SOL_SOCKET, SO_REUSEADDR, (char *)&bReuseAddr, sizeof(bReuseAddr));
    if (SOCKET_ERROR == nRet)
    {
        printf("Failed to set resueaddr socket!\n");
        WSACleanup();
        return 0;
    }

    //设置端口非阻塞
    unsigned   long cmd = 1;
    nRet = ioctlsocket(sclient, FIONBIO, &cmd);
    if (SOCKET_ERROR == nRet)
    {
        printf("Failed to set resueaddr socket!\n");
        WSACleanup();
        return 0;
    }

    //发送数据
    send(sclient, "Hello Server", strlen("Hello Server"), 0);

    //接收数据
    while (true)
    {
        fd_set fd;
        FD_ZERO(&fd);
        FD_SET(sclient, &fd);
        nRet = select(0, &fd, NULL, NULL,NULL);
        if (0 <= nRet)
        {
            char recData[255];
            int ret = recv(sclient, recData, 255, 0);
            if (ret > 0) {
                recData[ret] = 0x00;
                printf("Recv :%s\n",recData);
            }
            else
            {
                printf("Socket Error or Socket has closed\n");
                break;
            }
        }
        else if (nRet == SOCKET_ERROR)
        {
            printf("Faild to select sockt in server! Error Code[%d]\n", WSAGetLastError());
            break;
        }
    }
    closesocket(sclient);
    WSACleanup();
    return 0;
}

这篇关于Windows C++ TCP开发(使用select函数以及设置非阻塞/Reuse属性)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/809619

相关文章

C#借助Spire.XLS for .NET实现在Excel中添加文档属性

《C#借助Spire.XLSfor.NET实现在Excel中添加文档属性》在日常的数据处理和项目管理中,Excel文档扮演着举足轻重的角色,本文将深入探讨如何在C#中借助强大的第三方库Spire.... 目录为什么需要程序化添加Excel文档属性使用Spire.XLS for .NET库实现文档属性管理Sp

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

idea设置快捷键风格方式

《idea设置快捷键风格方式》在IntelliJIDEA中设置快捷键风格,打开IDEA,进入设置页面,选择Keymap,从Keymaps下拉列表中选择或复制想要的快捷键风格,点击Apply和OK即可使... 目录idea设www.chinasem.cn置快捷键风格按照以下步骤进行总结idea设置快捷键pyth

python中的flask_sqlalchemy的使用及示例详解

《python中的flask_sqlalchemy的使用及示例详解》文章主要介绍了在使用SQLAlchemy创建模型实例时,通过元类动态创建实例的方式,并说明了如何在实例化时执行__init__方法,... 目录@orm.reconstructorSQLAlchemy的回滚关联其他模型数据库基本操作将数据添

Spring配置扩展之JavaConfig的使用小结

《Spring配置扩展之JavaConfig的使用小结》JavaConfig是Spring框架中基于纯Java代码的配置方式,用于替代传统的XML配置,通过注解(如@Bean)定义Spring容器的组... 目录JavaConfig 的概念什么是JavaConfig?为什么使用 JavaConfig?Jav

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

C++构造函数中explicit详解

《C++构造函数中explicit详解》explicit关键字用于修饰单参数构造函数或可以看作单参数的构造函数,阻止编译器进行隐式类型转换或拷贝初始化,本文就来介绍explicit的使用,感兴趣的可以... 目录1. 什么是explicit2. 隐式转换的问题3.explicit的使用示例基本用法多参数构造

Java使用Spire.Doc for Java实现Word自动化插入图片

《Java使用Spire.DocforJava实现Word自动化插入图片》在日常工作中,Word文档是不可或缺的工具,而图片作为信息传达的重要载体,其在文档中的插入与布局显得尤为关键,下面我们就来... 目录1. Spire.Doc for Java库介绍与安装2. 使用特定的环绕方式插入图片3. 在指定位

Springboot3 ResponseEntity 完全使用案例

《Springboot3ResponseEntity完全使用案例》ResponseEntity是SpringBoot中控制HTTP响应的核心工具——它能让你精准定义响应状态码、响应头、响应体,相比... 目录Spring Boot 3 ResponseEntity 完全使用教程前置准备1. 项目基础依赖(M

Java使用Spire.Barcode for Java实现条形码生成与识别

《Java使用Spire.BarcodeforJava实现条形码生成与识别》在现代商业和技术领域,条形码无处不在,本教程将引导您深入了解如何在您的Java项目中利用Spire.Barcodefor... 目录1. Spire.Barcode for Java 简介与环境配置2. 使用 Spire.Barco