基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二)

作品已经完成,先上源码:

https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip

全文包含三篇,这是第二篇,主要讲述发送端程序的原理和过程。

第一篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一)

第三篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三)

以下是正文:

  发送端程序基于MFC的对话框类实现,开发环境Visual Studio 2012,主要实现了5个功能,下面逐个讲述:

  1、软件启动检查互斥体,防止程序重复启动。

  2、读取上一次启动的配置文件,初始化socket、获取本机ip地址。

  3、读取用户输入的接收端IP地址,利用Core Audio APIs初始化loopback(环回录音)模式,启动录音子线程。

  4、在子线程不断读取音频缓冲区数据,每0.1s将录制的数据打包以PCM格式,通过socket发送到接收端。

  5、最小化到系统托盘

一、检查互斥体

  创建互斥体是防止应用程序重复启动最常用的方式,本作品使用Core Audio APIs读取声卡音频数据,只能实例化一次。这是因为,这个作品完成后,作者在使用的过程中,发送端软件在运行一段时间后,总是不定期莫名其妙地出现“appcrash”错误,然后程序莫名崩溃,后来发现是因为作者之前使用过一个叫“wifiaudio”的程序,这个程序也是一样利用Core Audio APIs实现声卡的环回录音,而且它老是开机自启动,这样当我也运行这个作品的时候,两个程序就出现冲突,导致本作品运行不稳定,在解决了这个问题之后,作者也在作品中增加检查互斥体的功能,防止程序重复启动。

  以下是在应用程序实例化时增加的代码。

    //创建互斥体,防止应用程序重复启动,by Hecan

    HANDLE hMutex = ::CreateMutex(NULL, FALSE, "WifiSpeaker by Hecan");
    DWORD dwRet = ::GetLastError();

    if (hMutex)
    {
        if (ERROR_ALREADY_EXISTS == dwRet)
        {
            AfxMessageBox("应用程序已经运行,请关闭后重试!!!");
            CloseHandle(hMutex);  // should be closed
            return FALSE;
        }
    }
    else
        AfxMessageBox("创建互斥体错误,请检查源代码WiFiSpeaker.cpp");

  最后建议在dlg.DoModal()返回后增加关闭句柄的代码,虽然这工作在软件退出时系统会自动完成,但不建议由系统来做。

// 关闭互斥体句柄
    CloseHandle(hMutex);  

二、读取上一次启动的配置文件,初始化socket

  上一次启动的配置文件默认保存在可执行文件当前的目录下,后缀名为bin,这个文件只有一个作用,就是保存用户上一次退出时设定的接收端IP地址,减少用户每次打开程序都要设置IP的麻烦,这个文件固定16个字节,实际就是m_ClientAddr这个成员变量以2进制形式保存在bin文件中,m_ClientAddr成员变量的类型为SOCKADDR_IN结构体。

  代码中注意一下:

  1、发送端配置的端口为12320,接收端端为12321,这个是在程序中固化的,没有提供给用户做修改,这个值只能在源代码中修改后重新编译。修改后,接收端对应的本机端口也要同步修改。

  2、初始化中使用ioctlsocket函数把socket配置为非阻塞模式,这样后面调用sendto函数后,函数会立即返回。因为是UDP协议,数据发送后不需要关心接收端有没有收到,直接返回即可,提高程序的执行效率。

  3、BuffDuration_millisec是成员变量,表示初始化音频客户端请求的数据缓冲区大小,以毫秒为单位。后面会讲到。

  初始化代码如下:

BOOL CWiFiSpeakerDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
    //  执行此操作
    SetIcon(m_hIcon, TRUE);            // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // TODO: 在此添加额外的初始化代码

/*--------------------------------------------------------------------------------------------------------*/
    //读取初始化文件,如果没有,则按照默认192.168.1.100的ip地址初始化客户端ip,客户端口设为12321
    CFile iniFile;
    //iniFile.Open("./WiFiSpeaker.bin",CFile::modeReadWrite |CFile::modeCreate|CFile::modeNoTruncate);
    volatile BOOL resul = iniFile.Open("./WiFiSpeaker.bin",CFile::modeReadWrite |CFile::modeCreate|CFile::modeNoTruncate);

    if(iniFile.GetLength() == sizeof(m_ClientAddr))
        iniFile.Read(&m_ClientAddr,sizeof(m_ClientAddr));
    else
    {
        m_ClientAddr.sin_family = AF_INET;
        m_ClientAddr.sin_port =  htons(12321);
        m_ClientAddr.sin_addr.S_un.S_addr =inet_addr("192.168.1.100");
    }
    iniFile.Close();

    //初始化服务器IP地址,获取本机IP地址,服务器端口设置设为12320
    m_ServerAddr.sin_family = AF_INET;
    m_ServerAddr.sin_port = htons(12320);
    m_ServerAddr.sin_addr = GetLocalIPAddr();

    //把IP地址转为字符串并显示在编辑框中
    char a[15];
    sprintf_s(a,"%d.%d.%d.%d",m_ServerAddr.sin_addr.S_un.S_un_b.s_b1,m_ServerAddr.sin_addr.S_un.S_un_b.s_b2,m_ServerAddr.sin_addr.S_un.S_un_b.s_b3,m_ServerAddr.sin_addr.S_un.S_un_b.s_b4);
    this->SetDlgItemText(IDC_EDIT1,a);//服务器(本机)ip
    sprintf_s(a,"%d.%d.%d.%d",m_ClientAddr.sin_addr.S_un.S_un_b.s_b1,m_ClientAddr.sin_addr.S_un.S_un_b.s_b2,m_ClientAddr.sin_addr.S_un.S_un_b.s_b3,m_ClientAddr.sin_addr.S_un.S_un_b.s_b4);
    this->SetDlgItemText(IDC_EDIT2,a);//客户端ip

    this->GetDlgItem(IDC_BUTTON2)->EnableWindow(FALSE);//停止按钮禁用

    //初始化socket并绑定到主机地址,UDP模式
    m_socket = socket(AF_INET,SOCK_DGRAM,0);
    bind(m_socket,(SOCKADDR*)&m_ServerAddr,sizeof(SOCKADDR));//绑定套接字  

    u_long mode = 1;
    ioctlsocket(m_socket,FIONBIO,&mode);//设置为非阻塞模式(sendto函数立即返回)

/*---------------------------------------------------------------------------------------------------------*/
    //设置0.1s时长的音频缓冲区
    BuffDuration_millisec = 100;

    //初始化成员变量
    pAudioClient = NULL;
    pCaptureClient = NULL;
    pwfx =NULL;

/*---------------------------------------------------------------------------------------------------------*/
    //对话框初始化在屏幕右下角位置
    CRect dlg_windows,sysWorkArea;
    SystemParametersInfo(SPI_GETWORKAREA,0,&sysWorkArea,0);
    GetWindowRect(&dlg_windows);
    SetWindowPos(NULL,sysWorkArea.right-dlg_windows.right, sysWorkArea.bottom-dlg_windows.bottom, 0, 0, SWP_NOSIZE | SWP_NOZORDER);

    return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

三、启动按钮——读取用户输入的接收端IP地址,初始化loopback(环回录音)模式,启动录音子线程

  点击启动按钮后,首先读取用户输入的接收端IP地址,并存放在m_ClientAddr成员变量中。

  初始化音频客户端为loopback模式,这部分代码是参考msdn上的:https://msdn.microsoft.com/en-us/library/windows/desktop/dd316551(v=vs.85).aspx,主要有两个地方要注意:

  1、IMMDeviceEnumerator::GetDefaultAudioEndpoint函数的第一个参数必须为eRender。

  2、IAudioClient::Initialize函数第二个参数需配置为AUDCLNT_STREAMFLAGS_LOOPBACK。

  下面主要讲述IAudioClient::Initialize函数,这个函数的声明如下:

HRESULT Initialize(
  [in]       AUDCLNT_SHAREMODE ShareMode,
  [in]       DWORD             StreamFlags,
  [in]       REFERENCE_TIME    hnsBufferDuration,
  [in]       REFERENCE_TIME    hnsPeriodicity,
  [in] const WAVEFORMATEX      *pFormat,
  [in]       LPCGUID           AudioSessionGuid
);

  全部都是输入参数,

  ShareMode:共享模式独占还是共享,AUDCLNT_SHAREMODE_EXCLUSIVE或者AUDCLNT_SHAREMODE_SHARED,一般设置为AUDCLNT_SHAREMODE_SHARED。涉及知识产权问题时才使用独占模式。

  StreamFlags:流标志,本程序必须设为环回录音模式,AUDCLNT_STREAMFLAGS_LOOPBACK。

  pFormat:指定格式描述符,在程序中,我们先调用IAudioClient::GetMixFormat函数,获取声卡默认的录音格式,再做适当修改,例如把采样位深度修改由32位调整为16位,有助于减少录制的音频数据量。

  hnsBufferDuration:申请的buff持续时间,以100ns为单位,这个参数很重要,它指定了我们存放录音数据缓冲区的大小,它是以时间为单位的。举个例子,如果pFormat指定的音频格式为48kHz、双通道、16位深、无压缩的音频数据,那1s的数据量是48000×2×2=192000字节。如果把这个参数指定为1s,那么函数就会给程序分配192k字节的空间。在本程序中,设定每0.05s发送一次音频数据,所以把这个参数设定为0.1s,即两倍大小的缓冲区。

  hnsPeriodicity、AudioSessionGuid:未使用,置为空即可。

  调用该函数初始化音频客户端之后,必须使用IAudioClient::GetBufferSize获取系统分配给程序的缓冲区大小:

HRESULT GetBufferSize(
  [out] UINT32 *pNumBufferFrames
);

  这个函数只有一个参数,指向UINT32类型变量的指针,这个变量用来存放系统给程序分配的缓冲区大小,以帧为单位。这里解释一下帧的含义,采样一次即为一帧。2通道、32位深的音频数据,一帧就有2×4=8个字节。看回上面的例子,48kHz、2通道、16位深的音频数据,调用IAudioClient::Initialize函数申请0.1s的缓冲区,正常情况下,IAudioClient::GetBufferSize函数会返回4800,表示系统分配了4800帧、19200字节的缓冲区。

  申请内存后,就可以调用AfxBeginThread函数启动录音及发送音频数据子线程。以下为点击启动按钮的处理代码:

void CWiFiSpeakerDlg::OnBnClickedButton1()
{
    // TODO: 在此添加控件通知处理程序代码

    //读取设定的客户端IP地址并存放到m_ClientAddr成员变量中
    CString strIP;
    this->GetDlgItemText(IDC_EDIT2,strIP);
    m_ClientAddr.sin_addr.S_un.S_addr = inet_addr(strIP.GetBuffer(strIP.GetLength()));  

    //检测输入的IP地址是否有误
    if(m_ClientAddr.sin_addr.S_un.S_addr == 0xffffffff)
    {
        AfxMessageBox("客户端IP地址输入有误!!!");
        return;
    }

/*----------------------------------------------------------------------------------*/
//以下为实现系统录音的代码,大部分都是参考MSDN的例程
//捕获(录音)例程:https://msdn.microsoft.com/en-us/library/windows/desktop/dd370800(v=vs.85).aspx
//环回录音()系统录音例程:https://msdn.microsoft.com/en-us/library/windows/desktop/dd316551(v=vs.85).aspx

    HRESULT hr;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;

    //指定初始化函数分配100ms的缓冲区,音频设备的初始化函数只接受时间参数来分配内存空间,不能直接指定要多少字节
    //例如44100Hz的音频,0.1s就有4410帧数据(1帧就是一次采样的数据量),如果是2通道,16位的话,那1帧数据就是4个字节,0.1s共17640字节
    REFERENCE_TIME hnsRequestedDuration = BuffDuration_millisec*REFTIMES_PER_MILLISEC;

    //系统分配给我们的缓冲区,和上面的参数有关,以帧为单位,一般情况下我们申请的多长时间,按照采样率就给我们分配多少帧的音频缓冲区
    UINT32 bufferFrameCount;
    //临时的字符串变量
    CString tempstr;

    //获取设备枚举器
    hr = CoCreateInstance(
           CLSID_MMDeviceEnumerator, NULL,
           CLSCTX_ALL, IID_IMMDeviceEnumerator,
           (void**)&pEnumerator);

    //获取默认音频设备,注意,后面要初始化环回录音模式,这里必须是eRender参数,不能使用eCapture
    hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice );

    //激活音频客户端
    hr = pDevice->Activate( IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient);

    SAFE_RELEASE(pEnumerator);//pEnumerator已使用完,释放掉
    SAFE_RELEASE(pDevice);
    if (FAILED(hr)) {this->SetDlgItemText(IDC_EDIT3,"初始化设备失败code:1!");return;}    //错误退出

    //获取默认的音频格式
    hr = pAudioClient->GetMixFormat(&pwfx);

    //调整为16位,PCM格式
    AdjustFormatTo16Bits(pwfx);

    //音频客户端初始化,共享模式、换回录音模式、申请0.1s的缓冲区
    hr = pAudioClient->Initialize(
                         AUDCLNT_SHAREMODE_SHARED,
                         AUDCLNT_STREAMFLAGS_LOOPBACK,
                         hnsRequestedDuration,
                         0,
                         pwfx,
                         NULL);
    if (FAILED(hr)) {this->SetDlgItemText(IDC_EDIT3,"初始化设备失败code:2!");ErrorProcess();return;}    //错误处理

    //查看系统实际给我们分配多少的缓冲区
    hr = pAudioClient->GetBufferSize(&bufferFrameCount);
    tempstr.Format("目标ip:%s\r\n%d采样率%d通道%d位深\r\n实际系统分配缓冲区%d帧\r\n",strIP,pwfx->nSamplesPerSec,pwfx->nChannels,pwfx->wBitsPerSample,bufferFrameCount);
    this->SetDlgItemText(IDC_EDIT3,tempstr);

    //以下直接启动录音线程,因为pAudioClient->GetService和release()必须在同一个线程使用,所以只能在新线程里获取服务和启动录音。
    //启动录音处理线程,所有的音频数据的读取、打包、发送都在这个线程完成
    AfxBeginThread(RecordAndSendAudioStreamThread,this);

    bThreadisRunning = TRUE;

/*----------------------------------------------------------------------------------------*/
    this->GetDlgItem(IDC_EDIT2)->EnableWindow(FALSE);//编辑框只读。
    this->GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE);//开始按钮禁用
    this->GetDlgItem(IDC_BUTTON2)->EnableWindow(TRUE);//停止按钮恢复
    return;
}

四、录音及发送音频数据子线程

  子线程的工作就是启动录音,然后在循环中不断读取之前设置的音频缓冲区,再通过socket发送出去。这里有4点需要注意的:

  1、用来存放音频数据的缓冲区,作者在程序中是定义了一个long型的全局数组,有5000个数据大小。这个数组非常大,不能在子线程里面定义这个数组,因为系统为子线程分配的堆栈空间有限,所以如果在子线程里定义这么大的数组,会导致软件运行崩溃。

  2、设定每0.05s发送一次音频数据,但是0.05s的音频数据无法一次全部读出来,只能通过while循环,重复读取系统缓冲区,直至全部读出来为止。实际在测试中,可能由于线程调度导致延迟的关系,每0.05s的数据量有时会多一点,有时会少一点,所以之前初始化申请的缓冲区是按照0.05s的两倍来申请的,防止数据溢出被覆盖。

  3、双通道、16位深的音频数据,一帧数据是4个字节,所以程序中以long型数据代表一帧数据,这样在后续调用mencopy函数时就不用考虑字节对齐的问题了,相对比较方便。

  4、数据包的格式问题,作者人为地设定数据包的前40个字节为数据格式描述,实际就是把pwfx这个变量的内容,作为包头附到数据包中。这样,在接收端就可以根据数据包的包头获取数据的分辨率、位深等信息了。

//启动录音处理线程,所有的音频数据的读取、打包、发送都在这个线程完成
UINT RecordAndSendAudioStreamThread(LPVOID pParam )
{
    CWiFiSpeakerDlg* dlg=(CWiFiSpeakerDlg*) pParam;
    HRESULT hr;

    //缓冲区的下一个数据包的长度,以帧为单位
    UINT32 packetLength = 0;

    //缓冲区一次可以读取的帧数量,这个参数和上面那个的数值是一样的
    //至于为什么要设两个,是因为使用的情况不一样
    //上面那个是以函数返回值的形式返回,这个是以形参的形式跟缓冲区起始地址一起返回的
    UINT32 numFramesAvailable = 0;

    //标志位,指示静音什么的,这里不用
    DWORD flags;
    //这个是数据缓冲区,传递给函数的指针变量
    BYTE *pData;

    //计数器,记录读了多少数据帧数据
    UINT32 Counter=0;

    //把音频格式结构体复制到DataToSend中,占40个字节,真正的音频数据从第41个字节开始
    if(dlg->pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
        memcpy(DataToSend,dlg->pwfx,sizeof(WAVEFORMATEXTENSIBLE));
    else
        memcpy(DataToSend,dlg->pwfx,sizeof(WAVEFORMATEX));

    //初始化定时器
    LARGE_INTEGER FirstTime;
    HANDLE hTimerWakeUp = CreateWaitableTimer(NULL, FALSE, NULL);
    FirstTime.QuadPart = -dlg->BuffDuration_millisec * REFTIMES_PER_MILLISEC/2;

    //获取音频捕获(录音)客户端
    hr = dlg->pAudioClient->GetService( IID_IAudioCaptureClient, (void**)(&(dlg->pCaptureClient)));

    //启动捕获(录音)
    hr = dlg->pAudioClient->Start();
    if (FAILED(hr)) {dlg->SetDlgItemText(IDC_EDIT3,"初始化设备失败code:3!");dlg->ErrorProcess();return 0;}//错误处理

    //配置定时器,第一次信号定时0.05s,时间间隔0.05s,即每隔0.05把数据读出来并发送
    SetWaitableTimer(hTimerWakeUp,&FirstTime,(dlg->BuffDuration_millisec *5) /10,NULL, NULL, FALSE);

    //输出重定向到txt文件的方法,在命令行启动就可以看到调试信息,请参考https://blog.csdn.net/benkaoya/article/details/5935626
    //printf("/-------------------------------------------------------------------------------/\n");

    //主循环共有两层,这是因为数据缓冲区共有两个,
    //一个是音频客户端内部硬件的缓冲区(比较小,简称小buff,即下面pData指针),另一个是我们之前在初始化客户端申请的缓冲区(比较大,简称大buff)
    //小buff我在自己计算机上测试48kHz的情况下,每次只能读到480帧,可是我申请的大buff有0.1s,能装4800帧
    //所以需要多一层循环,把0.05s的数据以每次480的数量全部读出来后,再发送出去。
    //为什么不直接把每次480的小buff直接发出去,而多弄一个大Buff?因为这样的话会发送太频繁,会造成网络资源浪费
    while (bThreadisRunning == TRUE)
    {
        Counter =sizeof(WAVEFORMATEXTENSIBLE)>>2;    //计数器从置,从第41个字节开始写音频数据

        //线程休眠,一直录音,这里设置的时间要比BuffDuration_millisec短,因为后面复制数据也是需要时间的
        //官方给的例程是大buff时间的一半。
        //Sleep((dlg->BuffDuration_millisec * 5) / 10);
        WaitForSingleObject(hTimerWakeUp,INFINITE);

        hr = dlg->pCaptureClient->GetNextPacketSize(&packetLength);    //获取包长度,以帧为单位,这里获取的是小buff的数据包长度

        //输出重定向到txt文件的方法,在命令行启动就可以看到调试信息,请参考https://blog.csdn.net/benkaoya/article/details/5935626
        //printf("\nCounter:numFA: ");

        while (packetLength != 0)
        {
            //获取小buff的地址,同时获取帧数量,这个帧数量和上面的包长度数值是一样的
            hr = dlg->pCaptureClient->GetBuffer(&pData,&numFramesAvailable,&flags, NULL, NULL);

            //输出重定向到文件的方法,可以看到调试信息,请参考https://blog.csdn.net/benkaoya/article/details/5935626
            //printf("%04d:%d; ",Counter,numFramesAvailable);

            //保存音频数据
            memcpy(&(DataToSend[Counter]),pData,numFramesAvailable*dlg->pwfx->nBlockAlign);

            //计数总共读了多少帧
            Counter += numFramesAvailable;

            //释放小buff,并读取下一个数据包长度
            hr = dlg->pCaptureClient->ReleaseBuffer(numFramesAvailable);
            hr = dlg->pCaptureClient->GetNextPacketSize(&packetLength);

        }

        //这里跳出循环,如果是48kHz采样率的话,此时的Counter就应该为0.05s的帧数量,即2400帧
        //因为复制数据、发送数据都是需要时间的,实际不一定每次都刚好是2400帧,可能会多一点点或者少一点点
        //如果有数据,就立即socket发去客户端
        if(Counter > (sizeof(WAVEFORMATEXTENSIBLE)>>2))
            sendto(dlg->m_socket,(char*)DataToSend,Counter<<2,0,(SOCKADDR *)(&(dlg->m_ClientAddr)),sizeof(SOCKADDR));
    }

    //停止环回录音
    hr = dlg->pAudioClient->Stop();  

    CoTaskMemFree(dlg->pwfx);
    SAFE_RELEASE(dlg->pAudioClient)
    SAFE_RELEASE(dlg->pCaptureClient)

    return 0;
}

五、最小化到系统托盘

  这一块内容就不说了,作者也是直接参考别人的代码稍作修改实现的,可以参考:https://www.cnblogs.com/suthui/p/3492962.html

六、写在最后

  本作品发送的音频数据都是未经压缩的PCM原始数据,这种方法的好处就是发送端接收端没有压缩和解码的过程,效率高,实时性好。缺点就是传输的数据量大,占用网络带宽,以作者的48kHz、2通道、16位深的音频数据为例,网络带宽占用195KB/s。以下是发送端运行截图及windows资源管理器网络速度截图。

   

原文地址:https://www.cnblogs.com/qzrzq1/p/9074132.html

时间: 2024-10-14 00:45:28

基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二)的相关文章

基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(三)

作品已经完成,先上源码: https://files.cnblogs.com/files/qzrzq1/WIFISpeaker.zip 全文包含三篇,这是第三篇,主要讲述接收端程序的原理和过程. 第一篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(一) 第二篇:基于Orangpi Zero和Linux ALSA实现WIFI无线音箱(二) 以下是正文: 在进行接收端程序开发前,首先要了解Orangpi Zero的声音设备. Orangpi可以通过ALSA(The Adv

基于Linux ALSA音频驱动的wav文件解析及播放程序 2012

本设计思路:先打开一个普通wav音频文件,从定义的文件头前面的44个字节中,取出文件头的定义消息,置于一个文件头的结构体中.然后打开alsa音频驱动,从文件头结构体取出采样精度,声道数,采样频率三个重要参数,利用alsa音频驱动的API设置好参数,最后打开wav文件,定位到数据区,把音频数据依次写到音频驱动中去,开始播放,当写入完成后,退出写入的循环. 注意:本设计需要alsa的libasound-dev的库,编译链接时需要连接 —lasound. #include<stdio.h>#incl

Linux ALSA 音频系统:物理链路篇

1. Overview 硬件平台及软件版本: Kernel - 3.4.5 SoC - Samsung exynos CODEC - WM8994 Machine - goni_wm8994 Userspace - tinyalsa Linux ALSA 音频系统架构大致如下: +--------+ +--------+ +--------+ |tinyplay| |tinycap | |tinymix | +--------+ +--------+ +--------+ | ^ ^ V | V

基于IP-SAN的存储Linux挂载

1.卸载原有存储 umount /home/weishaostore/ 2.查看iSCSI session信息 iscsiadm -m session 3.设置InitiatorName vim /etc/iscsi/initiatorname.iscsi InitiatorName=iqn.2014-05.com.ruijie.whistle:whistle-100.e7839d5100 InitiatorName=iqn.2014-05.com.ruijie.whistle:whistle-

用shell脚本实现linux系统上wifi模式(STA和soft AP)的转换

转载请注明出处:http://blog.csdn.net/hellomxj1/ 功能:在linux系统上实现wifi STA与AP功能的转换 实现成果:1.添加wifi密码账户add_wifi_account: 2.wifi两种模式启动的脚本wifi_start: 3.帮助信息README_WIFI_START: 具体实现过程如下: 添加wifi密码账户add_wifi_account 1 #!/bin/sh 2 3 echo "Add Wifi Account ..." 4 5 if

基于Vmare的虚拟Linux集群搭建-lvs+keepalived

基于Vmare的虚拟Linux集群搭建-lvs+keepalived 本文通过keepalived实现lvs服务器的的双机热备和真实服务器之间的负载均衡.这方面的blog挺多,但是每个人搭建集群的环境不同,遇到的问题也不同,本文简述配置的一些过程以及配置过程中遇到问题时的解决方案. 准本工作 1. 创建Linux虚拟机 本文是基于Vmare虚拟环境搭建集群的,所以首先在Vmare中创建一些虚拟机(安装一个Linux系统,其他的复制第一次安装的即可),本文创建4台虚拟机.网卡模式设置成桥接方式,这

Kali Linux下破解WIFI密码挂载usb无线网卡的方法

Kali Linux下破解WIFI密码挂载usb无线网卡的方法 时间:2014-10-12    来源:服务器之家    投稿:root 首先我要说的是,wifi密码的破解不是想象中的那么容易,目前还没有一个可以破解任意类型wifi密码的工具或者方案存在.虽然你可以在网络上找到大量的教程,教程中有大量的成功案例,但是在实际场景中成功率是极低的. 其次,免费的午餐并不都那么好吃,希望读者在自己的内网上进行测试,毕竟学习技术是第一位的.免费午餐有可能会增加你遭受钓鱼攻击的风险. 本系列文章会针对不同

基于wifi无线PLC远程控制实现io开关量信号远程采集传输技术

深圳市综科智控科技开发有限公司是一家专注于生产与研发工业智能自动化设备及软件系统.工业物联网设备及软件系统的高新技术企业.公司致力于为客户提供从前端数据采集.传感器接入.IO控制.通信组网到云端联网.人机交互的一整套系统及方案,帮助客户实现其自动化设备及物联网设备的本地或者远程分布式控制与管理.本文档以综科智控无线IO模块为例,详细讲解基于wifi无线PLC远程控制实现io开关量信号远程采集传输的技术,无线PLC广泛应用于工业控制场合,物联网场合,智能家居场合,安防场以及不方便布线的场合,实测性

Kali Linux Web 渗透测试— 第十二课-websploit

Kali Linux Web 渗透测试— 第十二课-websploit 文/玄魂 目录 Kali Linux Web 渗透测试— 第十二课-websploit............................................... 1 Websploit 简介........................................................................................... 2 主要功能...........