《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) 第一时间获取最新文章
发表评论