c++ - 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时,程序会逐渐崩溃……而我想要实现的是一个类似于屏幕的本地网络工具广播系统)
强文本
解决方案
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()
由于初始套接字在开始时有一个相当大的未使用的空缓冲区,因此没有注意到初始长度的错误。但这仍然是一个错误。
从网络套接字读取和写入时:
- 读取和写入都应该使用大缓冲区。
- 必须始终检查read、write、send 或 recv 的返回值以确定实际读取和写入了多少字节,并且应用程序必须具有正确的逻辑来更新其缓冲区、指针等,以反映有多少数据实际上被处理了。
显示的代码已经在缓冲区中拥有要写入套接字的整个数据块。那么,为什么要一次发送一个字符呢?显示的代码应该简单地添加长度,然后尝试一次发送所有内容,然后检查实际发送了多少字符,然后尝试发送缓冲区的其余部分,依此类推,直到发送整个图像。
推荐阅读
- wpf - 如何在考虑命名空间的情况下导航到 Prism 区域中的视图
- sql - 在 SQL 中编写动态查询
- python - 编写 django rest 序列化程序的问题
- r - 为 geom_smooth 添加年龄调整
- wpf - WPF stringformat 仅显示数字
- javascript - 如何去除材料ui的文本字段的边缘
- c++ - 如何选择正确的运算符重载
- bash - 在 Bash 脚本中回显变量时如何转义特殊字符?
- c# - 未找到 WebApplicationContext.cs,System.Web.HttpException:请求在此上下文中不可用
- node.js - 获取所有通过的学生并计数