【C++ Socket编程】(一)阻塞模型

1. 阻塞模型
2. 阻塞+多线程
3. select
4. 异步IO
5. IOCP
6. epoll
7. Reactor和Proactor



基础知识

最常见的socket tcp编程模型就是阻塞模型,方法也很简单:

客户端:

  • connect
  • send
  • recv
  • closesocket

服务端:

  • bind
  • listen
  • accept
  • recv
  • send

各种socket编程模型都是对服务端来说的。下面上代码:

客户端

// sock_client.cpp

#include <stdio.h>
#include <WinSock2.h>

#include <iostream>
using namespace std;

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

#define SERVERADDR "127.0.0.1"
#define SERVERPORT 9000
#define DATA_MAXSIZE 128

int main(int argc, char **argv)
{
    cout << "this is sock client" << endl;

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData); //初始化Winsock DLL 与WSACleanup成对出现

    SOCKET sockClient;      //客户端socket
    SOCKADDR_IN addrServer; //服务端地址

    //创建客户端socket
    sockClient = socket(AF_INET, SOCK_STREAM, 0);

    //定义服务器地址
    addrServer.sin_addr.S_un.S_addr = inet_addr(SERVERADDR);
    addrServer.sin_family = AF_INET; // 协议族 ipv4
    addrServer.sin_port = htons(SERVERPORT);

    //连接到服务器
    connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

    //发送数据
    char message[DATA_MAXSIZE ] = "hello world";
    char buff[DATA_MAXSIZE ];

    int ret = send(sockClient, message, DATA_MAXSIZE , 0);
    printf("recv send return : %d\r\n", ret);
    ret = recv(sockClient, buff, DATA_MAXSIZE, 0);
    printf("recv from server : %s\r\n", buff);

    //关闭socket
    closesocket(sockClient);
    WSACleanup(); // 解除socket库的绑定

    return 0;
}

这种比较简单的代码我喜欢用MinGW编译:

> g++ sock_client.cpp -lws2_32 -o sock_client.exe

服务端

/* 
sock_server_block.cpp
阻塞模型
服务端同时只能处理一个客户端连接
*/

/* 阻塞模型
服务端同时只能处理一个客户端连接
*/

#include <iostream>
using namespace std;

#include <winsock2.h>
#include <stdio.h>
#include "utility.h"

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

#define SERVERPORT 9000
#define DATA_MAXSIZE 128

#define BACK_LOG 20	// 经过三次握手,但还没有accept的最大连接数,达到最大值后将拒绝连接

int main(int argc, char **argv)
{
    WSADATA wsaData;
    SOCKET sockServer;
    SOCKADDR_IN addrServer;
    SOCKET sockClient;
    SOCKADDR_IN addrClient;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    //创建服务端socket
    sockServer = socket(AF_INET, SOCK_STREAM, 0);
    addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //INADDR_ANY表示任何IP
    addrServer.sin_family = AF_INET;
    addrServer.sin_port = htons(SERVERPORT); //绑定端口
    //绑定socket
    bind(sockServer, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));

    //Listen监听端
    listen(sockServer, BACK_LOG); //BACK_LOG 为最大等待连接数目

    printf("server start:\nlistening...\n");
    int len = sizeof(SOCKADDR);
    char sendBuf[DATA_MAXSIZE]; //发送至客户端的字符串
    char recvBuf[DATA_MAXSIZE]; //接受客户端返回的字符串
    char stime[64];

    while (true)
    {
        //会阻塞进程,直到有客户端连接上来为止
        sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
        get_time(stime, sizeof(stime));
        printf("[%s] accept client : %#x\n", stime, &sockClient);

        while (true)
        {
            //阻塞等待接收数据
            int recv_size = recv(sockClient, recvBuf, DATA_MAXSIZE, 0);
            get_time(stime, sizeof(stime));
            if (recv_size > 0)
            {
                //打印客户端数据
                printf("[%s] recv from client[%#x] : %s\n", stime, &sockClient, recvBuf);
                sprintf(sendBuf, "this is sock server %#x", &sockClient);
                send(sockClient, sendBuf, DATA_MAXSIZE, 0);
            }
            else
            {
                cout << "[" << stime << "] client[" << &sockClient << "] socket closed" << endl;
                closesocket(sockClient);
                break;
            }
        }
    }

    //关闭socket
    closesocket(sockClient);
    WSACleanup();

    return 0;
}

编译:

>g++ sock_server_block.cpp utility.cpp -lws2_32 -o sock_server_block.exe -fpermissive

测试结果:
在这里插入图片描述

多客户端测试

上面一次只有一个客户端连接,看不出效果,我们重新写一个客户端代码,多个客户端同时连接和发送数据,看看会发生什么情况。

// sock_client_bulk.cpp
// 批量创建client,并发送数据

#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
#include <process.h>
#include <windows.h>
#include "utility.h"

#pragma comment(lib, "ws2_32.lib")
using namespace std;

#define SERVERADDR "127.0.0.1"
#define SERVERPORT 9000
#define MSG_BUF_SIZE 128

#define CLIENT_NUM 10

unsigned int CreateClient(void *args);
int send_times = 1;
int client_num = 1;

int sendFailedTimes = 0;
int connFailedTimes = 0;

CRITICAL_SECTION mCriticalSection_Send; // 定义临界区
CRITICAL_SECTION mCriticalSection_Conn; // 定义临界区
CRITICAL_SECTION mCriticalSection_Num;  // 定义临界区

// 通过在main函数中传入参数设置客户端数和发送次数
// sock_client_bulk.exe [client_num] [send_times]
int main(int argc, char **argv)
{
    // cout << argc << argv[0] << endl;
    client_num = CLIENT_NUM;
    send_times = 2;
    if (argc > 1)
    {
        // cout << argv[0] << endl;
        client_num = atoi(argv[1]);
    }
    if (argc > 2)
    {
        send_times = atoi(argv[2]);
    }

    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // printf("1");
    int cnum = client_num;
    char stime[64];
    get_time(stime, sizeof(stime));
    printf("[%s] client count %d start\r\n", stime, cnum);

    InitializeCriticalSection(&mCriticalSection_Send); // 初始化
    InitializeCriticalSection(&mCriticalSection_Conn); // 初始化
    InitializeCriticalSection(&mCriticalSection_Num);  // 初始化

    // 创建客户端
    for (int i = 0; i < client_num; i++)
    {
        // printf("1");
        _beginthreadex(0, 0, CreateClient, 0, 0, 0);
        // printf("1");
        // Sleep(200);
    }
    cin.get();
    return 0;
}

unsigned int CreateClient(void *args)
{
    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);

    //定义服务器地址
    SOCKADDR_IN addrServer;
    addrServer.sin_addr.S_un.S_addr = inet_addr(SERVERADDR);
    addrServer.sin_family = AF_INET; // 协议族 ipv4
    addrServer.sin_port = htons(SERVERPORT);

    char stime[64];
    int ret = 0;
    ret = connect(sockClient, (SOCKADDR *)&addrServer, sizeof(SOCKADDR));
    if (ret >= 0)
    {

        char buf[MSG_BUF_SIZE];
        int i = 0;

        char buf2[MSG_BUF_SIZE];
        while (i < send_times)
        {
            sprintf(buf, "this is sock client %#x , send times %d", &sockClient, i);
            // printf("%d sock %d\r\n", &sockClient, i);
            // printf("%s\r\n", buf);

            get_time(stime, sizeof(stime));
            printf("[%s] client[%#x] start send %d\r\n", stime, &sockClient, i);
            ret = send(sockClient, buf, MSG_BUF_SIZE, 0);
            if (ret > 0)
            {
                // get_time(stime, sizeof(stime));
                // printf("[%s] recv send return : %d\r\n", stime, ret);
                ret = recv(sockClient, buf2, MSG_BUF_SIZE, 0);
                // printf("[%s] recv from server : %s\r\n", stime, buf);
            }
            else
            {
                EnterCriticalSection(&mCriticalSection_Send); // 进入临界区
                sendFailedTimes++;
                LeaveCriticalSection(&mCriticalSection_Send); // 离开临界区

                set_console_text_color_red();
                printf("[%s] client[%#x] send failed , return value %d\r\n", stime, &sockClient, ret);
                set_console_text_color_white();
            }

            // Sleep(1000);

            i++;
        }
    }
    else
    {
        EnterCriticalSection(&mCriticalSection_Conn); // 进入临界区
        connFailedTimes++;
        LeaveCriticalSection(&mCriticalSection_Conn); // 进入临界区

        get_time(stime, sizeof(stime));
        set_console_text_color_red();
        printf("[%s] client[%#x] connect failed, return value %d\r\n", stime, &sockClient, ret);
        set_console_text_color_white();
    }

    get_time(stime, sizeof(stime));
    printf("[%s] client[%#x] closed\r\n", stime, &sockClient);
    closesocket(sockClient);


    EnterCriticalSection(&mCriticalSection_Num); // 进入临界区
    client_num--;
    LeaveCriticalSection(&mCriticalSection_Num); // 进入临界区

    if (client_num <= 0)
    {
        WSACleanup();
        DeleteCriticalSection(&mCriticalSection_Send); // 删除
        DeleteCriticalSection(&mCriticalSection_Conn); // 删除
        DeleteCriticalSection(&mCriticalSection_Num); // 删除

        get_time(stime, sizeof(stime));
        cout << "[" << stime << "] client work complete ,connect failed times : " << connFailedTimes << ", send failed times : " << sendFailedTimes << endl
             << "press any keys to continue..." << endl;
    }
}

编译:

> g++ sock_client_bulk.cpp utility.cpp -lws2_32 -o sock_client_bulk.exe -fpermissive

运行测试:
创建100个连接,每个连接发送2次数据。

> sock_client_bulk.exe 100 2

可以反复测试,固定服务端listen的最大连接队列,调整客户端数量。

在设置服务端listen最大值为1时,客户端数为20,发送次数为10时,结果如下:
在这里插入图片描述

总结

  • 阻塞型的socket服务端每次只能处理一个连接
  • 虽然可以通过调整listen的最大连接队列值,但是依旧很容易达到客户端数量上限,导致连接或发送数据失败

utility.h、utility.cpp

// utility.h

// 获取当前时间
int get_time(char ** time);
int get_time(char * timeBuf,int bufSize);

// 设置控制台文字颜色为红色
void set_console_text_color_red();
// 恢复控制台文字为白色(默认颜色)
void set_console_text_color_white();
// utility.cpp
#include "utility.h"
#include <stdio.h>
#include <time.h>
#include <sys/timeb.h>
#include <Windows.h>

int get_time(char **t)
{
    time_t timep;
    time(&timep);
    *t = new char[64];

    struct timeb tb;
    ftime(&tb);

    size_t sz = strftime(*t, 64, "%Y-%m-%d %H:%M:%S", localtime(&timep));

    sprintf(*t + sz, " %d", tb.millitm);
    // printf("%s \r\n", t);
    return 0;
}

int get_time(char *timeBuf, int bufSize)
{
    if (nullptr == timeBuf)
        return -1;
    time_t timep;
    time(&timep);
    struct timeb tb;
    ftime(&tb);
    size_t sz = strftime(timeBuf, bufSize, "%Y-%m-%d %H:%M:%S", localtime(&timep));
    sprintf(timeBuf + sz, " %d", tb.millitm);
    // printf("%s \r\n", timeBuf);
    return 0;
}

void set_console_text_color_red()
{
    HANDLE handle;
    handle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED); //设置为红色
}

void set_console_text_color_white()
{
    HANDLE handle;
    handle = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); //恢复默认的白色
}

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值