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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具