C++Socket套接字编程使用winsock2.h

套接字有两种概念:服务器和客户端
下面是用于创建流式 (steam)TCP/IP 服务器和客户端的常规模型。

服务器

  1. 初始化 Winsock。
  2. 创建套接字。
  3. 绑定套接字。
  4. 在套接字上监听客户端。
  5. 接受来自客户端的连接。
  6. 接收和发送数据。
  7. 断开连接

客户端

  1. 初始化 Winsock。
  2. 创建套接字。
  3. 连接到该服务器。
  4. 发送和接收数据。
  5. 断开连接

很明显, 1, 2, 还有 断开连接 步骤完全相同

0.检测环境

用vs, 调试下面代码, 判断环境是否准备充分

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

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

int main() {
    printf("Hello World");
    return 0;
}

1. 初始化

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

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

int main() {
    WSADATA wsaData;
    int iResult;

    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }

    printf("Hello World");
    return 0;
}

2. 服务器端创建套接字

首先为服务器创建套接字, 这样接下来的客户端就可以连接调试

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

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


#define DEFAULT_PORT "9501" // 服务器监听的端口

int main() {
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
    struct addrinfo* 
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // 创建socket对象
    SOCKET ListenSocket = INVALID_SOCKET;
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end


    return 0;
}

每个步骤尽量都要调试一番, 已检查是否错误

3.绑定套接字

若要使服务器接受客户端连接,它必须绑定到服务器的网络地址。

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

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


#define DEFAULT_PORT "9501" // 服务器监听的端口

int main() {
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
    struct addrinfo* 
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // 创建socket对象
    SOCKET ListenSocket = INVALID_SOCKET;
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 绑定套接字 start
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 3. 绑定套接字 end

    return 0;
}

4.在套接字上监听客户端

将套接字绑定到系统上的 IP 地址和端口之后,服务器必须在该 IP 地址和端口上侦听传入的连接请求

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

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


#define DEFAULT_PORT "9501" // 服务器监听的端口

int main() {
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
    struct addrinfo* 
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // 创建socket对象
    SOCKET ListenSocket = INVALID_SOCKET;
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 绑定套接字 start
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 3. 绑定套接字 end

    // 4.在套接字上监听客户端 start
    if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
        // SOMAXCONN定义了此套接字允许最大连接
        printf("Listen failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 4.在套接字上监听客户端 end

    return 0;
}

备注: window10第一次调试这一步骤会让用户给予防火墙权限

5. 接受来自客户端的连接。

当套接字侦听连接后,程序必须处理该套接字上的连接请求

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

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


#define DEFAULT_PORT "9501" // 服务器监听的端口

int main() {
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
    struct addrinfo* 
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // 创建socket对象
    SOCKET ListenSocket = INVALID_SOCKET;
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 绑定套接字 start
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 3. 绑定套接字 end

    // 4.在套接字上监听客户端 start
    if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
        // SOMAXCONN定义了此套接字允许最大连接
        printf("Listen failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 4.在套接字上监听客户端 end
    
    // 5.接受来自客户端的连接 start
    SOCKET ClientSocket; //临时 套接字 对象,以接受来自客户端的连接
    // 这里没有考虑多线程并发接受多个客户端 只接受一个
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 5.接受来自客户端的连接 end
    return 0;
}

备注:运行这一步时, 控制台似乎没有显示任何东西, 其实 是accept 将逻辑流程卡住 等待 客户端连接, 如下图所示


accept 将逻辑流程卡住

6.在服务器上接收和发送数据

服务器接收的数据来自客户端, 发送也是向客户端发送数据, 故而需要等下面的客户端socket编写完毕才能进行最终的功能测试.

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

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




int main() {
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
#define DEFAULT_PORT "9501" // 服务器监听的端口
    struct addrinfo* 
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // 创建socket对象
    SOCKET ListenSocket = INVALID_SOCKET;
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 绑定套接字 start
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 3. 绑定套接字 end

    // 4.在套接字上监听客户端 start
    if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
        // SOMAXCONN定义了此套接字允许最大连接
        printf("Listen failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 4.在套接字上监听客户端 end
    
    // 5.接受来自客户端的连接 start
    SOCKET ClientSocket; //临时 套接字 对象,以接受来自客户端的连接
    // 这里没有考虑多线程并发接受多个客户端 只接受一个
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 5.接受来自客户端的连接 end

    // 6.在服务器上接收和发送数据 start
#define DEFAULT_BUFLEN 512 //  字符缓冲区长度
    char recvbuf[DEFAULT_BUFLEN]; // 字符缓冲区数组
    int  iSendResult;
    int recvbuflen = DEFAULT_BUFLEN;

    do {
        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

            // recvbuf参数表示: 这里为了简单将客户端发送过来的消息再发送给客户端 
            iSendResult = send(ClientSocket, recvbuf, iResult, 0);
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            printf("Bytes sent: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("Connection closing...\n");
        else {
            printf("recv failed: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
    } while (iResult > 0);
    // 6.在服务器上接收和发送数据 end

    return 0;
}

备注: 这一步相当于完成了服务器的书写,但为了保险, 还是要关闭连接

7.断开连接

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

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




int main() {
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
#define DEFAULT_PORT "9501" // 服务器监听的端口
    struct addrinfo* 
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // 创建socket对象
    SOCKET ListenSocket = INVALID_SOCKET;
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 绑定套接字 start
    iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);// 调用 bind 函数后,不再需要地址信息 释放
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 3. 绑定套接字 end

    // 4.在套接字上监听客户端 start
    if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) {
        // SOMAXCONN定义了此套接字允许最大连接
        printf("Listen failed with error: %ld\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 4.在套接字上监听客户端 end
    
    // 5.接受来自客户端的连接 start
    SOCKET ClientSocket; //临时 套接字 对象,以接受来自客户端的连接
    // 这里没有考虑多线程并发接受多个客户端 只接受一个
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }
    // 5.接受来自客户端的连接 end

    // 6.在服务器上接收和发送数据 start
#define DEFAULT_BUFLEN 512 //  字符缓冲区长度
    char recvbuf[DEFAULT_BUFLEN]; // 字符缓冲区数组
    int  iSendResult;
    int recvbuflen = DEFAULT_BUFLEN;

    do {
        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

            // recvbuf参数表示: 这里为了简单将客户端发送过来的消息再发送给客户端 
            iSendResult = send(ClientSocket, recvbuf, iResult, 0);
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            printf("Bytes sent: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("Connection closing...\n");
        else {
            printf("recv failed: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }
    } while (iResult > 0);
    // 6.在服务器上接收和发送数据 end

    // 7.断开连接 start
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }
    // 7.断开连接 end

    return 0;
}

备注: 这里没有写控制台输入判断 进行关闭服务器 而是等客户端传输完数据后自动执行关闭逻辑

1.初始化

这一步和服务端一样, 见最上面

2.客户端创建套接字

需要重新用vs创建一个新项目当做客户端

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

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




int main() {
    printf("启动客户端");
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
    struct addrinfo*
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }
    SOCKET ConnectSocket  = INVALID_SOCKET;
    ConnectSocket  = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ConnectSocket  == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    

    return 0;
}

3.客户端连接到该服务器

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

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




int main() {
    printf("启动客户端");
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
    struct addrinfo*
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }
    SOCKET ConnectSocket  = INVALID_SOCKET;
    ConnectSocket  = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ConnectSocket  == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 连接到套接字 start
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }


        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }
    // 3. 连接到套接字 end
    freeaddrinfo(result);

    return 0;
}

4.客户端发送和接收数据

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

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




int main() {
    printf("启动客户端");
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
    struct addrinfo*
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }
    SOCKET ConnectSocket  = INVALID_SOCKET;
    ConnectSocket  = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ConnectSocket  == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 连接到套接字 start
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }


        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }
    // 3. 连接到套接字 end
    freeaddrinfo(result);

    // 4.在客户端上发送和接收数据 start
#define DEFAULT_BUFLEN 512

    int recvbuflen = DEFAULT_BUFLEN;

    const char* sendbuf = "Hello World";
    char recvbuf[DEFAULT_BUFLEN];
    // 发送
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send failed: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }
    printf("Bytes Sent: %ld\n", iResult);

    // 关闭
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // 接收
    do {
        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0)
            printf("Bytes received: %d\n", iResult);
        else if (iResult == 0)
            printf("Connection closed\n");
        else
            printf("recv failed: %d\n", WSAGetLastError());
    } while (iResult > 0);
    // 4.在客户端上发送和接收数据 end



    return 0;
}

5.客户端断开连接

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

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




int main() {
    printf("启动客户端");
    // 1. 初始化 start
    WSADATA wsaData;
    int iResult;
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }
    // 1. 初始化 end

    // 2. 创建套接字 start
#define DEFAULT_IP "127.0.0.1"// 服务器为本机
#define DEFAULT_PORT "9501" // 服务器监听的端口
    struct addrinfo*
        result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET; //AF _INET 用于指定 IPv4 地址族
    hints.ai_socktype = SOCK_STREAM;// SOCK _STREAM 用于指定流套接字
    hints.ai_protocol = IPPROTO_TCP;// IPPROTO _TCP 用于指定 tcp 协议
    hints.ai_flags = AI_PASSIVE;

    // 从本机中获取ip地址等信息为了sockcet 使用
    iResult = getaddrinfo(DEFAULT_IP, DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }
    SOCKET ConnectSocket  = INVALID_SOCKET;
    ConnectSocket  = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ConnectSocket  == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }
    // 2. 创建套接字 end

    // 3. 连接到套接字 start
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }


        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }
    // 3. 连接到套接字 end
    freeaddrinfo(result);

    // 4.在客户端上发送和接收数据 start
#define DEFAULT_BUFLEN 512

    int recvbuflen = DEFAULT_BUFLEN;

    const char* sendbuf = "Hello World";
    char recvbuf[DEFAULT_BUFLEN];
    // 发送
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send failed: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }
    printf("Bytes Sent: %ld\n", iResult);

    // 关闭
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // 接收
    do {
        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0)
            printf("Bytes received: %d\n", iResult);
        else if (iResult == 0)
            printf("Connection closed\n");
        else
            printf("recv failed: %d\n", WSAGetLastError());
    } while (iResult > 0);
    // 4.在客户端上发送和接收数据 end

    // 5.断开连接 start
    // 这里和服务器断开连接写在最后不同, 客户端断开连接写在 发送后 和 接收前
    // shutdown(ConnectSocket, SD_SEND) SD_SEND表示socket的发送数据端虽然关闭(为了服务器释放客户端连接资源), 但是仍然能接收服务端的数据
    //iResult = shutdown(ClientSocket, SD_SEND);
    //if (iResult == SOCKET_ERROR) {
    //  printf("shutdown failed: %d\n", WSAGetLastError());
    //  closesocket(ClientSocket);
    //  WSACleanup();
    //  return 1;
    //}
    closesocket(ConnectSocket);
    WSACleanup();
    // 5.断开连接 end

    return 0;
}

结果

启动服务端 和客户端 可以看到, 双方都能接收到


image.png

备注: 本文内容来源于 [微软官方文档] (https://docs.microsoft.com/zh-cn/windows/win32/winsock/using-winsock)
因为文档 内容繁多 ,特做笔记

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容