实验六:基于 WSAAsyncSelect模型的 Socket 编程

一、实验目标

1.掌握非阻塞模式编程方法;

2.掌握基于WSAAsyncSelect模型的高级编程技术。

二、实验内容

在已有服务器程序基础上,补全空缺关键代码,使之可以正常运行。该服务器代码实现功能:

1) 基于 WSAAsyncSelect模型;

2) 接收多客户端连接,显示客户端连接信息;

3) 显示客户端发送的信息;

4) 接收多客户端发送的信息后,将接收到的信息回发给对应的客户端;

三、代码

服务器端代码

#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <tchar.h>
#include <iostream>
#include <string>
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#pragma   comment(lib,"ws2_32.lib")


#define BACKLOG 10							// 指定等待连接队列的最大长度
#define MAXDATASIZE 100						// 指定数据缓冲区的最大长度

#define WM_SOCKET (WM_USER + 1)		        // 定义套接口网络事件设置用户消息值

// 保存套接字信息
typedef struct _SOCKET_INFORMATION {
   CHAR Buffer[MAXDATASIZE];				// 发送和接收数据的套接字
   WSABUF DataBuf;							// 定义数据的内容和长度
   SOCKET Socket;							// 套接字
   _SOCKET_INFORMATION *Next;				// 向一个套接字信息
} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;

// 创建套接字信息
void CreateSocketInformation(SOCKET s);
// 获取指定套接字的信息
LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);

char buf[MAXDATASIZE];						// 数据缓冲区
HWND MakeWorkerWindow(void);				// 创建接收事件的窗口
SOCKET Accept;								// 与客户端进行通信的套接字
LPSOCKET_INFORMATION SocketInfoList;		// 所有套接字信息的列表
// 用于处理事件消息的窗口例程
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// 主函数
int _tmain(int argc, _TCHAR* argv[])
{
	MSG msg;							// 用于获取消息
	DWORD Ret;							// GetMessage()函数的返回结果
	SOCKET sockfd;						// 套接字
	struct sockaddr_in my_addr;			// 绑定到套接字的本地地址	
	HWND Window;						// 用于接收事件消息的窗口句柄

	// 创建窗口
	if ((Window = MakeWorkerWindow()) == NULL)
		return 0;

	// 初始化Windows Sockets环境
	WSADATA ws;
	WSAStartup(MAKEWORD(2,2),&ws);
	//监听
	SOCKET sListen = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	// 创建套接字
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	// 定义使用Window窗口接收FD_ACCEPT事件消息
	WSAAsyncSelect(sockfd, Window, WM_SOCKET, FD_ACCEPT);
	// 设置地址和端口
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(1234);		
	my_addr.sin_addr.s_addr = INADDR_ANY;	
	// 将套接字sockaddr绑定到my_addr
	if(bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))==SOCKET_ERROR)
		return -1;


	// 监听指定的套接字
	listen(sockfd, BACKLOG);
	// 获取消息队列中获取一个消息
	while(Ret = GetMessage(&msg, NULL, 0, 0))
	{	
		if (Ret == -1)
		{
			printf("GetMessage() failed with error %d\n", GetLastError());
			return 0;
		}
		// 将虚拟键消息转换为字符串消息 
		TranslateMessage(&msg);
		// 将消息转发到窗口例程
		DispatchMessage(&msg);
	}
	return 0;
}

// 窗口例程
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{	
	LPSOCKET_INFORMATION SocketInfo;	// 套接字消息
	 SOCKADDR_IN clientAddr; 			// 客户端地址结构体
    int addrLen = sizeof(clientAddr); 	// 客户端地址长度
	// 处理用户自定义消息
	if (uMsg == WM_SOCKET)
	{
		if (WSAGETSELECTERROR(lParam))			// 判断套接字是否错误
		{
			printf("Socket failed with error %d\n", WSAGETSELECTERROR(lParam));
		} 
		else
		{
			switch(WSAGETSELECTEVENT(lParam))	// 判断消息类型
			{
				case FD_ACCEPT:					
					// 接收来自客户端的连接,Accept是与客户端进行通信的套接字
					if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET)
					{		
						printf("accept() failed with error %d\n", WSAGetLastError());
						break;
					}
					// 为Accept创建套接字信息
					CreateSocketInformation(Accept);
					// 为Accept套接字订阅FD_READ、FD_WRITE和FD_CLOSE消息
					WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);
					break;
				case FD_READ:			 										// 处理FD_READ消息
					SocketInfo = GetSocketInformation(wParam);					// 获取套接字信息
					int Rec = recv(SocketInfo->Socket, buf, MAXDATASIZE, 0);	// 接收数据
					buf[Rec] = '\0';
					if( Rec > 0)    // 打印接收到的字符串
                    {
                        // 获取客户端地址信息
                        if (getpeername(SocketInfo->Socket, (SOCKADDR*)&clientAddr, &addrLen) == SOCKET_ERROR)
                        {
                            printf("getpeername() failed with error %d\n", WSAGetLastError());
                        }
                        else
                        {
							std::cout << "received: " << buf  <<"   Received from IP:  "<< inet_ntoa(clientAddr.sin_addr) << "  port:  "<<ntohs(clientAddr.sin_port) << std::endl;
                            
                        }
                        
                        // 发送客户端地址信息回客户端
                         send(SocketInfo->Socket, buf, Rec, 0);
                    }
                    else if (Rec == 0)
                    {
                        // 客户端关闭连接
                        printf("Client disconnected.\n");
                    }
                    else
                    {
                        // 发生错误
                        printf("recv() failed with error %d\n", WSAGetLastError());
                    }
                    break;
			}
		}
		return 0;
	}
	// 如果不是用户自定义消息,则调用默认的窗口例程
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LPSOCKET_INFORMATION GetSocketInformation(SOCKET s)
{
   SOCKET_INFORMATION *SI = SocketInfoList;

   while(SI)
   {
      if (SI->Socket == s)
         return SI;

      SI = SI->Next;
   }

   return NULL;
}

客户端代码

#define WIN32_LEAN_AND_MEAN		
#include <stdio.h>
#include <tchar.h>
#include <string>
#include <winsock2.h>
#include <iostream>

#pragma comment(lib,"WS32_32.lib")


#define BUF_SIZE    64          // 缓冲区大小  

int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA     wsd;					// 用于初始化Windows Socket   
    SOCKET      sHost;					// 与服务器进行通信的套接字   
    SOCKADDR_IN servAddr;			// 服务器地址   
    char        buf[BUF_SIZE];			// 用于接受数据缓冲区   
    int         retVal;							// 调用各种Socket函数的返回值   
	// 初始化Windows Socket
    if(WSAStartup(MAKEWORD(2,2),&wsd) != 0)   
    {   
        printf("WSAStartup failed !\n");   
        return 1;   
    }     
    // 创建套接字   
    sHost = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);   
    if(INVALID_SOCKET == sHost)   
    {   
        printf("socket failed !\n");   
        WSACleanup();   
        return -1;   
    }    
	// 设置套接字为非阻塞模式
	int iMode = 1;
	retVal = ioctlsocket(sHost, FIONBIO, (u_long FAR*) &iMode);
	if(retVal == SOCKET_ERROR)
	{
		printf("ioctlsocket failed !\n");
		WSACleanup();
		return -1;
	}
    // 设置服务器地址   
    servAddr.sin_family = AF_INET;   
    servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");		// 用户需要根据实际情况修改
    servAddr.sin_port = htons(1234);													// 在实际应用中,建议将服务器的IP地址和端口号保存在配置文件中
    int sServerAddlen = sizeof(servAddr);												// 计算地址的长度       
	// 循环等待
	while(true)
	{
		// 连接服务器   
		Sleep( 200 );
		retVal = connect(sHost,(LPSOCKADDR)&servAddr,sizeof(servAddr));   
		Sleep( 200 );
		if(SOCKET_ERROR == retVal)   
		{   
			int err = WSAGetLastError();
			if(err == WSAEWOULDBLOCK || err == WSAEINVAL)			// 无法立即完成非阻塞套接字上的操作
			{
				//Sleep(500);
				continue;
			}
			else if(err == WSAEISCONN)												// 已建立连接
			{
				break;
			}
			else
			{
								continue;
			}
		}   
	}
	// 循环向服务器发送字符串,并显示反馈信息。
	// 发送quit将使服务器程序退出,同时客户端程序自身也将退出
	while(true)
	{
		// 向服务器发送数据   
		printf("Please input a string to send: ");
		// 接收输入的数据
		std::string str;
		std::getline(std::cin, str);
		// 将用户输入的数据复制到buf中
		ZeroMemory(buf,BUF_SIZE);   
		strcpy(buf,str.c_str());   
		// 循环等待
		while(true)
		{
			// 向服务器发送数据
			retVal = send(sHost,buf,strlen(buf),0);   
			if(SOCKET_ERROR == retVal)   
			{   
				int err = WSAGetLastError();
				if(err == WSAEWOULDBLOCK)			// 无法立即完成非阻塞套接字上的操作
				{
					Sleep(500);
					continue;
				}
				else
				{
					printf("send failed !\n");   
					closesocket(sHost);   
					WSACleanup();   
					return -1;   
				}
			}
			break;
		}	  

		while(true)
		{
			ZeroMemory(buf,BUF_SIZE);						// 清空接收数据的缓冲区
			retVal = recv(sHost,buf,sizeof(buf)+1,0);   // 接收服务器回传的数据   
			if(SOCKET_ERROR == retVal)   
			{   
				int err = WSAGetLastError();				// 获取错误编码
				if(err == WSAEWOULDBLOCK)			// 接收数据缓冲区暂无数据
				{
					Sleep(100);
					printf("waiting back msg !\n");
					continue;
				}
				else if(err == WSAETIMEDOUT || err == WSAENETDOWN)
				{
					printf("recv failed !\n");   
					closesocket(sHost);   
					WSACleanup();   
					return -1;
				}
				break;
			}   
			break;
		}	
		//ZeroMemory(buf,BUF_SIZE);						// 清空接收数据的缓冲区
		//retVal = recv(sHost,buf,sizeof(buf)+1,0);   // 接收服务器回传的数据   

		printf("Recv From Server: %s\n",buf);   
		// 如果收到quit,则退出
		if(strcmp(buf, "quit") == 0)
		{
			printf("quit!\n");
			break;
		}
	}
    // 释放资源   
    closesocket(sHost);   
    WSACleanup();   
	// 暂停,按任意键继续
	system("pause");
    return 0;  
}

最终效果

四、结论

本次实验是基于 WSAAsyncSelect模型的简略代码,其中还有许多问题需要解决,代码仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值