教程和ppt图片来源:c/c++_socket编程_哔哩哔哩_bilibili
代码:
Iomode.h
//服务端
#pragma once
#include <iostream>
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#define RECV_POSTED 10
#pragma comment(lib, "ws2_32.lib")
using namespace std;
//句柄关联信息
struct PER_HANDLE_DATA
{
SOCKET socket;
sockaddr_in clientaddr;
};
//重叠结构IO关联信息
struct PER_IO_DATA
{
OVERLAPPED overlapped;
SOCKET client;
WSABUF wsbuf;
char buffer[1024];
int OperationType;
};
class Iomode
{
private:
sockaddr_in serveraddr;
public:
Iomode(int port,string ipaddr);
~Iomode();
static SOCKET clientInstance;
static WSABUF wsbuffer;
static CRITICAL_SECTION cs;
//选择模式
void selectmode();
//阻塞模式
void blockmode();
//事件选择模型
int WSeventSelectmode();
//重叠模型 事件通知模型
int overlappedmode();
//重叠模型 事件通知例程模型
int overlappedInstanceMode();
//重叠模型 IOCP模型
void completionportmode();
};
Iomode.cpp
#include "Iomode.h"
#include <vector>
#include <map>
Iomode::Iomode(int port, string ipaddr)
{
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData);
serveraddr.sin_family = AF_INET;
/*网络字节顺序与本地字节顺序之间的转换函数:
htonl()--"Host to Network Long"
ntohl()--"Network to Host Long"
htons()--"Host to Network Short"
ntohs()--"Network to Host Short"*/
serveraddr.sin_port = htons(port);
serveraddr.sin_addr.s_addr = inet_addr(ipaddr.c_str());
}
Iomode::~Iomode()
{
WSACleanup();
}
//阻塞模式
void Iomode::blockmode()
{
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//阻塞模式,0:阻塞,1:非阻塞
unsigned long ul = 0;
//控制socket阻塞模式
//FIONBIO属性 此属性的作用是“允许或者禁止套接字的非阻塞模式
int nret = ioctlsocket(server, FIONBIO, (unsigned long*)&ul);
int err;
err = bind(server,(sockaddr*)&serveraddr,sizeof(serveraddr));
char buffer[1024];
sockaddr_in clientaddr;
int len = sizeof(clientaddr);
recvfrom(server,buffer,1023,0,(sockaddr*)&clientaddr,&len);
//监听客户机传来的消息
err = listen(server, 5);
std::cout << "listen...." << std::endl;
//处理连接请求
SOCKET client = accept(server, (sockaddr*)&clientaddr, &len);
}
//选择模式
void Iomode::selectmode()
{
std::vector<SOCKET> clientset;
//套接字,地址
std::map<SOCKET, sockaddr_in> s2addr;
int ret = 0;
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//阻塞模式,0:阻塞,1:非阻塞
unsigned long ul = 1;
//控制socket阻塞模式
//FIONBIO属性 此属性的作用是“允许或者禁止套接字的非阻塞模式
int nret = ioctlsocket(server, FIONBIO, (unsigned long*)&ul);
bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
listen(server, 5);
//用于检查可读取数据的套接字集合
fd_set readfd;
//不断接收检查从客户机传来的消息集合
while (1)
{
sockaddr_in addr;
int len = sizeof(addr);
SOCKET client = accept(server, (sockaddr*)&addr, &len);
//如果client有效,加入
if (client != INVALID_SOCKET)
{
clientset.push_back(client);
s2addr[client] = addr;
cout << inet_ntoa(addr.sin_addr) << "已经加入连接..." << "当前连接数:" << clientset.size() << endl;
}
//将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空
//,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_ZERO(&readfd);
for (size_t i = 0; i < clientset.size(); i++)
{
FD_SET((int)(clientset[i]), &readfd);
}
//查看各个客户机是否发送数据过来
ret = 0;
if (!clientset.empty())
{
timeval tv = { 0,0 };
ret = select(clientset[clientset.size() - 1] + 1, &readfd, NULL, NULL, &tv);
}
//处理接收的数据
if (ret>0)
{
std::vector<SOCKET> deleteclient;
for (size_t i = 0; i < clientset.size(); i++)
{
if (FD_ISSET((int)(clientset[i]),&readfd))
{
//接受客户机的消息
char recvdata[1024] = { 0 };
//为了防止溢出,设置成1023
recv(clientset[i], recvdata, 1023, 0);
string data = recvdata;
if (data == "quit")
deleteclient.emplace_back(clientset[i]);
else
cout <<"来自" << inet_ntoa(s2addr[clientset[i]].sin_addr) << ":" << recvdata<< endl;
}
}
//关闭要退出的套接字,在套接字集合中删除它
if(!deleteclient.empty())
{
for (size_t i = 0; i < deleteclient.size(); i++)
{
cout << "客户机" << inet_ntoa(s2addr[deleteclient[i]].sin_addr) << "已退出连接,剩余连接数:" << clientset.size()-1 << endl;
auto it = find(clientset.begin(), clientset.end(), deleteclient[i]);
clientset.erase(it);
}
}
}
}
}
//事件选择模型
int Iomode::WSeventSelectmode()
{
/* 在WSAEventSelect模型中,基本流程如下:
1. 创建一个事件对象数组,用于存放所有的事件对象;
2. 创建一个事件对象(WSACreateEvent);
3. 将一组你感兴趣的SOCKET事件与事件对象关联(WSAEventSelect),然后加入事件对象数组;
4. 等待事件对象数组上发生一个你感兴趣的网络事件(WSAWaitForMultipleEvents);
5. 对发生事件的事件对象查询具体发生的事件类型(WSAEnumNetworkEvents);
6. 针对不同的事件类型进行不同的处理;
7. 循环进行 .4*/
//事件数组
WSAEVENT eventarray[WSA_MAXIMUM_WAIT_EVENTS], newevent;
//客户机套接字
SOCKET socketarray[WSA_MAXIMUM_WAIT_EVENTS];
//套接字,地址
map<SOCKET, sockaddr_in> s2addr;
//事件总数,这个事件总数也就是套接字与事件数组的长度,index返回通知事件的索引
DWORD eventtotal = 0, index;
//接收数据字符数组
char buffer[1024] = { 0 };
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//创建一个事件
newevent = WSACreateEvent();
//绑定
bind(server, (sockaddr*)&serveraddr,sizeof(serveraddr));
//接听
listen(server,5);
//为服务器套接字添加接收连接的网络事件,将服务器套接字与参数二事件关联
//一旦服务器套接字上发生网络事件,参数二的事件会通知我们
WSAEventSelect(server,newevent,FD_ACCEPT);
//在相应的套接字数组与事件数组中添加服务器套接字
socketarray[eventtotal] = server;
eventarray[eventtotal] = newevent;
eventtotal++;
while (1)
{
//需要等待事件的通知,检查是否有网络事件发生,返回的index就是发生时间的套接字的下标
//WSA_INFINITE永远等待
index = WSAWaitForMultipleEvents(eventtotal,eventarray,false,WSA_INFINITE,FALSE);
//规范套接字的下标,获取函数返回事件对象的索引
index = index - WSA_WAIT_EVENT_0;
//创建网络事件的结构体,用于检查发生了哪些网络事件
_WSANETWORKEVENTS networkevents;
//检查参数1套接字对应参数2事件对应的网络事件,在参数三中保存
WSAEnumNetworkEvents(socketarray[index], eventarray[index],&networkevents);
//接收
int a = networkevents.lNetworkEvents;
int b = FD_ACCEPT;
int aa = networkevents.lNetworkEvents & FD_ACCEPT;
if (networkevents.lNetworkEvents &FD_ACCEPT)
{
//使用错误码判断是否发生错误
if (networkevents.iErrorCode[FD_ACCEPT_BIT] !=0)
{
std::cout << "FD_ACCEPT failed!" <<endl;
break;
}
//接收客户机连接
sockaddr_in addr;
int len = sizeof(addr);
SOCKET client = accept(socketarray[index], (sockaddr*)&addr, &len);
//判断是否超出最大事件数
if (eventtotal>= WSA_MAXIMUM_WAIT_EVENTS)
{
std::cout << "超过最大数目" << endl;
break;
}
//记录套字节地址
s2addr[client] = addr;
//在这里继续使用ws事件将桃子姐和新事件关联,检查网络事件的读取和关闭操作
//创建一个事件
newevent = WSACreateEvent();
//为服务器套接字添加接收连接的网络事件,将服务器套接字与参数二事件关联
//一旦服务器套接字上发生网络事件,参数二的事件会通知我们
WSAEventSelect(client, newevent, FD_READ | FD_CLOSE);
//相应套字节中加入
//在相应的套接字数组与事件数组中添加服务器套接字
socketarray[eventtotal] = client;
eventarray[eventtotal] = newevent;
eventtotal++;
//打印连接信息
std::cout << inet_ntoa(addr.sin_addr) << client << "connected....." << endl;
WSAResetEvent(eventarray[index]);
}
//接收
if (networkevents.lNetworkEvents & FD_READ)
{
//使用错误码判断是否发生错误
if (networkevents.iErrorCode[FD_READ_BIT] != 0)
{
std::cout << "FD_READ failed!" << endl;
break;
}
//读数据
//为了防止溢出,设置成1023
recv(socketarray[index - WSA_WAIT_EVENT_0], buffer, 1023, 0);
//判断客户机是否关闭连接
string recvdata = buffer;
if (recvdata == "quit")
{
std::cout << inet_ntoa(s2addr[socketarray[index - WSA_WAIT_EVENT_0]].sin_addr)
<< socketarray[index - WSA_WAIT_EVENT_0] << "disconnected....." << endl;
//z在这里关闭套字节
closesocket(socketarray[index - WSA_WAIT_EVENT_0]);
}
else
std::cout << inet_ntoa(s2addr[socketarray[index - WSA_WAIT_EVENT_0]].sin_addr)
<< socketarray[index - WSA_WAIT_EVENT_0] << ":" <<recvdata<< endl;
WSAResetEvent(eventarray[index]);
}
//接收
if (networkevents.lNetworkEvents & FD_CLOSE)
{
//使用错误码判断是否发生错误
int num = networkevents.iErrorCode[FD_CLOSE_BIT];
if (networkevents.iErrorCode[FD_CLOSE_BIT] != 0)
{
std::cout << "FD_CLOSE failed!" << endl;
break;
}
//从socketarray中删除
for (int j =index;j<eventtotal;j++)
{
socketarray[j] = socketarray[j+1];
eventarray[j] = eventarray[j+1];
}
eventtotal--;
WSAResetEvent(eventarray[index]);
}
}
return 0;
}
//重叠模型 事件通知模型
int Iomode::overlappedmode()
{
//1.listen
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//绑定
bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
//接听
listen(server, 5);
//2.accpet
//接收客户机连接
sockaddr_in addr;
int len = sizeof(addr);
SOCKET client = accept(server, (sockaddr*)&addr, &len);
//3.创建重叠结构体,ws事件结构体
WSAOVERLAPPED overlapped;
//初始化
ZeroMemory(&overlapped, sizeof(overlapped));
WSAEVENT eventarray[WSA_MAXIMUM_WAIT_EVENTS];
DWORD eventtotal = 0,recvbytes=0,flags=0;
eventarray[eventtotal] = WSACreateEvent();
overlapped.hEvent = eventarray[eventtotal];
eventtotal++;
//4.使用重叠io,wsarecv
while (1)
{
WSABUF wsbuf = { 0 };
char buffer[1024] = { 0 };
wsbuf.len = 1024;
wsbuf.buf = buffer;
WSARecv(client, &wsbuf, 1, &recvbytes, &flags, &overlapped, NULL);
//5.等待重叠io操作完成,WSAWaitForMultipleEvents
//需要等待事件的通知,检查是否有网络事件发生,返回的index就是发生时间的套接字的下标
//WSA_INFINITE永远等待
int index = WSAWaitForMultipleEvents(eventtotal, eventarray, false, WSA_INFINITE, FALSE);
//6.重设事件对象
WSAResetEvent(eventarray[index - WSA_WAIT_EVENT_0]);
//7.wsagetoverlappedresult,重叠io是否成功完成,重复4-7
WSAGetOverlappedResult(client, &overlapped, &recvbytes, TRUE, &flags);
if (recvbytes == 0)
{
closesocket(client);
WSACloseEvent(eventarray[index - WSA_WAIT_EVENT_0]);
}
cout << wsbuf.buf << endl;
ZeroMemory(&overlapped, sizeof(overlapped));
overlapped.hEvent = eventarray[index - WSA_WAIT_EVENT_0];
//恢复信息
flags = 0;
}
return 0;
}
//定义成静态变量
SOCKET Iomode::clientInstance;
WSABUF Iomode::wsbuffer = { 0 };
void CALLBACK WorkerRoutine(DWORD error, DWORD bytes, LPWSAOVERLAPPED overlapped, DWORD flags)
{
if (error != 0 || bytes == 0)
{
closesocket(Iomode::clientInstance);
exit(0);
}
cout << Iomode::wsbuffer.buf << endl;
ZeroMemory(&overlapped, sizeof(overlapped));
};
//重叠模型 事件通知例程模型
int Iomode::overlappedInstanceMode()
{
//1.listen
SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//绑定
bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
//接听
listen(server, 5);
//2.accpet
//接收客户机连接
sockaddr_in addr;
int len = sizeof(addr);
clientInstance = accept(server, (sockaddr*)&addr, &len);
//3.创建重叠结构体,ws事件结构体
WSAOVERLAPPED overlapped;
//初始化
ZeroMemory(&overlapped, sizeof(overlapped));
WSAEVENT eventarray[WSA_MAXIMUM_WAIT_EVENTS];
DWORD eventtotal = 0, recvbytes = 0, flags = 0;
eventarray[eventtotal] = WSACreateEvent();
eventtotal++;
char buffer[1024] = { 0 };
wsbuffer.len = 1024;
wsbuffer.buf = buffer;
WSARecv(clientInstance, &wsbuffer, 1, &recvbytes, &flags, &overlapped, WorkerRoutine);
//4.使用重叠io,wsarecv
while (1)
{
//5.等待重叠io操作完成,WSAWaitForMultipleEvents
//需要等待事件的通知,检查是否有网络事件发生,返回的index就是发生时间的套接字的下标
//WSA_INFINITE永远等待
//最后一个标志位要true,例程的时候
int index = WSAWaitForMultipleEvents(eventtotal, eventarray, FALSE, WSA_INFINITE, TRUE);
//6.重设事件对象
WSAResetEvent(eventarray[0]);
wsbuffer.len = 1024;
wsbuffer.buf = buffer;
WSARecv(clientInstance, &wsbuffer, 1, &recvbytes, &flags, &overlapped, WorkerRoutine);
}
WSACleanup();
return 0;
}
CRITICAL_SECTION Iomode::cs;
DWORD WINAPI WorkerThreadData(LPVOID completionPortID)
{
int result = 0;
DWORD recvbytes = 0, flags = 0;
PER_HANDLE_DATA* perhandleData = NULL;
PER_IO_DATA* perIoData = NULL;
while (1)
{
//等待与完成端口关联的任意套接字上的I/O完成
result = GetQueuedCompletionStatus(completionPortID,&recvbytes,(PULONG_PTR)&perhandleData,(OVERLAPPED**)&perIoData,INFINITE);
//检查看是否有错误或者断开连接
if (recvbytes == 0 && perIoData->OperationType == RECV_POSTED)
{
EnterCriticalSection(&Iomode::cs);
cout << perIoData->client << "disconnectd...." << endl;
LeaveCriticalSection(&Iomode::cs);
closesocket(perIoData->client);
GlobalFree(perIoData);
perIoData = NULL;
continue;
}
//打印一下
if (perIoData->OperationType == RECV_POSTED)
{
EnterCriticalSection(&Iomode::cs);
cout << perIoData->client << ":" << perIoData->wsbuf.buf <<endl;
LeaveCriticalSection(&Iomode::cs);
}
WSARecv(perIoData->client,&(perIoData->wsbuf),1,&recvbytes, &flags, &(perIoData->overlapped), NULL);
}
return 0;
}
//重叠模型 IOCP模型
void Iomode::completionportmode()
{
int result = 0;
HANDLE completionPort;
SYSTEM_INFO systeminfo;
WSABUF wsbuf;
char buffer[1024] = { 0 };
wsbuf.len = 1024;
wsbuf.buf = buffer;
DWORD recvbytes = 0, flags = 0;
//接收客户机连接
sockaddr_in addr;
int len = sizeof(addr);
SOCKET server, client;
server = WSASocket(AF_INET, SOCK_STREAM, 0,NULL,0,WSA_FLAG_OVERLAPPED);
result = bind(server, (sockaddr*)&serveraddr, sizeof(serveraddr));
result = listen(server, SOMAXCONN);
GetSystemInfo(&systeminfo);
//完成端口的创建
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
InitializeCriticalSection(&cs);
//创建2*cpu核心数量用于处理网络IO
for (size_t i = 0; i < 2*systeminfo.dwNumberOfProcessors; i++)
{
HANDLE workerThreadHandle;
workerThreadHandle = CreateThread(NULL,0, &WorkerThreadData,completionPort,0,NULL);
CloseHandle(workerThreadHandle);
}
//主线程用死循环一直接收客户机连接
while (1)
{
//接收连接并且绑定到完成端口
//创建PER_HANDLE_DATA用于关联
PER_HANDLE_DATA* perhandleData = (PER_HANDLE_DATA*)GlobalAlloc(GPTR,sizeof(PER_HANDLE_DATA));
PER_IO_DATA *perIoData = (PER_IO_DATA*)GlobalAlloc(GPTR, sizeof(PER_IO_DATA));
client = accept(server, (sockaddr*)&addr, &len);
EnterCriticalSection(&cs);
cout <<client << "connectd...." << endl;
LeaveCriticalSection(&cs);
perhandleData->clientaddr = addr;
perhandleData->socket = client;
//关联客户机套接字和完成端口
CreateIoCompletionPort((HANDLE)client, completionPort, (DWORD)perhandleData, 0);
perIoData->client = client;
perIoData->wsbuf.len = 1024;
perIoData->wsbuf.buf = perIoData->buffer;
perIoData->OperationType = RECV_POSTED;
//投递一个WSARecv
int nRecv = WSARecv(perIoData->client, &perIoData->wsbuf,1,&recvbytes,&flags,&(perIoData->overlapped),NULL);
if ((SOCKET_ERROR == nRecv)&&(WSA_IO_PENDING != WSAGetLastError()))
{
EnterCriticalSection(&cs);
cout << "投递第一个wsarecv失败"<<endl;
exit(0);
LeaveCriticalSection(&cs);
}
}
}
//服务端不断发送消息
int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
/TCP传输
//客户机三个步骤:socket\connect\send\
//tcp传输
//ipv4和tcp协议
//创建套接字
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//绑定信息,需要地址信息
sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
//htons的作用是
serveraddr.sin_port = htons(12345);
serveraddr.sin_addr.s_addr = inet_addr("192.168.43.147");
err = connect(client, (sockaddr*)&serveraddr, sizeof(serveraddr));
while (1)
{
//接受客户机的消息
char data[1024] = { 0 };
std::cout << "输入数据或者quit退出" << std::endl;
cin >> data;
string sendData = data;
if (sendData == "quit") break;
send(client, sendData.c_str(), 1023, 0);
}
return 0;
}