windows套接字I/O模型之——阻塞模型(1)

      Windows套接字可以在两种模式下执行I/O操作:阻塞模式和非阻塞模式。在阻塞模式下,I/O操作完成前,执行操作的Winsock调用(例如send和recv)会一直等候下云,不会立即返回到程序中。

      我们现在就来研究一下阻塞模式是怎样工作的。

      阻塞模式的socket都遵照一种“生产者-消费者“模型来编制。下面先以一个最简单的server-client代码示例:

#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib,"WS2_32.lib")

void main(void)
{
	WSADATA              wsaData;
	SOCKET               ListeningSocket;
	SOCKET               NewConnection;
	SOCKADDR_IN          ServerAddr;
	SOCKADDR_IN          ClientAddr;
	int                  ClientAddrLen;
	int                  Port = 5150;
	int                  Ret;
	char                 DataBuffer[1024];

	// 初始化 Winsock version 2.2

	if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
	{
		//注意,如果这里调用失败了,我们不可以像处理其他错误一样使用WSAGetLastError来得到错误代码。
		//因为Winsock没有启动成功,就不可以使用Winsock相关函数来处理问题。我们只能报告这个错误状态,
		//接着退出。
		printf("WSAStartup failed with error %d\n", Ret);
		return;
	}

	//创建一个新的socket来监听客户的连接请求,AF_INET指明使用IPV4协议
	//SOCK_STREAM指明基于流来传送而不是数据报
	//IPPROTO_TCP指明使用使用TCP协议
	if ((ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) 
		== INVALID_SOCKET)
	{
		printf("socket failed with error %d\n", WSAGetLastError());
		WSACleanup();
		return;
	}

	//创建一个SOCKADDR_IN结构以供bind绑定使用
	ServerAddr.sin_family = AF_INET;//使用IPV4协议
	ServerAddr.sin_port = htons(Port);//转换成网络字节序    
	ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);//接收任何IP的连接请求

	// 将地址信息与socket绑定起来
	if (bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)) 
		== SOCKET_ERROR)
	{
		printf("bind failed with error %d\n", WSAGetLastError());
		closesocket(ListeningSocket);
		WSACleanup();
		return;
	}

	//设置ListeningSocket为监听状态,设置最多存储5个未处理的连接请求
	if (listen(ListeningSocket, 5) == SOCKET_ERROR)
	{
		printf("listen failed with error %d\n", WSAGetLastError());
		closesocket(ListeningSocket);
		WSACleanup();
		return;
	} 

	printf("We are awaiting a connection on port %d.\n", Port);

	// accept创建会一个新的socket,如果有一个连接请求到达,
	//则接收连接请求把这个新的连接句柄返回给NewConnection
	ClientAddrLen = sizeof(SOCKADDR);
	if ((NewConnection = accept(ListeningSocket, (SOCKADDR *) &ClientAddr,
		&ClientAddrLen)) == INVALID_SOCKET)
	{
		printf("accept failed with error %d\n", WSAGetLastError());
		closesocket(ListeningSocket);
		WSACleanup();
		return;
	}
	printf("We successfully got a connection from %s:%d.\n",
		inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));

	//这个时候可以回头再监听其他连接请求,也可以处理NewConnection这个连接上的
	//收发消息事务,为演示的简单起见,我们不回头监听其他连接,在这里关闭这个监听,
	//然后直接处理收发消息的事件。

	closesocket(ListeningSocket);

	//为了简单起见,这里只是简单的接收NewConnection这个已经连接的客户的一个消息
	//然后输出接收到的内容和长度。
	printf("We are waiting to receive data...\n");

	if ((Ret = recv(NewConnection, DataBuffer, sizeof(DataBuffer), 0)) 
		== SOCKET_ERROR)
	{
		printf("recv failed with error %d\n", WSAGetLastError());
		closesocket(NewConnection);
		WSACleanup();
		return;
	} 
	printf("We successfully received %s %d byte(s).\n", DataBuffer,uRet);   

	//这是一个简单示例,我们不打算做太多的处理,现在可以关闭NewConnection连接了。
	printf("We are now going to close the client connection.\n");
	closesocket(NewConnection);

	//最后要记得调用WSACleanup来终止Winsock
	WSACleanup();
}

      上面的关键点在accept函数调用的时候,如果当时没有连接请求到达,会形成阻塞,使程序进入等待状态。另外recv函数如果没有收到客户的数据,也会进入阻塞状态。其他部分在代码注释已经解析的很清楚。

      下面再看一下配套的客户端,代码相对较简单:

//使用方式:本程序名 服务端IP  如:tcpclient.exe 192.168.0.213
#include <winsock2.h>
#include <stdio.h>

#pragma comment(lib,"WS2_32.lib")
void main(int argc, char **argv)
{
	WSADATA              wsaData;
	SOCKET               s;
	SOCKADDR_IN          ServerAddr;
	int                  Port = 5150;
	int                  Ret;

	if (argc <= 1)
	{
		printf("USAGE: tcpclient <Server IP address>.\n");
		return;
	}

	if ((Ret = WSAStartup(MAKEWORD(2,2), &wsaData)) != 0)
	{
		printf("WSAStartup failed with error %d\n", Ret);
		return;
	}

	if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
		== INVALID_SOCKET)
	{
		printf("socket failed with error %d\n", WSAGetLastError());
		WSACleanup();
		return;
	}

	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = htons(Port);    
	ServerAddr.sin_addr.s_addr = inet_addr(argv[1]);//以第一个参数作为服务端IP

	// 使用s请求连接
	printf("We are trying to connect to %s:%d...\n",
		inet_ntoa(ServerAddr.sin_addr), htons(ServerAddr.sin_port));
	if (connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)) 
		== SOCKET_ERROR)
	{
		printf("connect failed with error %d\n", WSAGetLastError());
		closesocket(s);
		WSACleanup();
		return;
	} 

	printf("Our connection succeeded.\n");

	//在连接已经完成的情况下,我们可以接收和发送数据,这里为了简单演示
	//只做发送数据

	printf("We will now try to send a hello message.\n");

	if ((Ret = send(s, "Hello", 5, 0)) == SOCKET_ERROR)
	{
		printf("send failed with error %d\n", WSAGetLastError());
		closesocket(s);
		WSACleanup();
		return;
	}
	printf("We successfully sent %d byte(s).\n", Ret);

	// 当连接的socket上不需要收发数据了,关闭这个socket
	printf("We are closing the connection.\n");
	closesocket(s);

	//退出之前调用WSACleanup
	WSACleanup();
}
      从客户端看来,请求连接和发送数据是没有等待的,也没是没有阻塞。实际上,阻塞只存在于服务端的accept,以及任何一端的recv。

      实际上,从上面的服务端和客户端看来,都是非常简单而不实用的。那么,如果需要做一个真实可用的阻塞模式的服务和客户程序,怎么做?答案是利用多线程。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值