C++ Socket服务端和客户端多线程通信

最近项目中需要用到设备通信,特在VS2013中利用Windows自带的API进行服务端和客户端通信测试。

由于实际应用需要服务器和客户端不间断实时全双工通信,特利用多线程的方式简单实现socket通信。

socket的通信机制,我不做过多的阐述,相关的参考文档较多,接下来从code的角度实现多线程通信。

服务端程序如下:

-------------------------------

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"

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

char recvBuf[100], sendBuf[100];
SOCKET sockConn;

DWORD WINAPI sendFun(LPVOID IpParameter);
DWORD WINAPI receiveFun(LPVOID lpParamter);

/*
进行全双工通信,采用多线程的方式!

时间:2018/07/17
*/

int main()
{
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)//调用函数,指明使用的socket版本!wsaData返回版本信息
    {
        std::cout << "初始化失败!" << std::endl;
        std::cin.get();
        return 0;
    }
    //创建用于监听的套接字,即服务器的套接字
    /*
    struct sockaddr_in
    {
    sa_family_t    sin_family;//地址族
    uint16_t            sin_port;//16位TCP/UDP端口号
    struct in_addr        sin_addr;//32位IP地址
    char                    sin_zero[8];//不使用
    }
    struct in_addr
    {
    In_addr_t        s_addr;//32位IPv4地址
    }
    ------------------------------------------------------------------
    htons()将端口号由主机字节序转换为网络字节序的整数值
    inet_addr()将一个IP字符串转化为一个网络字节序的整数值
    inte_ntoa()将一个sin_addr结构体输入为IP字符串
    htonl()作用与htons()一样,不过针对32位
    ------------------------------------------------------------------
    */
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);//套接字
    if (sockSrv == -1)
    {
        std::cout << "初始化socket失败!" << std::endl;
        std::cin.get();
        return 0;
    }
    SOCKADDR_IN addrSrv;//建立Internet环境下套接字地址形式
    addrSrv.sin_family = AF_INET;//地址族形式
    addrSrv.sin_port = htons(8080);//1024以上的端口号
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//IPv4地址(不确定地址,也表示所有地址)
    int retVal = bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN));//将套接字进行地址和端口的绑定
    if (retVal == SOCKET_ERROR)
    {
        std::cout << "绑定失败! " << WSAGetLastError() << std::endl;
        std::cin.get();
        return 0;
    }
    /*
    listen()将主动连接套接字口变成被动连接套接字,使得一个进程可以接受其它进程的请求,从而变成一个服务器进程
    listen()函数一般在bind()函数后,accept()函数之前
    int listen(int sockfd,int backlog);
    backlog:等待连接队列的最大长度(某一时刻同时允许最多有backlog个客户端和服务端进行连接)
    */
    if (listen(sockSrv, 10) == SOCKET_ERROR)
    {
        std::cout << "监听失败! " << WSAGetLastError() << std::endl;
        std::cin.get();
        return 0;
    }

    /*
    accept(sockfd,sockaddr *addr,socklen_t *len);
    accept默认为阻塞进程,直到有一个客户连接建立后返回,其返回一个新可用的套接字(连接套接字)
    在调用listen函数后,一个套接字会从主动连接的套接字变身为一个监听套接字
    sockfd为监听套接字,accept返回一个连接套接字,表示一个网络已经存在点点连接
    ----------------------------------------------------------------------------
    send(socket s,const char *buf,int len,int flags);
    无论是客户端还是服务端都用send函数来向TCP连接的另一端发送数据,recv()函数也是一样的
    buf为存放应用程序发送数据的缓冲区
    */
    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);
    while (1)
    {
        //等待客户请求到来
        sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);//sockConn为连接套接字
        if (sockConn == SOCKET_ERROR)
        {
            std::cout << "等待请求失败!" << WSAGetLastError() << std::endl;
            break;
        }

        //发送数据
        char sendbuf[] = "通信已建立!";
        int iSend = send(sockConn, sendbuf, sizeof(sendbuf), 0);
        if (iSend == SOCKET_ERROR)
        {
            std::cout << "发送失败!" << std::endl;
            break;
        }
        recv(sockConn, recvBuf, sizeof(recvBuf), 0);//接收建立起连接的数据
        std::cout << recvBuf << " 客户端的IP是:" << inet_ntoa(addrClient.sin_addr) << std::endl;
        HANDLE hSendThread = CreateThread(NULL, 0, sendFun, NULL, 0, NULL);//在线程中进行数据的发送
        HANDLE hReceiveThread = CreateThread(NULL, 0, receiveFun, NULL, 0, NULL);//在线程中进行数据的接收
        CloseHandle(hSendThread);
        CloseHandle(hReceiveThread);
    }

    closesocket(sockSrv);
    WSACleanup();//调用此函数解除与socket库的绑定,并释放socket库所占用的系统资源
    system("pause");
    return 0;
}

DWORD WINAPI sendFun(LPVOID IpParameter)
{

    while (true)
    {
            memset(sendBuf, NULL, sizeof(sendBuf));
            std::cin >> sendBuf;
            std::cin.get();
            send(sockConn, sendBuf, sizeof(sendBuf), 0);
    }
    closesocket(sockConn);
    return 0;
}

DWORD WINAPI receiveFun(LPVOID IpParameter)
{

    while (true)
    {
        memset(recvBuf, NULL, sizeof(recvBuf));
        //接收数据
        int flag = (int)recv(sockConn, recvBuf, sizeof(recvBuf), 0);
        if (flag>0 )//有数据才响应
        {
            std::cout << "接收到的数据为:" << recvBuf << std::endl;
        }
        else
        {
            
        }
    }
    closesocket(sockConn);
    return 0;
}

-------------------------------

客户端程序如下:

------------------------------

#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include "winsock2.h"

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

SOCKET sockClient;
char buffs[100], buff[100];

DWORD WINAPI sendFun(LPVOID lpParameter);
DWORD WINAPI receiveFun(LPVOID lpParameter);

int main()
{
    WSADATA wsaData;
    char buff[1024];
    memset(buff, 0, sizeof(buff));

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        std::cout << "初始化winsock失败!" << std::endl;
        std::cin.get();
        return 0;
    }

    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(8080);//端口号
    addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.74.119");//IP地址

    //创建套接字
    sockClient = socket(AF_INET, SOCK_STREAM, 0);
    if (SOCKET_ERROR == sockClient)
    {
        std::cout << "Socket() error: " << WSAGetLastError() << std::endl;
        std::cin.get();
        return 0;
    }

    //向服务器发出连接请求
    if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET)
    {
        std::cout << "连接失败!" << WSAGetLastError() << std::endl;
        std::cin.get();
        return 0;
    }
    else
    {
        //接收数据
        recv(sockClient, buff, sizeof(buff), 0);
        std::cout << buff << " 服务端的IP是:" << inet_ntoa(addrSrv.sin_addr) << std::endl;
    }

    //发送数据
    char sendbuffs[] = "通信已建立!";
    send(sockClient, sendbuffs, sizeof(buffs), 0);

    //在线程中进行数据的收发
    while (true)
    {
        HANDLE hSendThread = CreateThread(NULL, 0, sendFun, NULL, 0, NULL);//在线程中进行数据的发送
        HANDLE hReceiveThead = CreateThread(NULL, 0, receiveFun, NULL,0, NULL);//在线程中进行数据的接收
        CloseHandle(hSendThread);
        CloseHandle(hReceiveThead);
    }
    //关闭套接字
    closesocket(sockClient);
    WSACleanup();//释放初始化Ws2_32.dll所分配的资源
    system("pause");
    std::cin.get();
    return 0;
}

DWORD WINAPI sendFun(LPVOID lpParameter)
{
    while (true)
    {
            memset(buffs, NULL, sizeof(buffs));
            std::cin >> buffs;
            send(sockClient, buffs, sizeof(buffs), 0);
    }
    closesocket(sockClient);
    return 0;
}

DWORD WINAPI receiveFun(LPVOID lpParameter)
{

    while (true)
    {
        memset(buff, NULL, sizeof(buff));
        int flag = (int)recv(sockClient, buff, sizeof(buff), 0);
        if (flag>0)
        {
            std::cout << "接收的数据为:" << buff << std::endl;
        }
        else
        {
            
        }
    }
    closesocket(sockClient);
    return 0;
}

------------------------------

程序测试效果:

特别地,我是在一个电脑上进行的不同程序间通信,所以服务端和客户端IP地址一致,若要在不同电脑上运行,可设置IP地址,但是要保持端口号一致。

另外,在关闭界面时,最好先关闭客户端的界面,否则可能会有卡顿的现象。。。

猜你喜欢

转载自blog.csdn.net/qq_33810188/article/details/81082273