首页 > 解决方案 > C++ Sockets 多个客户端的连接结构

问题描述

这是一个允许多个连接的socket程序,看起来成功了,但是当更多的客户端连接到主机时,CPU使用率确实很高。

这是主机的代码


#include <WINSOCK2.H>
#include <windows.h>
#include <opencv2/opencv.hpp>
#include <string>
#include <iostream>
#include "sendImg.cpp"
#include <thread>
#define PORT           5150

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

using namespace cv;
using namespace std;

int client_quan = 0;

void handle_connections();
void handle_clients(const int client_id);
void test();

Mat frame;


VideoCapture cap;      



int main() {

    cap.open(0);           


    thread handle_conns_thread(handle_connections);
    handle_conns_thread.join();


    return 0;
}

void handle_connections(){
    // Initialize Windows socket library
    WSADATA wsaData;
    WSAStartup(0x0202, &wsaData); //Winsock version2.2

    //store the info of socket
    SOCKADDR_IN local;
    SOCKADDR_IN client;


    int iaddrSize = sizeof(SOCKADDR_IN);

    //setting socket (local/internet/ipv4/ipv6, UDP/TCP, socket protocol)
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    local.sin_family = AF_INET; //TCP
    local.sin_port = htons(PORT); //convert Host Byte Order to Network Byte Order
    local.sin_addr.s_addr = htonl(INADDR_ANY); //any address
    //bind(int sock, (struct sockaddr *)address, socklen_t address_len);
    bind(sListen, (struct sockaddr *) &local, sizeof(SOCKADDR_IN));

    listen(sListen, 1);


    while(client_quan<=15){
        sClient[client_quan] = accept(sListen, (struct sockaddr *) &client, &iaddrSize); //block here
        printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr),
               ntohs(client.sin_port));
        thread handle_handle_clients_thread(handle_clients, client_quan); 
        handle_handle_clients_thread.detach();
        client_quan++;
    }
}

void handle_clients(const int client_id){
    while (true) {
        cap>>frame;
        imshow("Send Image", frame);
        sendImg(frame, client_id);
        waitKey(100);
    }
}


void sendImg(Mat frame, int client_quan){
    imencode(".jpg", frame, data_encode, {CV_IMWRITE_JPEG_QUALITY, 50}); // convert img to string
    int len_encode = data_encode.size(); //get string length
    string len = to_string(len_encode); //convert int to string(for sending)
    int length = len.length(); //get string length
    for (int i = 0; i < 10 - length; i++) //client side receive 10 chars first
    {
        len = len + " ";
    }
    while(client_quan>=0){
        send(sClient[client_quan], len.c_str(), strlen(len.c_str()), 0); //send length first
        char send_char[1]; // the type needs char[]
        // send it char by char
        for (int i = 0; i < len_encode; i++)
        {
            send_char[0] = data_encode[i];
            send(sClient[client_quan], send_char, 1, 0); // send
        }
        client_quan--;
    }

}

这是客户的代码

#include <WINSOCK2.H>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
//定義程式中使用的常數
#define SERVER_ADDRESS "127.0.0.1" //伺服器端IP地址
#define PORT           5150         //伺服器的埠號
#define MSGSIZE        10000         //收發緩衝區的大小
#pragma comment(lib, "ws2_32.lib")
using  namespace  std;
using  namespace  cv;

Mat recvImg();
void handle_connections();

// Declare socket
SOCKET sClient;
char recvBuf[16]; // used to save the length of string
char recvBuf_1[1]; // save data char by char
vector<u_char> data; // used to load sent data
Mat img_decode(0,3,CV_32FC1); // save result

float fx = 1.5;
float fy = 1.5;
int main()
{
    handle_connections();
    while (TRUE) {
        Mat img_decode = recvImg();
        imshow("Received Image", img_decode);
        //TODO: finish stream transmit(change pic into the img that cam read)
        waitKey(100);
    }
    // 釋放連線和進行結束工作
    closesocket(sClient);
    WSACleanup();
    return 0;
}

Mat recvImg(){

    if (recv(sClient, recvBuf, 10, 0))
    {
        for (int i = 0; i < 10; i++){
            if (recvBuf[i]<'0' || recvBuf[i]>'9') recvBuf[i] = ' '; // save length
        }
        data.resize(atoi(recvBuf)); //convert string to int
        for (int i = 0; i < atoi(recvBuf); i++){
            recv(sClient, recvBuf_1, 1, 0); //load data char by char
            data[i] = recvBuf_1[0];

        }
        img_decode = imdecode(data, CV_LOAD_IMAGE_COLOR);  // decode when transmit done
        //resize(img_decode, img_decode, Size(), fx, fy, INTER_CUBIC);
        return img_decode;
    }
}


void handle_connections(){
    // Initialize Windows socket library
    WSADATA wsaData;
    WSAStartup(0x0202, &wsaData); // Winsock version2.2
    
    //store the info of server socket
    SOCKADDR_IN server;

    //setting socket (local/internet/ipv4/ipv6, UDP/TCP, socket protocol)
    sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    memset(&server, 0, sizeof(SOCKADDR_IN)); //先將儲存地址的server置為全0

    server.sin_family = PF_INET; //TCP
    server.sin_port = htons(PORT); //convert Host Byte Order to Network Byte Order
    server.sin_addr.s_addr = inet_addr(SERVER_ADDRESS); //convert address to binary
    connect(sClient, (struct sockaddr *) &server, sizeof(SOCKADDR_IN));
    cout<< "connected to Server";
}

我所做的是使用一个始终等待新连接的线程,并在有新连接时启动一个新线程来处理客户端(不断向其发送图像)。有没有什么办法可以更省资源?(当客户端数量为4~5时,程序会逐渐崩溃……而我想要实现的是一个类似于屏幕的本地网络工具广播系统)

强文本

标签: c++sockets

解决方案


send(sClient[client_quan], len.c_str(), strlen(len.c_str()), 0);
    //send length first

的返回值send()被忽略。

// ...

send(sClient[client_quan], send_char, 1, 0);

返回值也被忽略,并且此循环send()一次一个字节。这是非常低效的,并且是高 CPU 负载的明显原因。MS-Windows 的线程实现以效率低下而著称,并且让多个线程不断地在内核模式之间切换,只是为了向套接字发送一个字节,这会浪费大量的 CPU 时间。

send()this和send()长度计数的返回值都被完全忽略。这也是一个错误。send()由于初始套接字在开始时有一个相当大的未使用的空缓冲区,因此没有注意到初始长度的错误。但这仍然是一个错误。

从网络套接字读取和写入时:

  1. 读取和写入都应该使用大缓冲区。
  2. 必须始终检查read、write、send 或 recv 的返回值以确定实际读取和写入了多少字节,并且应用程序必须具有正确的逻辑来更新其缓冲区、指针等,以反映有多少数据实际上被处理了。

显示的代码已经在缓冲区中拥有要写入套接字的整个数据块。那么,为什么要一次发送一个字符呢?显示的代码应该简单地添加长度,然后尝试一次发送所有内容,然后检查实际发送了多少字符,然后尝试发送缓冲区的其余部分,依此类推,直到发送整个图像。


推荐阅读