通过使用WinSock实现简单的服务器与客户端。

Windows套接字简介


Winsock是一个编程接口和支持程序,用于处理Windows 操作系统中 Internet应用程序的输入/输出请求。它被称为Winsock,因为它适用于Berkeley UNIX套接字接口的Windows 。套接字是用于在同一计算机内或网络上的两个程序进程之间连接和交换数据的特定约定。
整理自微软官方文档:Windows Sockets 2

引入相关头文件和库文件


#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>

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

包含Winsock2.h来使用Winsock API。该头文件包含了大部分Winsock的结构和定义。Ws2tcpip.h提供检索和修改本地计算机上传输控制协议/Internet协议(TCP/IP)传输的配置设的API。
注意:如果要使用ws2tcpip.h提供的API则包含winsock2.h的语句必须在ws2tcpip.h的前面->来自微软的官方文档,以免发生重定义错误(重复包含同一个头文件所导致的)

WSA初始化


在使用winsock的时候必须调用WSAStartup,以初始化WS2_32.DLL

// 初始化 Winsock
    errcode = WSAStartup(MAKEWORD(2, 2), &wsadata_c);
    if (errcode != 0)
    {
        cout << "初始化失败,错误代码: " << errcode << endl;
        return 1;
    }

客户端/服务端


客户端:客户端需要创建一个客户端套接字,用来与服务端套接字进行会话,在创建好套接字后,调用connect()函数来连接到服务端。建立连接成功后即可调用send()和recv()来发送和接收数据
服务端:服务端需要创建一个监听套接字用于监听端口的连接请求,在接受来自客户端请求后创建一个套接字与发送该请求的客户端套接字进行会话(在多线程的服务端中,每接收到一个客户端的请求,服务端便创建一个套接字与该请求的客户端套接字配对建立连接),建立连接后即可调用send()和recv()来发送和接收数据
注意:在使用完套接字之后一定要调用closesocket()和WSACleanup()释放资源,否则可能会报异常

客户端代码


#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>

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

using namespace std;

int main()
{
    WSADATA wsadata_c;
    SOCKET socket_c;
    int errcode;

    // 初始化 Winsock
    errcode = WSAStartup(MAKEWORD(2, 2), &wsadata_c);
    if (errcode != 0)
    {
        cout << "初始化失败,错误代码: " << errcode << endl;
        return 1;
    }

    // 创建用于连接到服务器的套接字
    socket_c = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socket_c == INVALID_SOCKET)
    {
        cout << "套接字创建失败,错误代码:" << WSAGetLastError() << endl;
        WSACleanup();
        return 1;
    }
    cout << "初始化完成......" << endl;

    //连接到服务器
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.0.102", &addr.sin_addr);
    addr.sin_port = htons(2233);

    errcode = connect(socket_c, (sockaddr*)& addr, sizeof(addr));
    if (errcode == SOCKET_ERROR)
    {
        cout << "连接失败,错误代码:" << errcode << endl;
        closesocket(socket_c);
        WSACleanup();
        return 1;
    }

    char* sendbuf = new char[1024]{};
    while (true)
    {
        cout << "输入需要发送的数据:";
        cin >> sendbuf;
        if (!strcmp(sendbuf, "over"))
        {
            break;
        }
        errcode = send(socket_c, sendbuf, strlen(sendbuf), 0);
        if (errcode == SOCKET_ERROR)
        {
            cout << "数据发送失败,错误代码:" << WSAGetLastError() << endl;
            return false;
        }
    }

    delete[] sendbuf;
    closesocket(socket_c);
    WSACleanup();
    return 0;
}

服务端代码



#include <iostream>
#include <winsock2.h>
#include <WS2tcpip.h>

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

using namespace std;

int main()
{
    WSADATA wsadata_s;
    SOCKET slisten;
    SOCKET socket_c;
    int errcode;

    // 初始化 Winsock
    errcode = WSAStartup(MAKEWORD(2, 2), &wsadata_s);
    if (errcode != 0)
    {
        cout << "初始化失败,错误代码: " << errcode << endl;
        return 1;
    }

    //创建用于监听的套接字
    slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (slisten == INVALID_SOCKET)
    {
        cout << "套接字创建失败,错误代码:" << WSAGetLastError() << endl;
        WSACleanup();
        return 1;
    }

    //绑定本机的端口与IP地址
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(2233);
    addr.sin_addr.S_un.S_addr = INADDR_ANY;

    errcode = bind(slisten, (sockaddr*)& addr, sizeof(addr));
    if (errcode == SOCKET_ERROR)
    {
        cout << "绑定端口或IP地址失败,错误代码:" << WSAGetLastError() << endl;
        closesocket(slisten);
        WSACleanup();
        return 1;
    }
    cout << "初始化完成......" << endl;

    //开始监听端口
    cout << "监听中......" << endl;
    if (listen(slisten, SOMAXCONN) == SOCKET_ERROR)
    {
        cout << "监听失败,错误代码:" << WSAGetLastError();
        closesocket(slisten);
        WSACleanup();
        return 1;
    }
    sockaddr_in user{};
    int len = sizeof(user);
    socket_c = accept(slisten, (sockaddr*)& user, &len);
    if (socket_c == INVALID_SOCKET)
    {
        cout << "接受连接失败,错误代码:" << WSAGetLastError();
        closesocket(slisten);
        WSACleanup();
        return 1;
    }

    wchar_t ip[32] = L"\n";
    InetNtopW(AF_INET, &user.sin_addr, ip, 32);
    wcout << "接受到一个连接:" << ip << endl;

    char* recvbuf = new char[1024]{};
    int recv_size = 0;

    while (true)
    {
        recv_size = recv(socket_c, recvbuf, sizeof(recvbuf), 0);
        if (recv_size > 0)
        {
            cout << "接收到数据字节数:" << recv_size << endl;
            cout << "接收到的数据:" << recvbuf << endl;
            if (!strcmp(recvbuf, "over"))
            {
                break;
            }
            memset(recvbuf, 0, sizeof(recvbuf));
        }
        else if (recv_size == 0)
        {
            cout << "连接中断" << endl;
            break;
        }
        else
        {
            cout << "数据接收失败,错误代码:" << WSAGetLastError() << endl;
            break;
        }
    }

    delete[] recvbuf;
    closesocket(slisten);
    closesocket(socket_c);
    WSACleanup();
    return 0;
}

总结


在初始化部分无论是客户端还是服务端都是一样的,但是区别在于
服务端:
1.需要创建一个套接字用于监听端口,接收来自客户端的请求
2.在接受请求之后要新创建一个套接字用于和客户端的交互
3.服务端应是被动的,只有在客户端发起请求的时候才做事,在无请求时便一直监听端口
客户端:
1.需要创建一个套接字用于连接到服务器
2.在连接到服务端后便可进行交互
3.客户端是应是主动的,主动连接服务器,主动断开于服务器的连接

Last modification:April 15th, 2020 at 09:13 am