《Windows网络编程》_罗莉琴版_8.3节_基于Select模型的Socket编程_代码

《Windows网络编程》_罗莉琴版_8.3节_基于Select模型的Socket编程_代码

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) 第一时间获取最新文章

%title插图%num

发表评论