《Windows网络编程》_罗莉琴版_8.3节_基于Select模型的Socket编程_代码
——独立观察员 2014.04.26
说明:
这是罗莉琴等人编著的《Windows网络编程》(《Windows Network Programming》)一书2011.04版(我们网络编程的课本)中,第8.3小节——”基于Select模型的Socket编程”中的代码。
配套的代码中没有这个,课件(PPT)中有前面一小部分代码,网上也没找到这个,为了做作业,只好手敲了。手打得挺辛苦,为了大家不再辛苦,所以发上来。另外,有些小地方做了改动,还有难免可能出错,所以建议对照书本学习。
以下就是代码:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#include <tchar.h>
#define PORT 8888
#define DATA_BUFSIZE 1024
/*
Select()函数原型:
int select(
int nfds, //会被忽略;
fd_set * readfds, //用于检测可读性的Socket集合;
fd_set * writefds, //可写性的;
fd_set * exceptfds, //错误的;
const struct timeval* timeout //等待的时间,设为null则是阻塞模式;
);
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
FD_CLR(s, *set) 从集合中删除指定的套接字;
FD_ISSET(s, *set) 如果参数s是集合中的成员,则返回非0值,否则返回0;
FD_SET(s, *set) 向集合中添加套接字;
FD_ZERO(s, *set) 将集合初始化为空集合;
*/
/*
《Windows网络编程》 8.3 基于Select模型的Socket编程;
独立观察员倾情手打 20140423
*/
typedef struct _SOCKET_INFORMATION{
CHAR Buffer[DATA_BUFSIZE];// 发送和接收数据的缓冲区
WSABUF DataBuf; // 定义发送和接收数据缓冲区的结构体,包括缓冲区的长度和内容
SOCKET Socket; // 与客户端进行通信的套接字
DWORD BytesSEND; // 保存套接字发送的字节数
DWORD BytesRECV; // 保存套接字接收的字节数
}SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
/* winsock2.h中定义的结构体;
typedef struct _WSABUF {
u_long len; // the length of the buffer
char FAR * buf; // the pointer to the buffer
} WSABUF, FAR * LPWSABUF;
*/
//记录正在使用的套接字总数量;
DWORD TotalSockets = 0;
//保存Socket信息对象的数组(Socket集合),FD_SETSIZE表示SELECT模型中允许的最大Socket数量(64);
LPSOCKET_INFORMATION SocketArray[FD_SETSIZE];
SOCKET ListenSocket; // 监听套接字
SOCKET AcceptSocket; // 与客户端进行通信的套接字
SOCKADDR_IN InternetAddr; // 服务器的地址
WSADATA wsaData; // 用于初始化套接字环境
INT Ret; // WinSock API的返回值
FD_SET WriteSet; // 获取可写性的套接字集合
FD_SET ReadSet; // 获取可读性的套接字集合
DWORD Total = 0; // 处于就绪状态的套接字数量
DWORD SendBytes; // 发送的字节数
DWORD RecvBytes; // 接收的字节数
BOOL CreateSocketInformation(SOCKET s)
{
LPSOCKET_INFORMATION SI; // 用于保存套接字的信息
// 为SI分配内存空间
if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
return FALSE;
}
// 初始化SI的值
SI->Socket = s; //与客户端进行通信的套接字;
SI->BytesSEND = 0; //发送的字节数初始为0;
SI->BytesRECV = 0; //接收的字节数也为0;
// 在SocketArray数组中增加一个新元素,用于保存SI对象
SocketArray[TotalSockets] = SI;
TotalSockets++; // 增加套接字数量
return(TRUE);
}
void FreeSocketInformation(DWORD Index)
{
// 获取指定索引对应的LPSOCKET_INFORMATION对象
LPSOCKET_INFORMATION SI = SocketArray[Index];
closesocket(SI->Socket); // 关闭套接字
//printf("Closing socket number %d\n", SI->Socket);
// 释放指定LPSOCKET_INFORMATION对象资源
GlobalFree(SI);
// 将数组中index索引后面的元素前移
for (DWORD i = Index; i < TotalSockets; i++)
{
SocketArray[i] = SocketArray[i+1];
}
TotalSockets--; // 套接字总数减
}
int _tmain(int argc, _TCHAR* argv[])
{
//初始化WinSock环境;
if((Ret = WSAStartup(0x0202,&wsaData)) != 0)
{
printf("WSAStartup() failed with error %d\n", Ret);
WSACleanup();
return -1;
}
//创建用于监听的Socket,WSASocket是Windows专用,支持异步操作;
if((ListenSocket = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("WSAStartup() failed with error %d\n", WSAGetLastError());
return -1;
}
//设置监听地址和端口号;
InternetAddr.sin_family = AF_INET; //Address family
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); //Wild card IP address
InternetAddr.sin_port = htons(PORT); //port to use
//绑定监听Socket到本地地址和端口;
if(bind( ListenSocket, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr) ) == SOCKET_ERROR)
{
printf("bind() failed with error %d\n", WSAGetLastError());
return -1;
}
//开始监听;
if(listen(ListenSocket, 5)) //同时设置等待队列长度;
{
printf("listen() failed with error %d\n", WSAGetLastError());
return -1;
}
//设置为非阻塞模式;
ULONG NonBlock = 1;
if(ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
{
printf("ioctlsocket() failed with error %d\n", WSAGetLastError());
return -1;
}
//为ListenSocket Socket创建对应的SOCKET_INFORMATION
//这样就可以把ListenSocket添加到SocketArray数组中;
CreateSocketInformation(ListenSocket);
while(TRUE)
{
//准备用于网络I/O通知的读/写Socket集合
FD_ZERO(&ReadSet); //将集合初始化为空集合;
FD_ZERO(&WriteSet);
//向ReadSet集合添加监听Socket ListenSocket
FD_SET(ListenSocket, &ReadSet);
//将SocketArray数组中的所有Socket添加到WriteSet和ReadSet集合中
//SocketArray数组中保存着监听Socket和所有与客户端进行通信的Socket
//这样就可以使用Select()判断哪个Socket有接入数据或者读取/写入数据
for(DWORD i=0; i<TotalSockets; i++)
{
LPSOCKET_INFORMATION SocketInfo = SocketArray[i];
FD_SET(SocketInfo->Socket, &WriteSet);
FD_SET(SocketInfo->Socket, &ReadSet);
}
//判断读/写Socket集合中就绪的Socket
if((Total = select(0,&ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR)
{
printf("select() returned with error %d\n", WSAGetLastError());
return -1;
}
//依次处理所有的Socket。本服务器是一个回应服务器,即将从客户端收到的字符串再发回到客户端
for(DWORD j=0; j<TotalSockets; j++)
{
//SocketInfo为当前要处理的Socket信息
LPSOCKET_INFORMATION SocketInfo = SocketArray[i];
//判断当前Socket的可读性,即是否有接入的连接请求或者可以接收数据
if(FD_ISSET(SocketInfo->Socket, &ReadSet))
{
//对于监听Socket来说,可读表示有新的连接请求
if(SocketInfo->Socket == ListenSocket)
{
Total--; //就绪的Socket减1
//接受连接请求,得到与客户端进行通信的Socket AcceptSocket
if((AcceptSocket = accept(ListenSocket, NULL,NULL)) != INVALID_SOCKET)
{
//设置Socket AcceptSocket为非阻塞模式;
//这样服务器在调用WSASend()函数发送数据时就不会被阻塞
NonBlock = 1;
if(ioctlsocket(AcceptSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
{
printf("ioctlsocket() failed with error %d\n", WSAGetLastError());
return -1;
}
//创建套接字信息,初始化LPSOCKET_INFORMATION结构体数据,
//将AcceptSocket(也就是新的Socket)添加到SocketArray数组中
if(CreateSocketInformation(AcceptSocket) == FALSE)
return -1;
}
else //出错;
{
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("accept() failed with error %d\n", WSAGetLastError());
return -1;
}
}
}
else //对于其它Socket来说,表示可接收数据;
{
//如果当前Socket在ReadSet集合中,则表明该Socket上有可以读取的数据
if(FD_ISSET(SocketInfo->Socket, &ReadSet))
{
Total--; //减少一个处于就绪状态的套接字
memset(SocketInfo->Buffer, ' ', DATA_BUFSIZE); //初始化缓冲区
SocketInfo->DataBuf.buf = SocketInfo->Buffer; //初始化缓冲区位置
SocketInfo->DataBuf.len = DATA_BUFSIZE; //初始化缓冲区长度
//接收数据
DWORD Flags = 0;
if(WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes, &Flags, NULL, NULL) == SOCKET_ERROR)
{
//错误编码等于WSAEWOULDBLOCK表示暂时没有数据,否则表示出现异常
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
FreeSocketInformation(i); //释放Socket信息;
}
continue;
}
else //接收数据未出错
{
SocketInfo->BytesRECV = RecvBytes; //记录接收数据的字节数
if(RecvBytes == 0) //如果收到0个字节,则表示对方关闭连接
{
FreeSocketInformation(i); //释放Socket信息;
continue;
}
else //如果成功接收数据则打印收到的数据;
{
printf(SocketInfo->DataBuf.buf);
printf("\n");
}
}
}
}
}
else //不是落在ReadSet集合内
{
//如果当前Socket在WriteSet集合中,则表明该Socket的内部数据缓冲区有数据可以发送
if(FD_ISSET(SocketInfo->Socket, &WriteSet))
{
Total--; //就绪的Socket减1
//初始化缓冲区位置
SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;//因为是回显程序?
//初始化缓冲区长度
SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;
if(SocketInfo->DataBuf.len > 0)
{
if(WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0, NULL, NULL) == SOCKET_ERROR)
{
//错误编码等于WSAEWOULDBLOCK表示暂时没有数据,否则表示出现异常
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("WSASend() failed with error %d\n", WSAGetLastError());
FreeSocketInformation(i); //释放Socket信息;
}
continue;
}
else //发送数据未出错;
{
SocketInfo->BytesSEND += SendBytes; //记录发送数据的字节数
//如果从客户端接收到的数据都已经发回到客户端
//则将发送和接收的字节数量设置为0
if(SocketInfo->BytesSEND == SocketInfo->BytesRECV)
{
SocketInfo->BytesSEND = 0;
SocketInfo->BytesRECV = 0;
}
}
}
}
}
}//for语句结束
}//while
}
原创文章,转载请注明: 转载自 独立观察员(dlgcy.com)
本文链接地址: [《Windows网络编程》_罗莉琴版_8.3节_基于Select模型的Socket编程_代码](https://dlgcy.com/select-model/)
关注微信公众号 独立观察员博客(DLGCY_BLOG) 第一时间获取最新文章
发表评论