C++编程笔记(通信)(win32平台)

一、初始化网络库

基本都得这么些

bool InitSocket() {//可以直接写到mian中
    WSADATA *wsadata = new WSADATA();
    if (0 != WSAStartup(MAKEWORD(2, 2), wsadata)) {
        ERROR("WSADATA");
        return false;
    };
    return true;
}
InitSocket();
SetConsoleOutputCP(65001);//防止乱码,因为我用clion调用命令行会乱码

二、socket套接字

2.1服务端

//用于输出错误信息
#define ERROR(errMsg) std::cout<< "[error] "<<errMsg<<" failed code:" << WSAGetLastError()<<"\tline:"<<__LINE__<<"\n";

//创建一个空socket
SOCKET socket1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket1) {
    ERROR("SOCKET");
    return INVALID_SOCKET;
}
struct sockaddr_in addrClient;//用来放连接者的ip信息
//绑定ip+port
struct sockaddr_in *addr = new struct sockaddr_in;
addr->sin_family = AF_INET;
addr->sin_port = htons(8888);
addr->sin_addr.S_un.S_addr = ADDR_ANY;

if (SOCKET_ERROR == bind(socket1, reinterpret_cast<const sockaddr *>(addr), sizeof(*addr))) {
    ERROR("bind");
    return INVALID_SOCKET;
}
delete addr;
listen(socket1, 20);		//最大连接数

clientSocket = accept(serverSocket, (struct sockaddr *) &addrClient, socket_len);//接受连接,并返回客户端socket,通过这个socket与客户端通信


2.2客户端

SOCKET socket1 = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socket1) {
    ERROR("SOCKET");
    return INVALID_SOCKET;
}
//绑定服务器的ip+port
struct sockaddr_in *addr = new struct sockaddr_in;
addr->sin_family = AF_INET;
addr->sin_port = htons(8888);
addr->sin_addr.S_un.S_addr = inet_addr(ip);//绑定服务器ip("127.0.0.1")  字符串类型
if (INVALID_SOCKET == connect(socket1, reinterpret_cast<const sockaddr *>(addr), sizeof(*addr))) {
    ERROR("connect");
    delete addr;
    return INVALID_SOCKET;
}
delete addr;
return socket1;//通过这个socket就可以与服务器通信了

三、发送、接收数据

3.1发送

/*
*  参数一: 套接字,要往哪里发数据就写那个套接字
*  参数二: 发送的数据
*  参数三: 发送的数据的长度
*  参数四: flag  我也不知道有啥用,反正无脑填0没问题
*/
char *buf = new char[1024];
memset(buf, 0, sizeof(buf));//清理内存,建议往里面填东西之前先清空
send(clientSocket, buf, strlen(buf), 0);

3.2接收数据

/*recv函数(阻塞函数) 
* 参数一: 发送者的socket
* 参数二: 接受数据的缓冲区(用之前一定要清空)
* 参数三: buf的长度
* 参数四: flag
* 返回值: 接收到的数据长度,若为0,则说明连接断开
*/
char *buf = new char[1024];
memset(buf, 0, sizeof(*buf));//接受数据之前一定要清空,一定要清空,一定要清空,一定要清空
int ret2 = recv(this->Socket, buf, 1024, 0);
if (ret2 <= 0) {
    cout << "id: " << this->Socket << "  disconnect!\n";
    closesocket(this->Socket);
    return;
} else {
    cout << "recv:\t" << buf << endl;
}

四、自定义的结构体

4.1 发送端

typedef struct user {
    char userName[32];
    char password[32];
} USER;

char *buf = new char[1024];
USER *login = new USER();
memset(buf, 0, sizeof(buf));		//将内存置空
memcpy(buf, login, sizeof(USER));	//将结构体数据拷贝到内存发送缓冲区
send(clientSocket, buf, sizeof(*login), 0);

4.2接收端

char *buf = new char[1024];
USER *user = new USER();
memset(buf, '\0', 1024);
memset(user, 0, sizeof(USER));
ret1 = recv(this->Socket, buf, sizeof(USER), 0);
memcpy(user, buf, sizeof(*user));//也可以事先判断一下是否接收成功
/*
* ret返回值
* 大于0: 接收到的字节数
* 等于0: 连接断开
* 小于0: 出错,一般就是套接字关闭了
*/

IPV6版本套接字的创建

//server.cpp
#include <iostream>
#include <string>
#include "windows.h"
#include <WinSock2.h>
#include <WS2tcpip.h>

int main(int argc, char *argv[]) {
	/*
	首先初始化网络库,这里就不写出来了
	*/
    if (argc < 2) {
        cout << "param error ,you should give port\n exmaple: server.exe 9999" << endl;
        exit(-1);
    }
    SetConsoleOutputCP(65001);//避免控制台乱码
    int listenfd = 0;
    const char on = 1;
    struct addrinfo hints{0}, *res, *ressave;
    hints.ai_flags = AI_PASSIVE;//被动匹配所有ip包括ipv6,通常用于bind
    hints.ai_family = AF_UNSPEC;//允许ipv4或者ipv6
    hints.ai_socktype = SOCK_STREAM;//流类型
    hints.ai_protocol = IPPROTO_IP;//匹配所有ip协议
    char mIpAddr[16];//ipv6 128位

    //获取ip地址,res指向一个包含ip信息的链表
    //param1:IP地址,null表示所有ip;param2:端口;param3:参数设置,上述有详解;param4返回值,返回包含端口,ip信息的一个链表
    //使用getaddrinfo获取本地所有的ip包括可以使用域名作为参数
    auto ret = getaddrinfo(nullptr, argv[1], &hints, &res);//nullptr表示本机所有ip和别名
    if (ret == -1) {
        perror("getaddrinfo");
        exit(ret);
    }
    ressave = res;

    while (res != nullptr) {
        //创建一个监听套接字
        if (-1 == (listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol))) {
            perror("socket");
            res = res->ai_next;
            continue;
        }

        //设置端口复用
        if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
            cout << "setsockopt error: " << strerror(errno) << endl;
            closesocket(listenfd);
            res = res->ai_next;
            continue;
        }
        int ipv6only = 0;//将ipv6only设置为0,这样两个版本的ip都能用
        if (setsockopt(listenfd, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &ipv6only, sizeof(ipv6only)) != 0) {
            cout << "set ipv6only failed!";
            continue;
        }
        //绑定
        if (SOCKET_ERROR == bind(listenfd, res->ai_addr, res->ai_addrlen)) {
            closesocket(listenfd);
            perror("bind");
            res = res->ai_next;
            continue;
        }
        //监听
        if (-1 == listen(listenfd, 10)) {
            closesocket(listenfd);
            perror("listen");
            res = res->ai_next;
            continue;
        }
        break;
    }

    freeaddrinfo(ressave);
    ressave = nullptr;
    res = nullptr;

    if (listenfd < 0) {
        perror("listenfd");
        exit(-1);
    }
    struct sockaddr_storage clientAddr{0};//通用结构体
    int len = sizeof(clientAddr);

    //等待链接
    int clientfd = accept(listenfd, (struct sockaddr *) &clientAddr, &len);
    auto tmpaddr = new char[32];
    DWORD tmpaddrlen=INET6_ADDRSTRLEN;
    //sockaddr *addr = (struct sockaddr *) &clientAddr;
    if (clientAddr.ss_family == AF_INET6) {
        struct sockaddr_in6 *p = (struct sockaddr_in6 *) &clientAddr;
        printf("16进制ip地址为:");
        WSAAddressToStringA((LPSOCKADDR) p, sizeof(struct sockaddr_in6), nullptr, (LPSTR) tmpaddr, &tmpaddrlen);
        cout << tmpaddr << endl;
    } else if (clientAddr.ss_family == AF_INET) {
        struct sockaddr_in *p = (struct sockaddr_in *) &clientAddr;
        cout << "client ip:" << inet_ntoa(p->sin_addr);
    } else {
        perror("获取客户端信息失败");
        exit(-1);
    }
    //至此连接已经建立,就可以通过,可以使用clientfd进行通信了

}

客户端口

//client.cpp
//
// Created by lhh on 2022/4/16.
//
#include <iostream>
#include <string>
#include "windows.h"
#include <WinSock2.h>
#include <WS2tcpip.h>//getaddrinfo inet_ntop

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

using namespace std;

int main(int argc, char *argv[]) {
    if (argc < 3) {
        cout << "param error ,you should give ip and port\n exmaple: server.exe 127.0.0.1 9999" << endl;
        exit(-1);
    }
    SetConsoleOutputCP(65001);
    int sockfd = 0;
    const char on = 1;
    struct addrinfo hints{0}, *res, *ressave;
    hints.ai_flags = AI_PASSIVE;//匹配所有ip
    hints.ai_family = AF_UNSPEC;//允许ipv4或者ipv6
    hints.ai_socktype = SOCK_STREAM;//流类型
    hints.ai_protocol = IPPROTO_IP;//匹配所有协议
    char mIpAddr[16];//ipv6 128位

    //获取ip地址,res指向一个包含ip信息的链表
    auto ret = getaddrinfo(argv[1], argv[2], &hints, &res);
    if (ret == -1) {
        perror("getaddrinfo");
        exit(ret);
    }
    ressave = res;
    char *tmpaddr = new char[50];
    memset(tmpaddr,0,50);
    DWORD tmpaddrlen=INET6_ADDRSTRLEN;
    //查看获取到的ip地址
    for (auto cur = res; cur != nullptr; cur = cur->ai_next) {
        if (cur->ai_family == AF_INET) {
            auto addr = (sockaddr_in *) cur->ai_addr;//解析出ip的地址
            sprintf(mIpAddr, "IPV4:%d.%d.%d.%d", addr->sin_addr.S_un.S_un_b.s_b1,
                    addr->sin_addr.S_un.S_un_b.s_b2, addr->sin_addr.S_un.S_un_b.s_b3, addr->sin_addr.S_un.S_un_b.s_b4);
            printf("%s\n", mIpAddr);
        } else if (cur->ai_family == AF_INET6) {
            struct sockaddr_in6 *p = (struct sockaddr_in6 *) (cur->ai_addr);
            WSAAddressToStringA((LPSOCKADDR) cur->ai_addr, sizeof(struct sockaddr_in6), nullptr, (LPSTR) tmpaddr, &tmpaddrlen);
            printf("IPV6:%s",tmpaddr);
        }
    }

    while (res != nullptr) {//由于获取到的ip不一定都能访问到,所以循环读取链表,读取到可以成功连接的就跳出循环
        //创建一个套接字
        if (-1 == (sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol))) {
            perror("socket");
            res = res->ai_next;
            continue;
        }
        //建立链接
        if (-1 == connect(sockfd, res->ai_addr, res->ai_addrlen)) {
            perror("connect");
            closesocket(sockfd);
            res = res->ai_next;
            continue;
        }
        printf("connect success");
        break;
    }
    freeaddrinfo(ressave);
    ressave = nullptr;
    res = nullptr;
    //后续通过套接字通信就可以了

}


#ifdef WIN32

class WSInit {
public:
    WSInit() {
        WSADATA wsadata;
        WSAStartup(MAKEWORD(2, 2), &wsadata);
    }

    ~WSInit() { WSACleanup(); }
};

static WSInit wsinit_;
#endif

参考:

IPV6相关函数解析:https://blog.csdn.net/v6543210/article/details/106927210

posted @   DaoDao777999  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示