博文由来:笔者突发奇想,做采集4个USB摄像头画面小实验时,却遇到了在电脑上最多只能同时开启3个这样头痛的问题(个人分析认为是电脑的问题),故出此下策,在客户端挂1个,服务器挂3个摄像头,利用socket进行视频传输,本篇文章是利用的是TCP协议。笔者拙见,欢迎提出批评。
说明:在此之前笔者也从未接触过socket编程,也不懂TCP/IP、UDP等基本知识。为了避免让读者感到繁琐,我将运用小学及初中学习的总分总、承上启下的写作技巧进行讲解,力图做到详略有致,言简意赅,通俗易懂。如确实还有问题,可以直接回复博客或图文并茂的将问题邮件给笔者([email protected]),笔者知识有限,但尽量答复。
实验平台:VS2010 + opencv2.4.10(其他版本搭配也是一样的做法)
准备工作:
1、一台电脑,能跑vs和opencv,用来运行服务器(server)和客户端(client)代码;
2、一个摄像头(笔记本摄像头、USB摄像头等,网络摄像头我还没接触过,暂不考虑)。
在同一台电脑上实验,即运行服务器程序,又跑客户端程序,也就是说通过socket编程来实现数据的自发自收,这一步通过了接下来跑服务器和客户端分开的实验就简单了。两台PC之间的视频传输见下一节:【二】Opencv结合socket进行视频传输(TCP协议)
那我们先在就开始吧,不用等下一曲了。
实验1、简单的数据传输,分三步。
①建立一个名为server的vs空项目,再新建demo.cpp源文件,将下面的代码拷贝到demo.cpp中,编译好后关闭vs。
#include "stdafx.h"
#include
#include
#pragma comment(lib,"ws2_32.lib")
int main(void)
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[255];
while (true)
{
printf("等待连接...\n");
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
printf("accept error !");
continue;
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//接收数据
int ret = recv(sClient, revData, 255, 0);
if(ret > 0)
{
revData[ret] = 0x00;
printf(revData);
}
//发送数据
char * sendData = "你好,TCP客户端!\n";
send(sClient, sendData, strlen(sendData), 0);
closesocket(sClient);
}
closesocket(slisten);
WSACleanup();
return 0;
}
②建立一个名为client的vs空项目,再新建demo.cpp源文件,将下面的代码拷贝到demo.cpp中,编译好后关闭vs。
#include "stdafx.h"
#include
#include
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
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;
}
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;
}
char * sendData = "你好,TCP服务端,我是客户端!\n";
send(sclient, sendData, strlen(sendData), 0);
char recData[255];
int ret = recv(sclient, recData, 255, 0);
if(ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}
closesocket(sclient);
WSACleanup();
return 0;
}
③同时打开server和client这两个解决方案。先运行server;再运行client,你会发现二者建立起了通信,并相互之间成功将数据进行了传输。
到此我们已经学会了怎么使用socket进行发送接收数据了,这里有两个函数send()和recv()。注意:127.0.0.1这个IP地址是回送地址(自发自收),每台电脑的回环地址都是这个IP,是固定的。很多时候我们组装电脑时给电脑插网卡,就可以在cmd窗口里面ping此IP,若不通则说明网卡没插好。
实验2、简单的图片传输,分三步。
接下来我们就在实验1的基础上添加入opencv代码,读取图片,为尽量简单,我们将图像灰度化。设置好opencv的include和lib路径及依赖项后,就可以开始动手敲代码了。
①先看client端,思路是将读出的图像数据放到一个数组里面,再用send()发送出去,代码如下:
#include "stdafx.h"
#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
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 !\n");
return 0;
}
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 !\n");
closesocket(sclient);
return 0;
}
//读取图像并发送
IplImage *image_src = cvLoadImage("wall.jpg");//载入图片
IplImage *image_dst = cvCreateImage(cvSize(640, 480), 8, 1);//放置灰度图像
int i, j;
char sendData[1000000] = "";
cvNamedWindow("client", 1);
cvCvtColor(image_src, image_dst, CV_RGB2GRAY);
for(i = 0; i < image_dst->height; i++)
{
for (j = 0; j < image_dst->width; j++)
{
sendData[image_dst->width * i + j] = ((char *)(image_dst->imageData + i * image_dst->widthStep))[j];
}
}
cvShowImage("client", image_dst);
cvWaitKey(0);//任意键发送
send(sclient, sendData, 1000000, 0);//发送
cvDestroyWindow("client");
closesocket(sclient);
WSACleanup();
return 0;
}
②server端,思路是将recv()接收到的数据放到一个数组里面,再还原为图像数据,代码如下:
#include "stdafx.h"
#include
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
return 0;
}
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
printf("socket error !");
return 0;
}
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);//端口8888
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("bind error !");
}
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
printf("listen error !");
return 0;
}
//循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
printf("等待连接...\n");
do
{
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
}while(sClient == INVALID_SOCKET);
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
char revData[1000000] = "";
IplImage *image_src = cvCreateImage(cvSize(640, 480), 8, 1);//接收灰度图像
int i, j;
int ret;
cvNamedWindow("server", 1);
while(true)
{
//接收数据
ret = recv(sClient, revData, 1000000, 0);
if(ret > 0)
{
revData[ret] = 0x00;
for(i = 0; i < image_src->height; i++)
{
for (j = 0; j < image_src->width; j++)
{
((char *)(image_src->imageData + i * image_src->widthStep))[j] = revData[image_src->width * i + j];
}
}
ret = 0;
}
cvShowImage("server", image_src);
cvWaitKey(1);
}
cvDestroyWindow("server");
closesocket(slisten);
WSACleanup();
return 0;
}
③ 先运行server,再运行client。在client端,按任意键即可发送数据,而server端马上会收到数据并还原为图像显示出来。
经过上述的实验及讲解,可以对使用socket的编程有一定的了解。如果能够启发读者想去探索,并开发些好玩的东西出来,那自然是一件幸事,但这里就不深入了。
本次实验所需要的所有文件都已打包上传,此处下载:http://download.csdn.net/detail/hujingshuang/8400083
特别注意:
1、本博客例程仅做学习交流用,切勿用于商业用途。
2、欢迎交流,转载请注明出处:http://blog.csdn.net/hujingshuang/article/details/43191461