编程思路

第一步

创建一个WASDATA结构体变量,用于存储关于Winsock库的信息;初始化Winsock库。

第二步

创建TCP套接字。

第三步

创建sockaddr_in结构体变量,用于储存服务器地址信息。里面包括设置地址族、IP地址、端口号。

第四步

调用connect函数连接服务器。

通信

send函数发送数据

recv函数接收数据

实现代码

头文件部分

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

#pragma comment(lib, "ws2_32.lib") //告诉编译器链接Winsock库

main 函数部分

int main()
{
    WSADATA wsaData; //创建一个结构体变量,用于存储关于Winsock库的信息
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData); //初始化Winsock库,指定版本号2.2,检查返回值
    if (result != 0)
    {
        std::cout << "WSAStartup failed: " << result << std::endl; //输出错误信息并退出程序
        return 1;
    }

    SOCKET connectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //创建一个TCP套接字,检查返回值
    if (connectSocket == INVALID_SOCKET)
    {
        std::cout << "socket failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序
        WSACleanup(); //清除Winsock库
        return 1;
    }

    sockaddr_in service; //创建一个结构体变量,用于存储服务器地址信息
    service.sin_family = AF_INET; //指定地址族为IPv4
    inet_pton(AF_INET, "127.0.0.1", &service.sin_addr); //将字符串类型的IP地址转换为二进制网络字节序的IP地址,并存储在结构体中
    service.sin_port = htons(6666); //将端口号从主机字节序转换为网络字节序,并存储在结构体中

    result = connect(connectSocket, (SOCKADDR*)&service, sizeof(service)); //连接到服务器,检查返回值
    if (result == SOCKET_ERROR)
    {
        std::cout << "connect failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序
        closesocket(connectSocket); //关闭套接字
        WSACleanup(); //清除Winsock库
        return 1;
    }

    std::cout << "Connected to server." << std::endl; //连接成功,输出消息

    char sendBuffer[1024] = "Hello, server!"; //创建发送缓冲区,存储待发送的数据
    result = send(connectSocket, sendBuffer, sizeof(sendBuffer), 0); //向服务器发送数据,检查返回值
    if (result == SOCKET_ERROR)
    {
        std::cout << "send failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序
        closesocket(connectSocket); //关闭套接字
        WSACleanup(); //清除Winsock库
        return 1;
    }

    char recvBuffer[1024]; //创建接收缓冲区,用于存储从服务器接收到的数据
    result = recv(connectSocket, recvBuffer, sizeof(recvBuffer), 0); //从服务器接收数据,检查返回值
    if (result == SOCKET_ERROR)
    {
        std::cout << "recv failed with error: " << WSAGetLastError() << std::endl; //输出错误信息并退出程序
        closesocket(connectSocket); //关闭套接字
        WSACleanup(); //清除Winsock库
        return 1;
    }

    std::cout << "Received message from server: " << recvBuffer << std::endl; //输出从服务器收到的数据

    closesocket(connectSocket); //关闭套接字
    WSACleanup(); //清除Winsock库

    return 0;
}

注意事项(debug过程)

运行代码之前要使用网络调试助手打开TCP服务端,注意端口号、IP地址这些要匹配。

代码主要是C语言,C++部分是控制台输入输出的,如果纯C语言就include<stdio.h>,用printfscanf这些库函数替换就好了。

以上代码使用VS的编译器上是可以直接运行的,但是如果是使用MinGW gcc/g++就会出现问题。我们来看报错。

1.inet_pton函数未定义。

inet_pton函数将点分十进制串转换成网络字节序二进制值,此函数对IPv4地址和IPv6地址都能处理,在Windows下只需包含ws2tcpip.h头文件就行了

根据网上的说法,是因为Windows gcc 默认的 _WIN32_WINNT 是 502 Windows Server 2003,所以解决方案是重新定义_WIN32_WINNT,在ws2tcpip.h前加入

#ifdef  _WIN32_WINNT
#undef  _WIN32_WINNT
#define  _WIN32_WINNT  0x0600
#endif

还有一个方法,那就是。。。不用inet_pton()函数了,用别的IP地址转换函数——inet_addr()

这样第三步的代码改为

    sockaddr_in service; //创建一个结构体变量,用于存储服务器地址信息
    service.sin_family = AF_INET; //指定地址族为IPv4
    service.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //将点分十进制转化32位unsigned int
    service.sin_port = htons(6666); //将端口号从主机字节序转换为网络字节序,并存储在结构体中

这个问题就解决了。

2. 链接动态库问题undefined reference to __imp_WSAStartup

在vs中直接加入

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

就可以链接winsock动态库,不知道为什么gcc编译器不行。1 问题解决后,报如下错误:

所以在vscode的配置tasks.json的args参数加一个:"-lwsock32",在编译命令中指定链接就可以了。

对于如果找不到_imp_inet_pton的话,就需要再加args参数:"-lws2_32"

然后就能连上TCP服务器了。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。