TCP例子


服务端

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib, "Ws2_32.lib")

/// <summary>
/// 初始化套接字库
/// </summary>
void initialization() {
    WORD w_req = MAKEWORD(2, 2);//版本号
    WSADATA wsadata;
    int err;
    err = WSAStartup(w_req, &wsadata);
    if (err != 0) {
        cout << "初始化套接字库失败!" << endl;
    }
    else {
        cout << "初始化套接字库成功!" << endl;
    }
    //检测版本号
    if (LOBYTE(wsadata.wVersion) != 2  HIBYTE(wsadata.wHighVersion) != 2) {
        cout << "套接字库版本号不符!" << endl;
        WSACleanup();
    }
    else {
        cout << "套接字库版本正确!" << endl;
    }
    //填充服务端地址信息
}

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
    SOCKET sClient = (SOCKET)lpParameter;

    while (true)
    {
        // 此处PACKAGE为自定义数据结构
        PACKAGE pkgForm;
        // 接收数据并对数据反序列化
        int nRet = recv(sClient, (char*)&pkgForm, sizeof(PACKAGE), 0);
        if (nRet == 0  nRet == SOCKET_ERROR)
        {
            if (WSAGetLastError() == WSAECONNRESET)
            {
                printf("远程主机强迫关闭了一个现有的连接\r\n");
                // 当客户端断开连接时,关闭socket并退出线程
              closesocket(sClient);
                return 0;
            }
            else
            {
                // 其他问题则跳过本次循环
                continue;
            }
        }

        // TODO: 进行包的数据分发由各自函数解析函数
        // TODO: 需要将socket存入一个链表或动态数组中进行保存,以便消息广播等作用。在多线程中访问全局数据需要做线程同步

    // 缓解cpu压力
        Sleep(1);
    }

    return 0;
}

int main()
{
    initialization();
    //1)创建socket
    SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sockServer == INVALID_SOCKET)
    {
        cout << "socket 创建失败 \r\n" << endl;
        return 0;
    }

    //绑定端口 127.0.0.1 回环地址   
    sockaddr_in siServer;
    siServer.sin_family = AF_INET;
    siServer.sin_port = htons(0x9527); // 服务器监听端口
    siServer.sin_addr.S_un.S_addr = inet_addr("0.0.0.0"); // 服务器地址,也可以是0.0.0.0
    int nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));
    if (nRet == SOCKET_ERROR)
    {
        cout << "绑定地址失败 \r\n" << endl;
        return 0;
    }
    else
    {
        cout << "服务器启动完毕 \r\n" << endl;
    }

    if (listen(sockServer, SOMAXCONN) == SOCKET_ERROR)
    {
        cout << "端口监听失败 \r\n" << endl;
    }
    cout << "服务端正在监听连接,请稍候...." << endl;

    // 循环读取消息
    while (true)
    {
        //接受连接请求
        sockaddr_in siAccept;
        int nLen = sizeof(SOCKADDR_IN);
        // 当有客户连接是阻塞取消
        SOCKET sClient = ::accept(sockServer, (SOCKADDR*)&siAccept, &nLen);
        if (sClient == SOCKET_ERROR) {
            cout << "连接失败!" << endl;
            WSACleanup();
            return 0;
        }
        cout << "连接建立[" << inet_ntoa(siAccept.sin_addr) << ":" << ntohs(siAccept.sin_port) << "],准备接受数据" << endl;
    // 连接建立成功则启动一个线程用来和客户端通讯
        // 将客户端的socket传入线程中用来通讯
        HANDLE hThread = CreateThread(NULL, 0, ThreadProc, (PVOID)sClient, 0, NULL);
        if (hThread == NULL)
        {
            cout << "工作线程建立失败" << endl;
            closesocket(sClient);
            continue;
        }
        else
        {
            cout << "工作线程建立成功" << endl;
        }
        // 关闭线程句柄,但是线程仍在运行
    CloseHandle(hThread);
        Sleep(1);
    }

    //关闭套接字
    closesocket(sockServer);
    //释放DLL资源
    WSACleanup();
}

客户端

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib, "Ws2_32.lib")

// 自定义用户消息,用来多线程中同步修改UI数据
#define WM_UPDATE_UI WM_USER + 0x1

/// <summary>
/// 初始化套接字库
/// </summary>
void initialization() {
    WORD w_req = MAKEWORD(2, 2);//版本号
    WSADATA wsadata;
    int err;
    err = WSAStartup(w_req, &wsadata);
    if (err != 0) {
        cout << "初始化套接字库失败!" << endl;
    }
    else {
        cout << "初始化套接字库成功!" << endl;
    }
    //检测版本号
    if (LOBYTE(wsadata.wVersion) != 2  HIBYTE(wsadata.wHighVersion) != 2) {
        cout << "套接字库版本号不符!" << endl;
        WSACleanup();
    }
    else {
        cout << "套接字库版本正确!" << endl;
    }
}

// 多线程处理服务器返回消息
DWORD WINAPI RecvSockMessage(LPVOID lpParameter)
{
    // 类中调用多线程的时候将类传入函数
    CClientDlg* pThis = (CClientDlg*)lpParameter;

    while (true)
    {
        // 反序列化
        // PACKAGE同服务端
        PACKAGE pkgForm;
        // 接收服务端返回消息,若无返回则会阻塞
        int nRet = recv(pThis->m_socketClient, (char*)&pkgForm, sizeof(PACKAGE), 0);
        if (nRet == 0  nRet == SOCKET_ERROR)
        {
            continue;
        }

        // 线程安全的将每个封包传入类中的封包队列中
        pThis->m_clsLock.Lock();
        pThis->m_lstPakToHandle.push_back(pkgForm);
        pThis->m_clsLock.Unlock();
        // 发送用户自定义消息进行UI更新以及数据包处理
        pThis->PostMessage(WM_UPDATE_UI);
        Sleep(1);
    }
    return 0;
}

// 用户自定义消息的处理函数
afx_msg LRESULT CClientDlg::OnUpdateUI(WPARAM wParam, LPARAM lParam)
{
    // 线程安全获取封包
    m_clsLock.Lock();
    auto pkgForm = m_lstPakToHandle.front();
    // 获取一个包将队列中的包去除一个
    m_lstPakToHandle.pop_front();
    m_clsLock.Unlock();

    // TODO 数据包分发函数进行处理

    return 0;
}

// 在不通环境下将此段代码加入各自的初始化函数中
void InitSocket()
{
    initialization();
  // 初始服务器数据
    m_serverInfo.m_wPort = htons(0x9527); // 服务器端口
    m_serverInfo.m_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服务器IP

    // 服务器连接结构体
    SOCKADDR_IN sServerAddr;
    sServerAddr.sin_family = AF_INET;
    sServerAddr.sin_addr = m_serverInfo.m_addr;
    sServerAddr.sin_port = m_serverInfo.m_wPort;

    // 创建socket
    // m_socketClient 为全局或类中成员,类型为SOCKET
    m_socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_socketClient == INVALID_SOCKET)
    {
        AfxMessageBox("程序异常,请检查软件权限");
        exit(0);
    }

    // 连接服务器
    if (connect(m_socketClient, (SOCKADDR*)&sServerAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
        AfxMessageBox("服务器连接失败");
        WSACleanup();
        exit(0);
    }

    // 启动收数据线程
    m_hThread = CreateThread(NULL, 0, RecvSockMessage, this, 0, NULL);
    if (m_hThread == NULL)
    {   
        AfxMessageBox("程序异常 \r\n");
        exit(0);
    }

    // 当客户端和服务器连接成功后,可以使用如下代码获取客户端的本地端口
    sockaddr_in si;
    int nLen = sizeof(si);
    getsockname(m_socketClient, (sockaddr*)&si, &nLen);
    m_localUserInfo.m_wPort = si.sin_port;

    // TODO: 发送自己的数据包,比如登录包
}

UDP例子


服务端

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib, "Ws2_32.lib")

/// <summary>
/// 初始化套接字库
/// </summary>
void initialization() {
    WORD w_req = MAKEWORD(2, 2);//版本号
    WSADATA wsadata;
    int err;
    err = WSAStartup(w_req, &wsadata);
    if (err != 0) {
        cout << "初始化套接字库失败!" << endl;
    }
    else {
        cout << "初始化套接字库成功!" << endl;
    }
    //检测版本号
    if (LOBYTE(wsadata.wVersion) != 2  HIBYTE(wsadata.wHighVersion) != 2) {
        cout << "套接字库版本号不符!" << endl;
        WSACleanup();
    }
    else {
        cout << "套接字库版本正确!" << endl;
    }
    //填充服务端地址信息
}

int main()
{
    initialization();
    // 创建socket
    SOCKET sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); // udp协议
    if (sockServer == INVALID_SOCKET)
    {
        cout << "socket 创建失败 \r\n");
        return 0;
    }

    //绑定服务器IP和端口  
    sockaddr_in siServer;
    siServer.sin_family = AF_INET;
    siServer.sin_port = htons(0x9527);
    siServer.sin_addr.S_un.S_addr = inet_addr("0.0.0.0");
    // 
    int nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));
    if (nRet == SOCKET_ERROR)
    {
        printf("绑定地址失败 \r\n");
        return 0;
    }
    else
    {
        printf("服务器启动完毕 \r\n");
    }

    // 维护一个在线列表
    list<USERINFO> lstOnlineClient;

    // 循环读取消息
    while (true)
    {
        sockaddr_in siClient = { 0 };
        int nLen = sizeof(siClient);
        // 反序列化
        PACKAGE pkgForm;
        int nRet = recvfrom(sockServer, (char*)&pkgForm, sizeof(PACKAGE), 0, (sockaddr*)&siClient, &nLen);
        if (nRet == 0  nRet == SOCKET_ERROR)
        {
            if (WSAGetLastError() == WSAECONNRESET)
            {
                printf("远程主机强迫关闭了一个现有的连接\r\n");
                continue;
            }
            else
            {
                continue;
            }
        }

        // TODO: 数据处理
    }

    // 释放资源
    closesocket(sockServer);
    WSACleanup();
}

客户端

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib, "Ws2_32.lib")

#define WM_UPDATE_UI WM_USER + 0x1

/// <summary>
/// 初始化套接字库
/// </summary>
void initialization() {
    WORD w_req = MAKEWORD(2, 2);//版本号
    WSADATA wsadata;
    int err;
    err = WSAStartup(w_req, &wsadata);
    if (err != 0) {
        cout << "初始化套接字库失败!" << endl;
    }
    else {
        cout << "初始化套接字库成功!" << endl;
    }
    //检测版本号
    if (LOBYTE(wsadata.wVersion) != 2  HIBYTE(wsadata.wHighVersion) != 2) {
        cout << "套接字库版本号不符!" << endl;
        WSACleanup();
    }
    else {
        cout << "套接字库版本正确!" << endl;
    }
    //填充服务端地址信息
}

// 多线程处理服务器返回消息
DWORD WINAPI RecvSockMessage(LPVOID lpParameter)
{
    // 类中调用多线程的时候将类传入函数
    CClientDlg* pThis = (CClientDlg*)lpParameter;

    while (true)
    {
        sockaddr_in siClient = { 0 };
        int nLen = sizeof(siClient);
        // 反序列化
        // PACKAGE同服务端
        PACKAGE pkgForm;
        // 接收服务端返回消息,若无返回则会阻塞
        int nRet = recvfrom(pThis->m_socketClient, (char*)&pkgForm, sizeof(PACKAGE), 0, (sockaddr*)&siClient, &nLen);
        if (nRet == 0  nRet == SOCKET_ERROR)
        {
            continue;
        }

        // 线程安全的将每个封包传入类中的封包队列中
        pThis->m_clsLock.Lock();
        pThis->m_lstPakToHandle.push_back(pkgForm);
        pThis->m_clsLock.Unlock();
        // 发送用户自定义消息进行UI更新以及数据包处理
        pThis->PostMessage(WM_UPDATE_UI);
        Sleep(1);
    }
    return 0;
}

// 用户自定义消息的处理函数
afx_msg LRESULT CClientDlg::OnUpdateUI(WPARAM wParam, LPARAM lParam)
{
    // 线程安全获取封包
    m_clsLock.Lock();
    auto pkgForm = m_lstPakToHandle.front();
    // 获取一个包将队列中的包去除一个
    m_lstPakToHandle.pop_front();
    m_clsLock.Unlock();

    // TODO 数据包分发函数进行处理

    return 0;
}

// 在不通环境下将此段代码加入各自的初始化函数中
void InitSocket()
{
    initialization();
  // 初始服务器数据
    m_serverInfo.m_wPort = htons(0x9527); // 服务器端口
    m_serverInfo.m_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 服务器IP

    // 创建socket
    // m_socketClient 为全局或类中成员,类型为SOCKET
    m_socketClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (m_socketClient == INVALID_SOCKET)
    {
        AfxMessageBox("程序异常,请检查软件权限");
        exit(0);
    }

    // 启动收数据线程
    m_hThread = CreateThread(NULL, 0, RecvSockMessage, this, 0, NULL);
    if (m_hThread == NULL)
    {   
        AfxMessageBox("程序异常 \r\n");
        exit(0);
    }

    // 当客户端发送一次封包后,可以使用如下代码获取客户端的本地端口
    sockaddr_in si;
    int nLen = sizeof(si);
    getsockname(m_socketClient, (sockaddr*)&si, &nLen);
    m_localUserInfo.m_wPort = si.sin_port;

    // TODO: 发送自己的数据包,比如登录包
}