C++ 之 SOCKET 通信详解

C++ 之 SOCKET 通信详解

  • SOCKET中首先我们要理解如下几个定义概念:

    • 一是IP地址:IP Address, 就是依照TCP/IP协议分配给本地主机的网络地址, 比如两个进程要通讯,任一进程要知道通讯对方的位置,就用对方的IP。
    • 二是端口号: 用来标识本地通讯进程,方便OS提交数据.就是说进程指定了对方进程的网络IP,但这个IP只是用来标识进程所在的主机,如何来找到运行在这个主机的这个进程呢,就用端口号。
    • 三是连接: 指两个进程间的通讯链路
    • 四是半相关: 网络中用一个三元组可以在全局唯一标志一个进程:(协议,本地地址,本地端口号)
    • 五是全相关:一个完整的网间进程通信需要由两个进程组成,并且只能使用同一种高层协议。也就是说,不可能通信的一端用TCP协议,而另一端用UDP协议。因此一个完整的网间通信需要一个五元组来标识:(协议,本地地址,本地端口号,远地地址,远地端口号)
  • 运行模式:

    • 在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。
    • 客户/服务器模式的建立基于以下两点:首先,建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而造就拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。其次,网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区,因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是客户/服务器模式的TCP/IP。
  • 下面详细总结一下 SOCKET 相关原理

1. SOCKET 基础

  • Socket 是计算机网络中的端点,客户端和服务器通过Socket进行通信。每个Socket都是由操作系统提供的一个API,它封装了网络协议栈的各种细节,程序通过调用Socket相关函数来完成数据的发送和接收。

  • Socket的类型在网络编程中,主要有两种Socket类型:

    • TCP Socket (SOCK_STREAM):面向连接的可靠通信。数据以流的形式传输,适用于要求可靠性、顺序传输的应用(如HTTP、FTP等)。
    • UDP Socket (SOCK_DGRAM):无连接的、不可靠的通信方式。数据报文独立发送,不保证顺序、可靠性,适用于对实时性要求较高但对丢包可以容忍的场景(如DNS、视频流、实时游戏等)。
  • Socket是网络通信端点,TCP协议提供可靠连接(类似打电话),UDP协议提供无连接服务(类似发短信)。

  • 完整流程总结

1. ​初始化 Winsock:WSAStartup()2. 创建 Socket:socket()3. 绑定地址​(服务器):bind()4. 监听连接​(TCP 服务器):listen()5. 接受连接​(TCP 服务器):accept()6. 连接服务器​(TCP 客户端):connect()7. 数据传输:send()/recv()sendto()/recvfrom()8. 关闭 Socket:closesocket()9. 清理资源:WSACleanup()

2. 基础接口

1. 初始化与清理

  • WSAStartup()
    ​功能:初始化 Winsock 库,必须在使用其他 Socket 函数前调用。
  • ​函数原型:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

​参数: wVersionRequested:请求的 Winsock 版本(如 MAKEWORD(2, 2) 表示 2.2 版本)。
lpWSAData:指向 WSADATA 结构的指针,用于返回库的详细信息。
​返回值:成功返回 0,失败返回非零错误码。

  • ​示例:
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
    std::cerr << "WSAStartup 失败,错误码: " << WSAGetLastError() << std::endl;
    return 1;
}
  • WSACleanup()
    ​功能:清理 Winsock 库资源,程序退出前调用。
  • ​函数原型:
int WSACleanup();

​返回值:成功返回 0,失败返回 SOCKET_ERROR。 ​
注意事项:必须与 WSAStartup() 成对使用。

2. Socket 的创建与关闭

  • socket()
    ​功能:创建 Socket 句柄。
    ​- 函数原型:
SOCKET socket(int af, int type, int protocol);

​参数: af:地址族(如 AF_INET 表示 IPv4,AF_INET6 表示 IPv6)。
type:Socket 类型(SOCK_STREAM 为 TCP,SOCK_DGRAM 为 UDP)。
protocol:协议类型(通常设为 0,系统自动选择)。
​返回值:成功返回 SOCKET 句柄,失败返回 INVALID_SOCKET。

​- 示例:

SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
    std::cerr << "socket 失败,错误码: " << WSAGetLastError() << std::endl;
}
  • closesocket()
    ​功能:关闭 Socket 句柄(Windows 专用,替代 close())。
  • ​函数原型:
int closesocket(SOCKET s);

​返回值:成功返回 0,失败返回 SOCKET_ERROR。

3. 地址绑定与连接

  • bind()
    ​功能:将 Socket 绑定到本地 IP 和端口。
    ​函数原型:
int bind(SOCKET s, const sockaddr* addr, int addrlen);

​参数:
s:Socket 句柄。
addr:指向 sockaddr_in(IPv4)或 sockaddr_in6(IPv6)的指针。
addrlen:地址结构体的长度。

​- 示例:

sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡
serverAddr.sin_port = htons(8080);              // 端口 8080
if (bind(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
    std::cerr << "bind 失败,错误码: " << WSAGetLastError() << std::endl;
}
  • listen()
    ​功能:将 TCP Socket 设为监听模式,等待客户端连接。
  • ​函数原型:
int listen(SOCKET s, int backlog);

​参数:
backlog:等待连接队列的最大长度(通常设为 SOMAXCONN)。
​返回值:成功返回 0,失败返回 SOCKET_ERROR。

  • accept()
    ​功能:接受 TCP 客户端的连接请求,返回新 Socket 用于通信。
  • ​函数原型:
SOCKET accept(SOCKET s, sockaddr* addr, int* addrlen);

​参数:
addr:用于存储客户端地址的缓冲区。
addrlen:输入时为缓冲区大小,输出时为实际地址长度。 ​返回值:成功返回新
Socket,失败返回 INVALID_SOCKET。

  • connect()
    ​功能:TCP 客户端连接服务器;UDP 可指定默认目标地址。
  • ​函数原型:
int connect(SOCKET s, const sockaddr* name, int namelen);
  • ​示例:
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
    std::cerr << "connect 失败,错误码: " << WSAGetLastError() << std::endl;
}

4. 数据传输

  • ​TCP 数据传输
    send() 和 ​recv()
int send(SOCKET s, const char* buf, int len, int flags);
int recv(SOCKET s, char* buf, int len, int flags);

参数: flags:控制选项(如 MSG_WAITALL 等待所有数据到达)。 ​
返回值:成功返回发送/接收的字节数,连接关闭返回 0,失败返回 SOCKET_ERROR。

  • ​UDP 数据传输
    sendto() 和 ​recvfrom()
int sendto(SOCKET s, const char* buf, int len, int flags, const sockaddr* to, int tolen);
int recvfrom(SOCKET s, char* buf, int len, int flags, sockaddr* from, int* fromlen);

​- 示例​(UDP 服务器接收数据):

char buffer[1024];
sockaddr_in clientAddr{};
int clientAddrLen = sizeof(clientAddr);
int bytesRead = recvfrom(sock, buffer, sizeof(buffer), 0,
                         (sockaddr*)&clientAddr, &clientAddrLen);

5. 错误处理

WSAGetLastError()
​功能:获取最后一次 Socket 操作的错误码。

  • ​示例:
if (listen(sock, 5) == SOCKET_ERROR) {
    int error = WSAGetLastError();
    std::cerr << "listen 错误,错误码: " << error << std::endl;
}

3. 一个简单的C++ TCP服务器和客户端

  • 服务端
/*
* 服务端
*/

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")  // 链接 Winsock 库

int main() {
    // 1. 初始化 Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup 失败!错误码: " << WSAGetLastError() << std::endl;
        return 1;
    }

    // 2. 创建 TCP Socket
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET) {
        std::cerr << "创建 Socket 失败!错误码: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // 3. 绑定地址
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;  // 绑定所有网卡
    serverAddr.sin_port = htons(8080);        // 端口 8080
    if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "绑定失败!错误码: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    // 4. 监听连接
    if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) {
        std::cerr << "监听失败!错误码: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    std::cout << "等待客户端连接..." << std::endl;

    // 5. 接受客户端连接
    sockaddr_in clientAddr{};
    int clientAddrLen = sizeof(clientAddr);
    SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrLen);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "接受连接失败!错误码: " << WSAGetLastError() << std::endl;
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    // 6. 接收数据
    char buffer[1024];
    int bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        std::cout << "收到消息: " << std::string(buffer, bytesReceived) << std::endl;
    }

    // 7. 发送响应
    const char* response = "Hello from Windows Server!";
    send(clientSocket, response, strlen(response), 0);

    // 8. 关闭 Socket 和清理
    closesocket(clientSocket);
    closesocket(serverSocket);
    WSACleanup();
    return 0;
}
  • 客户端
/*
* 客户端
*/

#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")

int main() {
    // 1. 初始化 Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup 失败!错误码: " << WSAGetLastError() << std::endl;
        return 1;
    }

    // 2. 创建 TCP Socket
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        std::cerr << "创建 Socket 失败!错误码: " << WSAGetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

    // 3. 连接服务器
    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
    if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "连接服务器失败!错误码: " << WSAGetLastError() << std::endl;
        closesocket(clientSocket);
        WSACleanup();
        return 1;
    }

    // 4. 发送数据
    const char* message = "Hello from Windows Client!";
    send(clientSocket, message, strlen(message), 0);

    // 5. 接收响应
    char buffer[1024];
    int bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0);
    if (bytesReceived > 0) {
        std::cout << "服务器响应: " << std::string(buffer, bytesReceived) << std::endl;
    }

    // 6. 关闭 Socket 和清理
    closesocket(clientSocket);
    WSACleanup();
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明月醉窗台

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值