服务端:
1.打开网络库
int WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
2.校验版本号
2 != HIBYTE(wsaData.wVersion) || 2 != LOBYTE(wsaData.wVersion) // (检验版本号是 2.2)
3.创建socket 套接字
SOCKET socket(int af, int type, int protocol);
?4.绑定地址与端口号
int bind(SOCKET s, const sockaddr* addr, int namelen);
?5.进行监听
int WSAAPI listen(SOCKET s, int backlog);
6.事件选择模型开始
第一步:创建事件对象:
WSAEVENT WSAAPI WSACreateEvent();
第二步:绑定并投递
int WSAAPI WSAEventSelect
(
SOCKET s,
WSAEVENT hEventObject,
long lNetworkEvents
);
第三步:创建结构体用于装所有的socket和对应的事件对象,并记录对应的个数
struct fd_es_set
{
SOCKET allsocks[64]; // 装所有的socket
WSAEVENT allevents[64]; // 装所有的事件
int count; // 记录个数
};
第四步:等待事件
DWORD WSAAPI WSAWaitForMultipleEvents(
DWORD cEvents, //事件,socket的个数
const WSAEVENT *lphEvents, //事件数组的首地址
BOOL fWaitAll, //直接填FALSE
DWORD dwTimeout, //可以填WSA_INFINITE,表示一直等待事件的发生
BOOL fAlertable //直接填FALSE
);
??????? 如果WSAWaitForMultipleEvents()函数中的参数4 填具体的等待时间的话,要进行判断是否超时操作:
判断是否超时
if (WSA_WAIT_TIMEOUT == retWaitEv)
{
// 在规定的时间每没有等待到事件发生 直接继续进行等待
continue;
}
第五步:列举事件
int WSAAPI WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
);
第六步:分类处理 FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
// 处理 FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
if (lpNetworkEvents->lNetworkEvents & FD_ACCEPT)
{
if (lpNetworkEvents->iErrorCode[FD_ACCEPT_BIT] == 0)
{
}
}
代码如下:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
/*
事件选择模型步骤:
第一步:创建一个事件对象
WSACreateEvent();
第二步:将socket 和 对应的事件绑定在一起 并投递给系统,由系统帮我们监视着
WSAEventSelect();
第三步:创建一个结构体,用来装所有的socket和对应的事件,每创建一个socket和对应的事件都装进结构体对应的数组中
struct fd_eventsocket_set{SOCKET allsock[64]; WSAEVENT allevent[64]; int count;};
注意每装一次 对应的结构体的变量 count就要+1
第四步:查看事件是否有信号
WSAWaitForMultipleEvents();
第五步:有信号的话进行分类处理
WSAEnumNetworkEvents();
第六步:分类处理 FD_ACCEPT FD_WRITE FD_READ FD_CLOSE
*/
// 6.3、创建结构体用来装所有的socket和对应的事件
struct fd_es_set
{
SOCKET allsocks[64]; // 装所有的socket
WSAEVENT allevents[64]; // 装所有的事件
int count; // 记录个数
};
// 定义结构体对象
struct fd_es_set esSet = { {0}, {NULL}, 0 };
// 声明WSAStartup 函数中参数2所需的结构体
WSADATA wsadata;
// 监视窗口的回调函数
BOOL WINAPI fun(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_CLOSE_EVENT:
{
// 关闭套接字 和对应的事件
for (int i = 0; i < esSet.count; i++)
{
closesocket(esSet.allsocks[i]);
WSACloseEvent(esSet.allevents[i]);
}
// 清理网络库
WSACleanup();
}
break;
}
return TRUE;
}
// 1.打开网络库
void OpenWSAStarup()
{
WORD version = MAKEWORD(2, 2); // 设置版本号为 2.2
int retStartup = WSAStartup(version, &wsadata);
// 检验是否成功打开网络库
if (0 != retStartup)
{
// 出错了
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("打开网络库失败,错误码为: %d\n", error);
return; // 退出
}
}
// 2.检验版本号
void CheckVersion()
{
if (2 != HIBYTE(wsadata.wVersion) || 2 != LOBYTE(wsadata.wVersion))
{
// 出错了
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("检验版本号出错,错误码为: %d\n", error);
// 清理网络库
WSACleanup();
return; // 退出
}
}
// 3.创建socket 服务端的socket
SOCKET createSocket()
{
SOCKET socket_Server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 检验是否成功创建socket
if (INVALID_SOCKET == socket_Server)
{
// 出错了
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("创建socket错误,错误码为: %d\n", error);
// 清理网络库
WSACleanup();
return 0; // 退出
}
return socket_Server;
}
// 4.绑定地址与端口号
void Bind(SOCKET socket_Server)
{
// 声明bind() 函数所需的参数2 的结构体
struct sockaddr_in serverMsg;
// 给结构体成员初始化
serverMsg.sin_family = AF_INET; // ip 地址的类型
serverMsg.sin_port = htons(12345); // 端口号
serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地ip
int retBind = bind(socket_Server, (struct sockaddr*)&serverMsg, sizeof(serverMsg));
// 检验是否成功绑定ip和端口号
if (SOCKET_ERROR == retBind)
{
// 出错了
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("绑定ip和端口号失败,错误码为: %d\n", error);
// 关闭套接字
closesocket(socket_Server);
// 清理网络库
WSACleanup();
return; // 退出
}
}
// 5.进行监听
void Listen(SOCKET socket_Server)
{
if (SOCKET_ERROR == listen(socket_Server, SOMAXCONN))
{
// 出错了
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("进行监听失败,错误码为: %d\n", error);
// 关闭套接字
closesocket(socket_Server);
// 清理网络库
WSACleanup();
return; // 退出
}
}
// 6.事件选择模型核心代码
void EventSelect(SOCKET socket_Server)
{
// 6.1、创建一个事件对象
WSAEVENT eventServer = WSACreateEvent();
// 检验是否成功创建事件对象
if (WSA_INVALID_EVENT == eventServer)
{
// 创建事件对象失败
// 出错了
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("WSACreateEvent() 创建事件对象失败,错误码为: %d\n", error);
// 关闭套接字
closesocket(socket_Server);
// 清理网络库
WSACleanup();
return; // 退出
}
// 6.2、绑定并投递
int retEtSel = WSAEventSelect(socket_Server, eventServer, FD_ACCEPT);
// 检验是否成功绑定并投递
if (SOCKET_ERROR == retEtSel)
{
// 绑定并投递失败
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("WSAEventSelect() 绑定并投递失败,错误码为: %d\n", error);
// 关闭套接字
closesocket(socket_Server);
// 关闭事件对象
WSACloseEvent(eventServer);
// 清理网络库
WSACleanup();
return; // 退出
}
6.3、创建结构体用来装所有的socket和对应的事件
//struct fd_es_set
//{
// SOCKET allsocks[64]; // 装所有的socket
// WSAEVENT allevents[64]; // 装所有的事件
// int count; // 记录个数
//};
定义结构体对象
//struct fd_es_set esSet = { {0}, {NULL}, 0 };
// 将socket装进数组中
esSet.allsocks[esSet.count] = socket_Server;
// 将对应的事件装进数组中
esSet.allevents[esSet.count] = eventServer;
// 记录个数++
esSet.count++;
// 循环等待事件 一直进行等待
while (1)
{
// 6.4、等待事件 参数1:事件、socket 的个数 参数2:事件数组的首地址 参数3:直接填FALSE 参数4:等待时间(WSA_INFINITE 等待直到事件发生) 参数5:直接填FALSE
DWORD retWaitEvn = WSAWaitForMultipleEvents(esSet.count, esSet.allevents, FALSE, WSA_INFINITE, FALSE);
// 检验是否等到事件发生
if (WSA_WAIT_FAILED == retWaitEvn)
{
// 没有等到事件的发生 进行继续等待
continue;
}
// 获取发生事件所在的数组下标
DWORD iIndex = retWaitEvn - WSA_WAIT_EVENT_0;
// 获取下标之后的操作
// 6.5、列举事件 获取事件类型,并将事件上的信号重置
// 声明 WSAEnumNetworkEvents()函数 中参数3所需的结构体
/*
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;
*/
WSANETWORKEVENTS NetworkEvents;
int retEumEv = WSAEnumNetworkEvents(esSet.allsocks[iIndex], esSet.allevents[iIndex], &NetworkEvents);
// 判断列举事件是否出错
if (SOCKET_ERROR == retEumEv)
{
// 列举事件出错
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("WSAEnumNetworkEvents() 列举事件出错,错误码为: %d\n", error);
continue;
}
// 6.6、分类处理 FD_ACCEPT FD_WRITE FD_READ FD_CLOSE
// 处理FD_ACCEPT
if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
{
if (0 == NetworkEvents.iErrorCode[FD_ACCEPT_BIT])
{
// accept进行链接 本质创建客户端socket
SOCKET socketClient = accept(esSet.allsocks[iIndex], NULL, NULL);
// 判断是否成功创建客户端socket
if (INVALID_SOCKET == socketClient)
{
// 创建客户端socket失败
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("处理FD_ACCEPT 创建客户端socket失败,错误码为: %d\n", error);
continue;
}
// 创建对应的事件对象
WSAEVENT eventClient = WSACreateEvent();
// 检验是否成功创建事件对象
if (WSA_INVALID_EVENT == eventClient)
{
// 创建对应的事件对象失败
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("创建对应的(client)事件对象失败,错误码为: %d\n", error);
// 关闭客户端socket
closesocket(socketClient);
continue;
}
// 将客户端socket和对应的事件对象绑定在一起并投递给系统
int retEventSel = WSAEventSelect(socketClient, eventClient, FD_ACCEPT | FD_READ | FD_WRITE | FD_CLOSE);
// 判断是否绑定并投递成功
if (SOCKET_ERROR == retEventSel)
{
// 绑定并投递失败
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("绑定并投递失败(client),错误码为: %d\n", error);
// 关闭客户端socket
closesocket(socketClient);
// 关闭事件对象
WSACloseEvent(eventClient);
continue;
}
// 将客户端socket和对应的事件对象装进到数组中
esSet.allsocks[esSet.count] = socketClient;
esSet.allevents[esSet.count] = eventClient;
esSet.count++; // 注意个数要++
// 可以打印提示一下成功处理 FD_ACCEPT
printf("accept success\n");
}
else
{
// 处理FD_ACCEPT 出错
printf("处理FD_ACCEPT出错 错误码为%d\n", NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
continue;
}
}
// 处理FD_WRITE
if (NetworkEvents.lNetworkEvents & FD_WRITE)
{
if (0 == NetworkEvents.iErrorCode[FD_WRITE_BIT])
{
// 进行 send
if (SOCKET_ERROR == send(esSet.allsocks[iIndex], "connection ok", sizeof("connection ok"), 0))
{
// 发送数据失败
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("处理FD_WRITE send 发送数据失败,错误码为: %d\n", error);
continue;
}
printf("write ok\n");
}
else
{
// 处理FD_WRITE 出错
printf("处理FD_WRITE出错 错误码为%d\n", NetworkEvents.iErrorCode[FD_WRITE_BIT]);
continue;
}
}
// 处理FD_READ
if (NetworkEvents.lNetworkEvents & FD_READ)
{
if (0 == NetworkEvents.iErrorCode[FD_READ_BIT])
{
// recv 接收信息
// 定义接收数据的缓存区
char recvBuf[1024] = { 0 };
int retRecv = recv(esSet.allsocks[iIndex], recvBuf, sizeof(recvBuf), 0);
// 判断是否成功接收到信息
if (SOCKET_ERROR == retRecv)
{
// 接收到信息
int error = WSAGetLastError(); // 通过WSAGetLastError 获取错误码
printf("处理FD_READ recv 接收到信息,错误码为: %d\n", error);
continue;
}
printf("客户端say: %s\n", recvBuf);
}
else
{
// 处理FD_READ_BIT 出错
printf("处理FD_READ出错 错误码为%d\n", NetworkEvents.iErrorCode[FD_READ_BIT]);
continue;
}
}
// 处理FD_CLOSE
if (NetworkEvents.lNetworkEvents & FD_CLOSE)
{
// 打印客户端下线了
printf("客户端下线了!!!\n");
// 出错了
// 先关闭对应的socket 在 删除,删除的时候可以将数组最后的一个元素覆盖掉要删除的元素
closesocket(esSet.allsocks[iIndex]);
esSet.allsocks[iIndex] = esSet.allsocks[esSet.count - 1];
// 先关闭对应的事件 在删除 同上
WSACloseEvent(esSet.allevents[iIndex]);
esSet.allevents[iIndex] = esSet.allevents[esSet.count - 1];
// 注意socket、对应的事件个数要--
esSet.count--;
}
}
}
int main()
{
// 监视关闭窗口按钮
SetConsoleCtrlHandler(fun, TRUE);
// 1.打开网络库
OpenWSAStarup();
// 2.检验版本号
CheckVersion();
// 3.创建socket 服务端的socket
SOCKET socketServer = createSocket();
// 4.绑定地址与端口号
Bind(socketServer);
// 5.进行监听
Listen(socketServer);
// 6.事件选择模型核心代码
EventSelect(socketServer);
// 关闭套接字 和对应的事件
for (int i = 0; i < esSet.count; i++)
{
closesocket(esSet.allsocks[i]);
WSACloseEvent(esSet.allevents[i]);
}
// 清理网络库
WSACleanup();
system("pause");
return 0;
}
客户端代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Winsock.h>
#pragma comment(lib,"ws2_32.lib")
WSADATA WSAData;
// 1,打开网络库
void OpenWSAStarup()
{
// 1,打开网络库
WORD version = MAKEWORD(2, 2); //设置要使用的库的版本 MAKEWORD(主版本,副版本);
int n = WSAStartup(version, &WSAData);
// 检验是否成功打开网络库
if (0 != n)
{
// 打开网络库失败,通过n 返回的错误码,给出提示
switch (n)
{
case WSASYSNOTREADY:
printf("重启电脑试试,或者检查ws_2_32库是否存在\n");
break;
case WSAVERNOTSUPPORTED:
printf("当前库版本号不支持,尝试更换版本试试\n");
break;
case WSAEPROCLIM:
printf("已达到对Windows套接字实现支持的任务数量的限制\n");
break;
case WSAEINPROGRESS:
printf("正在阻止Windows Sockets 1.1操作,当前函数运行期间,因为某些原因造成阻塞\n");
break;
case WSAEFAULT:
printf("lpWASData参数不是有效的指针,参数写错了\n");
break;
default:
break;
}
}
}
// 2.检验版本
void CheckVersion()
{
if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
{
// 打开版本号 2.2 失败
printf("打开版本号 2.2 失败, 正在退出程序...\n");
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 3.创建socket
SOCKET CreateSocket()
{
// 3.创建套接字 socket 创建服务端的socket
SOCKET socketCreate = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 检验是否成功创建套接字
if (INVALID_SOCKET == socketCreate)
{
// 创建套接字失败
// 获取错误码
int error = WSAGetLastError();
printf("创建套接字失败,返回错误号为:%d\n", error);
// 关闭网络库
WSACleanup();
// 退出程序
return 0;
}
return socketCreate;
}
// 4.连接服务器
void Connection(SOCKET socket_)
{
// 4.连接到服务器
// 创建connect() 函数中参数2所需的结构体 用来装IP地址和端口号
sockaddr_in client_s;
//给结构体成员赋值
client_s.sin_family = AF_INET; //IP地址类型
client_s.sin_port = htons(12345); //端口号
client_s.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int nCon = connect(socket_, (struct sockaddr*)&client_s, sizeof(client_s));
// 检验是否成功连接到服务端
if (SOCKET_ERROR == nCon)
{
// 连接服务端失败
// 获取错误码
int error = WSAGetLastError();
printf("连接服务端失败,返回错误号为:%d\n", error);
// 关闭套接字
closesocket(socket_);
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 5.与服务端发送信息
void RecvAndSend(SOCKET socket_)
{
// 只收一次 信息
// 定义接收服务端信息的缓存区
char recvBuf[1024] = { 0 };
int res = recv(socket_, recvBuf, 1024, 0);
// 判断是否成功接收到信息
if (0 == res)
{
printf("服务端下线了\n");
return;
}
else if (SOCKET_ERROR == res)
{
// 获取错误码
int error = WSAGetLastError();
printf("连接服务端失败,返回错误号为:%d", error);
printf("\n");
return;
}
else
{
// 打印信息
printf("服务端say: %s\n", recvBuf);
}
printf("我是客户端,正在发送信息,请稍等...\n");
// 5.与服务端进行收发信息
while (1)
{
// 发信息
char sendBuf[1024] = { 0 };
scanf_s("%s", sendBuf, 1024);
// 设置输入 0 时自动退出
if ('0' == sendBuf[0])
{
break; //退出循环
}
send(socket_, sendBuf, 1024, 0);
}
}
int main()
{
// 1,打开网络库
OpenWSAStarup();
// 2.校验版本
CheckVersion();
// 3.创建socket
SOCKET socketServer = CreateSocket();
// 4.连接服务器
Connection(socketServer);
// 5.与服务端发送信息
RecvAndSend(socketServer);
// 关闭套接字
closesocket(socketServer);
// 关闭网络库
WSACleanup();
system("pause");
return 0;
}
程序运行结果:
|