NoteDeep

网络头文件、网络库:

打开网络库:

校验版本:

创建socket:

绑定地址端口:

开始监听:

select:

逻辑
1.每个客户端都有socket, 服务器也有自己的socket, 都放入一个数组中
2.通过select函数, 遍历所有socket, 将有响应的socket反馈回来
3.相应的处理:
服务器socket:有客户端连接, 调用accept
客户端socket:客户端请求通信, recv或send

select
第一步:定义一个装客户端socket结构:fd_set
fd_set clientsockets;

fd_set结构体
#ifndef FD_SETSIZE
#define FD_SETSIZE 64
#endif /* FD_SETSIZE */

typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
默认是64个socket, 在包含头文件winsock2.h之前可以先定义FD_SETSIZE改变数量
过多效率会低, select模型小用户量访问, 简单方便

四个操作fd_set的参数宏:
FD_ZERO() :将集合清零--将fd_count清零
FD_SET() :向集合中添加一个元素socket
FD_CLR() :在集合中删除指定的socket, socket还需要手动释放closesocket()
FD_ISSET() :判断一个socket是否在集合中

FD_ZERO(&clientsockets);
FD_SET(sockServer, &clientsockets);
FD_CLR(sockServer, &clientsockets);
int x = FD_ISSET(sockServer, &clientsockets);

第二步
作用:socket集合, 如果某个socket发生时间(连接或收发数据), 通过返回值以及参数告诉我们
select函数
int WSAAPI select(
int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
const timeval *timeout
);

参数
1.ignored不理会, 一般填0, 为了兼容
2.socket集合, 检查是否recv、accept。初始化为所有的socket, 函数调用完成后将会赋值为有请求的socket / NULL
3.是否有可写的socket, send(一直有反应, 只要连接上随时可以send)。初始化为所有的socket, 函数调用完成后将会赋值为可以send的socket / NULL
4.检查套接字上的异常错误, 初始化为所有的socket, 函数调用完成后将会赋值为有异常的socket / NULL
获取socket异常:
int WSAAPI getsockopt(
SOCKET s, // socket
int level, // SOL_SOCKET
int optname, // SO_ERROR
char *optval, // 存储空间, 放错误码
int *optlen // 存储空间的长度
);
5.最大等待时间
typedef struct timeval {
long tv_sec;
long tv_usec;
} TIMEVAL, *PTIMEVAL, *LPTIMEVAL;
秒、微秒
0, 0:非阻塞状态, 立即返回
NULL:完全阻塞, 直到客户端有反应才继续
3, 4:在无客户端无相应的情况下等待3秒4微秒

返回值
0:客户端在等待时间内没有反应
>0:有客户端请求交流
SOCKET_ERROR:WSAGetLastError()

fd_set allsockets;
FD_ZERO(&allsockets);
FD_SET(sockServer, &allsockets);

while (1)
{
fd_set readpsockets = allsockets;
fd_set writesockets = allsockets;
FD_CLR(sockServer, &writesockets);
// 可以删除服务器socket, 也可以不删
fd_set errorsockets = allsockets;

// 时间段
timeval ti;
ti.tv_sec = 0;
ti.tv_usec = 0;

int nRes = select(0, &readpsockets, &writesockets, &errorsockets, &ti);

if (0 == nRes)
{
// 没有响应的socket
continue;
}
else if (nRes > 0)
{
// 第四个参数
for (u_int i = 0; i < errorsockets.fd_count; i++)
{
char buf[100] = { 0 };
int len = 99;
int res = getsockopt(errorsockets.fd_array[i], SOL_SOCKET, SO_ERROR, buf, &len);

if (SOCKET_ERROR == res)
{
int errorcode = WSAGetLastError();
printf("无法获取错误信息.\n");
}
}

// 第三个参数
for (u_int i = 0; i < writesockets.fd_count; i++)
{
// 可写的socket没有服务器本身
if (writesockets.fd_array[i] == sockServer)
{
printf("find sockserver!\n");
}
else
{
int res = send(writesockets.fd_array[i], "ok", strlen("ok"), 0);
if (SOCKET_ERROR == res)
{
// 向下线的客户端发送消息也会返回SOCKET_ERROR
int errorcode = WSAGetLastError();
}

}
}

// 第二个参数
for (u_int i = 0; i < readpsockets.fd_count; i++)
{
if (readpsockets.fd_array[i] == sockServer)
{
// accept
SOCKET socketClient = accept(sockServer, NULL, NULL);
if (INVALID_SOCKET == socketClient)
{
// 连接出错
continue;
}
// 将新的socket放入socket集合
FD_SET(socketClient, &allsockets);
printf("newclient accept.\n");
}
else
{
char strBuf[1500] = { 0 };
// 客户端发来消息
int nRecv = recv(readpsockets.fd_array[i], strBuf, 1500, 0);
if (SOCKET_ERROR == nRecv)
{
int errorcode = WSAGetLastError();
// 客户端强制下线错误码:10054
switch (errorcode)
{
case 10054:
FD_CLR(readpsockets.fd_array[i], &allsockets);
closesocket(readpsockets.fd_array[i]);
printf("client force exit.\n");
break;
default:
printf("other errorcode.\n");
break;
}

}
else if (nRecv == 0)
{
// 客户端正常下线
// 1.从集合中去掉 2.释放socket
//SOCKET socketTemp = tempsockets.fd_array[i]
FD_CLR(readpsockets.fd_array[i], &allsockets);
closesocket(readpsockets.fd_array[i]);
printf("client quit.\n");
}
else
{
// 接收数据
printf("Recv:%s\n", strBuf);
}
}
}
}
else
{
int errorcode = WSAGetLastError();
// 可以自定义退出条件
}
}
// 释放所有socket, 关闭网络库
for (u_int i = 0; i < allsockets.fd_count; i++)
{
closesocket(allsockets.fd_array[i]);
}

WSACleanup();

窗口关闭(控制台关闭事件处理):

让操作系统在指定时间发生的时候调用一个函数
主窗口向系统投递一个监视:
SetConsoleCtrlHandler(fun, TRUE, )

参数
BOOL WINAPI HandlerRoutine(
_In_ DWORD dwCtrlType
);

fd_set allsockets; // 将socket集合作为全局变量
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType)
{
switch (dwCtrlType)
{
case CTRL_CLOSE_EVENT: // 退出
for (u_int i = 0; i < allsockets.fd_count; i++)
{
closesocket(allsockets.fd_array[i]);
}

WSACleanup();
break;
}
}

int main()
{
SetConsoleCtrlHandler(HandlerRoutine, TRUE);










评论列表

    网络头文件、网络库:
    打开网络库:
    校验版本:
    创建socket:
    绑定地址端口:
    开始监听:
    select:
    窗口关闭(控制台关闭事件处理):