前几天接到老师的一个小任务,要求用C++编写一个socket控制超声机并接收来自超声机发送的数据。这个任务师兄本来已经用python完成了,但是我们的总的项目是用C++写的,所以要集成在一起就必须用c++写一个。socket本身比较简单,但我的c++能力一般,socket之前更是没听说过,所以在做的过程中遇到了很多问题,好在费了一番功夫之后解决了问题,实现了要求。在做的过程中在CSDN上查阅了大量的资料,感觉CSDN确实是一个好地方,所以也打算写一次博客记录这次任务,同时也恳请大家批评指正。
1.任务要求
(1)监听端口3001,连接后,接受客户端发送以下两个命令,需以\n结尾
freeze
unfreeze
(2)监听端口3000,客户端连接后,实时下持续发送图像数据,数据帧格式为:
quint32 flag; //固定值0xAAAA5555
quint32 time_stamp; //从解冻开始,以us为单位
quint32 buf_size; //固定值640*480
quint8 buffer[buf_size]; //8位图像像素数据
任务简介:在上面的要求当中,socket的sever端已经被集成在超声机的系统中,采用的是TCP/IP协议,我们无法更改,所以我们只需要实现client端即可,并且采用TCP/IP协议。
2.实现第一个要求
编译环境:vs2010
项目形式:32位控制台应用程序
参考资料链接:
https://www.cnblogs.com/yuqiao/p/5786427.html
https://blog.csdn.net/xiaoquantouer/article/details/58001960
https://www.cnblogs.com/kefeiGame/p/7246942.html
我的代码:
#include<WINSOCK2.H>
#include<STDIO.H>
#include<iostream>
#include<cstring>
#include<string>
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
//初始化WSA,我看别人的代码都加上了这段代码,所以就默认加上了
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if(WSAStartup(sockVersion, &data)!=0)
{
return 0;
}
//循环创建和关闭socket,并发送指令
while(true)
{
//创建套接字,采用SOCK_STREAM形式,即TCP协议,保证接收数据的可靠性
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET) //如果没有创建成功,就发出创建失败的信息打印在屏幕上
{
printf("invalid socket!");
return 0;
}
//绑定IP和端口
sockaddr_in serAddr; //可以理解为类型和变量的关系,实际上就是一个声明
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(3001); //绑定端口号,即client的端口号是3001
serAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.125");//这个按我的理解是绑
//定服务器的IP地址,就是客户端的目的地
if(connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{ // 创建socket并绑定端口号和目的地地址后,要通过connect连接sever端,连接失败
printf("connect error !"); //会在屏幕上打印信息
closesocket(sclient);
return 0;
}
//由于我感觉在屏幕上打英文单词太麻烦,就把用数字来代替指令
string data;
int a=0;
cout<<"please enter a number:1 = freeze and 2 = unfreeze \n ";
BEGIN:
cin>>a;
if(a==1) //也可以用switch实现
{
data="freeze\n";
}
else if(a==2)
{
data="unfreeze\n";
}
else{
cout<<"enter error!please enter 1 or 2:\n";
goto BEGIN;
}
const char * sendData;
sendData = data.c_str(); //string转const char* 这是我在网上代码基础上改的,
//我认为直接定义一个字符型数组就可以了,不需要转换
send(sclient, sendData, strlen(sendData), 0);
//send()用来将数据由指定的socket传给对方主机
//int send(int s, const void * msg, int len, unsigned int flags)
//s为已建立好连接的socket,msg为指向数据内容的指针,len则为数据长度(单位是字节),参数flags一般设0
//成功则返回实际传送出去的字符数,失败返回-1,错误原因存于error
closesocket(sclient); //关闭套接字
}
WSACleanup();
return 0;
}
我的代码比较简单,本身c++水平也比较低,能实现要求我就尽量运用简单的语法。
socket最关键的几个函数就是socket(),bind(),listen(),accept(),send(),recv()等,主要就是了解他们的参数和参数意义,在我上面的链接中有详细介绍,大概看个十几分钟就能现学现用了。
3.实现第二个要求
编译环境:vs2010
实现思路:这部分要求其实就是接受数据而已,用recv()接收数据存在数组里面就OK了,我为了检测数组里面的数据是否正确,就生成了BMP位图,自己写的位图头文件等信息,结果反而走了弯路,浪费了些时间,不过也学到了些知识。
难点:如何定量接收数据。
我的代码;
#include<WINSOCK2.H>
#include<iostream>
#include<cstring>
#include<string>
#include "time.h"
using namespace std;
#pragma comment(lib, "ws2_32.lib")
int main()
{
//初始化WSA,固定套路,加上就可以了
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if(WSAStartup(sockVersion, &data)!=0)
{
return 0;
}
clock_t start, finish; //时间函数变量声明,为了测量程序运行时间
double duration; //持续时间声明
/*
{
BITMAPFILEHEADER fileHead; //位图头文件,大小14字节
fileHead.bfType=(WORD)0x4D42;//表明是BMP位图文件
fileHead.bfSize=308278;//图片总大小:640*480+14+40+1024
fileHead.bfReserved1=0;//保留为0
fileHead.bfReserved2=0;//保留为0
fileHead.bfOffBits=1078;//从位图最开始字节到位图像素数据字节
BITMAPINFOHEADER infoHead; //位图信息头
infoHead.biSize=40; //信息头大小
infoHead.biWidth= 640;//宽度
infoHead.biHeight=480;//高度
infoHead.biPlanes=1; //目标设备位平面数,其值设置为1
infoHead.biBitCount=8; //每个像素所占位数
infoHead.biCompression=0;//表示不压缩
infoHead.biSizeImage=0; //压缩图像大小的字节数,为压缩图像为0
infoHead.biXPelsPerMeter=0;//水平分辨率(像素点每米)默认为0
infoHead.biYPelsPerMeter=0;//垂直分辨率(像素点每米)默认为0
infoHead.biClrUsed=256;//调色板颜色数
infoHead.biClrImportant=0; //颜色都重要,设为0
RGBQUAD *ipRGB2 = (RGBQUAD *)malloc(256*sizeof(RGBQUAD));//位图调色板
for ( int m = 0; m < 256; m++ )
{ //灰度图像,红绿蓝颜色分量相等
ipRGB2[m].rgbRed = ipRGB2[m].rgbGreen = ipRGB2[m].rgbBlue = m;
ipRGB2[m].rgbReserved=0;}
}
*/
start = clock(); //开始计时
//创建套接字
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket!");
return 0;
}
//绑定IP和端口
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(3000);//端口号
serAddr.sin_addr.S_un.S_addr = inet_addr("192.168.1.125");//绑定服务器IP
if(connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{ //连接失败
printf("connect error !");
closesocket(sclient);
return 0;
}
char PICTUREDATA[307200]; //设置缓存数组,长度307200,640*480
//int i=1; //为了循环使用
// while(i<=1) //不循环可以去掉这个while
//{
//cout<<i<<endl;
//i++;
char HeadData[12]; //存储头信息,包括flag,时间戳,图像大小
recv(sclient, HeadData, 12, 0); //接收数据头信息,包括标志位、时间戳、大小
int n=0;
char PictureData[3072];
int k = 0;
while(n<307200) //循环接收一幅图像的像素数据,严格保证接收307200个字节数据
{ //并存在数组中
int a = recv(sclient,PictureData, 3072, 0);
// cout<<a<<" "; //a是实际接收的字节数,不要以为指定了3072个字节它的服务器
//端就真的每次发送3072个字节
while(k<a)
{
PICTUREDATA[k+n] = PictureData[k];
k++;
}
k = 0;
n += a;
// cout<<n<<endl;
if((0!=(307200-n))&&((307200-n)<3072))
{
a = recv(sclient,PictureData, (307200-n), 0);
while(k<a)
{
PICTUREDATA[k+n] = PictureData[k];
k++;
}
n = 307200;
}
}
//}
closesocket(sclient); //关闭SOCket
WSACleanup();
finish = clock(); //接收一幅图片数据的时间
duration = (double)(finish - start) / CLOCKS_PER_SEC;
printf( "%f seconds\n", duration );
//char FileName[40];//存文件名的数组
//sprintf_s(FileName,"F:\\Socket\\data%d.bmp",1); //文件名
//FILE* fp=fopen(FileName,"wb+"); //以可读写方式创建文件
//fwrite (&fileHead, sizeof(BITMAPFILEHEADER), 1, fp );//写入位图头文件
//fwrite (&infoHead, sizeof( BITMAPINFOHEADER), 1, fp );//写入位图头信息
//fwrite (ipRGB2, 1024, 1, fp );//写入位图调色板
//fwrite (PICTUREDATA, sizeof(char), 307200, fp );//写入数据信息
//fclose(fp); //关闭文件
return 0;
}
BMP位图链接:https://blog.csdn.net/caicai_zju/article/details/51008892
一个隐形BUG:https://blog.csdn.net/cnhk1225/article/details/77883701
fopen,fwrite用法:https://blog.csdn.net/yang2011079080010/article/details/52528261
在windows平台下写数据时一定要用二进制形式写数据,否则会莫名其妙多若干个字节,这是我认为的一个隐形bug,坑了我整整两天,写成位图时莫名其妙多几百个字节的数据,还好我用UltralEdit发现了,其实还是我编程经验少。其实要是不写成文件的话就不会多字节。
我写的这个程序最后会封装成一个函数,在项目的大程序里调用一下就存一个图片的数据,所有有些代码还没有添加上。
关于socket我认为还是比较简单的,网上的很多代码只需要简单修改就可以了,主要是对socket的几个函数参数多了解就行了。我也只是对应用有所了解,对底层原理我不太了解,所以有些叙述可能有错误,欢迎大家批评指正。
最后要说的是我用的是vs2010写的代码,运行是没问题的,但是在2015平台上有个地方会报错,不过不用担心,把报错信息百度下就有结果了。