WIN32下Socket最简单的实现,Select模型,IOCP_demo

Socket

服务器

#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef WIN32
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif

#define IP "127.0.0.1"
#define PORT 6080

int main(int argc, char** argv)
{
#ifdef WIN32	//配置win socket 版本
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	int ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0)
	{
		printf("WSAStart up error\n");
		return -1;
	}
#endif

	int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (listenfd == INVALID_SOCKET)
	{
		goto failed;
	}

	struct sockaddr_in sockaddr;
	sockaddr.sin_addr.S_un.S_addr = inet_addr(IP);
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(PORT);

	ret = bind(listenfd, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
	if (ret != 0)
	{
		goto failed;
	}

	ret = listen(listenfd, 1);

	while (1)
	{
		struct sockaddr_in c_address;
		int address_len = sizeof(c_address);
		printf("Waiting...");
		int client_fd = accept(listenfd, (struct sockaddr*)&c_address, &address_len);
		printf("new client comming...! %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));

		char buf[11];
		memset(buf, 0, 11);
		recv(client_fd, buf, 5, 0);

		printf("recv: %s\n", buf);

		send(client_fd, buf, 5, 0);

		closesocket(client_fd);
	}
failed:
	if (listenfd != INVALID_SOCKET)
	{
		closesocket(listenfd);
	}
#ifdef WIN32
	WSACleanup();
#endif
	return 0;
}

客户端

#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef WIN32
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif

#define IP "127.0.0.1"
#define PORT 6080

int main(int argc, char** argv)
{
#ifdef WIN32	//配置win socket 版本
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	int ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0)
	{
		printf("WSAStart up error\n");
		return -1;
	}
#endif
	int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s == -1)
	{
		printf("create socket error!\n");
		goto falise;
	}
	struct sockaddr_in sockaddr;
	sockaddr.sin_addr.S_un.S_addr = inet_addr(IP);
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(PORT);

	ret = connect(s, (struct sockaddr*)&sockaddr, sizeof(sockaddr));
	if (ret != 0)
	{
		goto falise;
	}

	char buff[11];
	memset(buff, 0, 11);
	send(s, "Hello", 5, 0);
	recv(s, buff, 5, 0);
	printf("recv: %s", buff);

falise:
	if (s != INVALID_SOCKET)
	{
		closesocket(s);
		s = INVALID_SOCKET;
	}

#ifdef WIN32
	WSACleanup();
#endif
	return 0;
}

上述方法一次只能接入一个连接.下面使用select

select

定义一个句柄集合

fd_set set;

清空

FD_ZERO(&set);

将监听句柄加入到等待集合里面

FD_SET(listenfd, &set);  // 将监听句柄加入到等待集合里面

设置select

ret = select(0, &set, NULL, NULL, NULL); //读事件
if (ret < 0)
{
	printf("select error\n");
	continue;
}
else if (ret == 0)  //超时, 参数5决定, NULL表示阻塞
{
	printf("select timeout\n");
	continue;
}

判断句柄是否有新事件

//main外,demo用,接入的客户端会单独管理,这里存到数组以作为演示
static int client_fd[4096];  //保存连接句柄
static int socket_count = 0;
//--------------------------
if (FD_ISSET(listenfd, &set))//发送过来了新的连接请求数据
{
	struct sockaddr_in c_address;
	int address_len = sizeof(c_address);
	printf("Waiting...");
	int cs_fd= accept(listenfd, (struct sockaddr*)&c_address, &address_len);
	printf("new client comming...! %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));

	client_fd[socket_count] = cs_fd;
	socket_count++;
	continue;
}

将新的客户端链接句柄加入到等待集合中

//将新的客户端链接句柄加入到等待集合中
for (int j = 0; j < socket_count; j++)
{
FD_SET(client_fd[j], &set);
}

历所有的客户端句柄, 判断连接句柄有没有读事件

//遍历所有的客户端句柄, 判断连接句柄有没有读事件
for (int j = 0; j < socket_count; j++)
{

	if (client_fd[j] != INVALID_SOCKET && FD_ISSET(client_fd[j], &set))//有读事件
	{
		char buff[1024];
		ret = recv(client_fd[j], buff, sizeof(buff), 0);
		if (ret <= 0)  //客户端关闭
		{
			closesocket(client_fd[j]);
			client_fd[j] = INVALID_SOCKET;
		}
		else
		{
			buff[ret] = 0;
			printf("%s\n", buff);
			send(client_fd[j], buff, ret, 0);
		}
	}

}

完整代码

#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef WIN32
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif

#define IP "127.0.0.1"
#define PORT 6080

static int client_fd[4096];  //保存连接句柄
static int socket_count = 0;

int main(int argc, char** argv)
{
#ifdef WIN32	//配置win socket 版本
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	int ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0)
	{
		printf("WSAStart up error\n");
		return -1;
	}
#endif

	int listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (listenfd == INVALID_SOCKET)
	{
		goto failed;
	}

	struct sockaddr_in sockaddr;
	sockaddr.sin_addr.S_un.S_addr = inet_addr(IP);
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons(PORT);

	ret = bind(listenfd, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));
	if (ret != 0)
	{
		goto failed;
	}

	ret = listen(listenfd, 1);

	fd_set set;

	while (1)
	{
		FD_ZERO(&set);
		FD_SET(listenfd, &set);  // 将监听句柄加入到等待集合里面

		//将新的客户端链接句柄加入到等待集合中
		for (int j = 0; j < socket_count; j++)
		{
			if (client_fd[j] != INVALID_SOCKET)
				FD_SET(client_fd[j], &set);
		}

		ret = select(0, &set, NULL, NULL, NULL);
		if (ret < 0)
		{
			printf("select error\n");
			continue;
		}
		else if (ret == 0)  //超时, 参数5决定, NULL表示阻塞
		{
			printf("select timeout\n");
			continue;
		}

		//判断句柄事件
		if (FD_ISSET(listenfd, &set))//发送过来了新的连接请求数据
		{
			struct sockaddr_in c_address;
			int address_len = sizeof(c_address);

			int cs_fd = accept(listenfd, (struct sockaddr*)&c_address, &address_len);
			printf("new client comming...! %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));

			client_fd[socket_count] = cs_fd;
			socket_count++;
			continue;
		}
		//遍历所有的客户端句柄, 判断连接句柄有没有读事件
		for (int j = 0; j < socket_count; j++)
		{

			if (client_fd[j] != INVALID_SOCKET && FD_ISSET(client_fd[j], &set))//有读事件
			{
				char buff[1024];
				ret = recv(client_fd[j], buff, sizeof(buff), 0);
				if (ret <= 0)  //客户端关闭
				{
					closesocket(client_fd[j]);
					client_fd[j] = INVALID_SOCKET;
				}
				else
				{
					buff[ret] = 0;
					printf("%s\n", buff);
					send(client_fd[j], buff, ret, 0);
				}
			}

		}
	}
failed:
	if (listenfd != INVALID_SOCKET)
	{
		closesocket(listenfd);
	}
#ifdef WIN32
	WSACleanup();
#endif
	return 0;
}

select缺点

1: 性能不好,每次有事件的时候都要遍历所有的句柄,然后查是哪个句柄的事件;
2: 能够管理的句柄的数目是有限制的, 2048个
3: socket的读和写仍然是同步的, 我们发送和接受数据的时候会等在网卡上面;

IOCP管理模型

1: IOCP: 是windows针对高性能服务器做的IO的管理模式,又叫完成端口;
2: IOCP的核心模式:

 1>提交请求;
 2>等待结果;
 3>继续提交请求;

3: 监听:

 1>提交一个监听请求,使用完成端口来等待这个请求到来;
 2>请求来了后,处理,继续提交请求;

4: 读取数据:

 1>提交一个读取数据的请求。
 2>请求完成后,处理完后继续提交;

5: 发送数据的请求:

 1>提交一个发送数据的请求;
 2>请求完成后,继续处理;

IOCP

#pragma warning(disable:4996)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <WinSock2.h>
#include <mswsock.h>
#include <windows.h> 

#pragma comment(lib, "WSOCK32.lib ")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "odbc32.lib")
#pragma comment(lib, "odbccp32.lib")

#define IP "127.0.0.1"
#define PORT 6080

enum
{
	IOCP_ACCEPT = 0,  //监听socket,接入请求
	IOCP_RECV,        //读请求
	IOCP_WRITE		  //写请求
};

//接受数据时候最大的buf大小;
#define MAX_RECV_SIZE 8192

struct io_package
{
	WSAOVERLAPPED overlapped;
	int opt;                   //操作类型, 监听, 读, 写, IOCP_ACCEPT/IOCP_RECV/IOCP_WRITE
	int accept_sock;           //提交请求的句柄, accept的句柄或是读写请求的句柄
	WSABUF wsabuffer;		   //配合读写数据时候用的buffer
	char pkg[MAX_RECV_SIZE];   //定义了一个buff, 这个buff就是真正的内存
};

//投递一个用户请求
static void
post_accept(SOCKET l_sock)
{
	//分配一个io_package 数据结构
	struct io_package* pkg_data = (struct io_package*)malloc(sizeof(struct io_package));
	memset(pkg_data, NULL, sizeof(struct io_package));

	//初始化接受数据的buf
	pkg_data->wsabuffer.buf = pkg_data->pkg;
	pkg_data->wsabuffer.len = MAX_RECV_SIZE - 1;
	pkg_data->opt = IOCP_ACCEPT;  //请求类型

	DWORD dwBytes = 0;
	SOCKET client = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
	int addr_size = (sizeof(struct sockaddr_in) + 16);
	pkg_data->accept_sock = client;

	AcceptEx(l_sock, client, pkg_data->wsabuffer.buf, 0,
		addr_size, addr_size, &dwBytes, &pkg_data->overlapped);
}

static void
post_recv(SOCKET client_fd)
{
	struct io_package* io_data = (struct io_package*)malloc(sizeof(struct io_package));
	memset(io_data, NULL, sizeof(struct io_package));

	io_data->opt = IOCP_RECV;
	io_data->wsabuffer.buf = io_data->pkg;
	io_data->wsabuffer.len = MAX_RECV_SIZE - 1;
	io_data->accept_sock = client_fd;

	DWORD dwRecv = 0;
	DWORD dwFlags = 0;
	//异步读数据
	int ret = WSARecv(client_fd, &(io_data->wsabuffer),
		1, &dwRecv, &dwFlags,
		&(io_data->overlapped), NULL);

}

int main(int argc, char** argv)
{
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	int ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0)
	{
		printf("WSAStart up error\n");
		return -1;
	}

	SOCKET l_sock = INVALID_SOCKET;

	//创建一个完成端口
	HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (iocp == INVALID_HANDLE_VALUE)
	{
		goto failed;
	}

	//创建socket
	l_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (l_sock == INVALID_SOCKET)
	{
		goto failed;
	}

	struct sockaddr_in s_address;
	memset(&s_address, 0, sizeof(s_address));
	s_address.sin_family = AF_INET;
	s_address.sin_addr.s_addr = inet_addr(IP);
	s_address.sin_port = htons(PORT);

	if (bind(l_sock, (struct sockaddr*)&s_address, sizeof(s_address)))
		goto failed;

	if (listen(l_sock, SOMAXCONN) != 0)
		goto failed;

	//IOCP管理l_sock
	//参数三: 用户传入的自定义数据
	CreateIoCompletionPort((HANDLE)l_sock, iocp, (DWORD)0, 0);
	//发送一个监听用户进来的请求
	post_accept(l_sock);

	while (1)
	{
		DWORD dwTrans; //当有数据读到时, 读到的数据数量大小
		DWORD udata;   //用户数据
		struct io_package* io_data;
		// 通过完成端口, 来获得这个请求的结果
		// 调用操作系统的API函数, 查看哪个请求完成了
		// 如果没有状态完成, 那么任务会挂起
		ret = GetQueuedCompletionStatus(iocp, &dwTrans,
			&udata, (LPOVERLAPPED*)&io_data, WSA_INFINITE);

		if (ret == 0)
		{
			if (io_data && io_data->opt == IOCP_RECV)
			{
				closesocket(io_data->accept_sock);
				//关闭socket
				free(io_data);
				continue;
			}
			else if (io_data->opt == IOCP_ACCEPT)
			{
				free(io_data);
				post_accept(l_sock);
			}
		}

		if (dwTrans == 0 && io_data->opt == IOCP_RECV)  //读请求
		{
			closesocket(io_data->accept_sock);
			//关闭socket
			free(io_data);
			continue;
		}

		switch (io_data->opt)
		{
		case IOCP_ACCEPT:  // 接入一个socket
		{
			int client_fd = io_data->accept_sock;  //接入的client_socket
			int addr_size = (sizeof(struct sockaddr_in) + 16);
			struct sockaddr_in* l_addr = NULL;
			int l_len = sizeof(struct sockaddr_in);

			struct sockaddr_in* r_addr = NULL;
			int r_len = sizeof(struct sockaddr_in);

			//可以拿到端口和地址
			GetAcceptExSockaddrs(io_data->wsabuffer.buf,
				0, addr_size, addr_size,
				(struct sockaddr**)&l_addr, &l_len,
				(struct sockaddr**)&r_addr, &r_len);

			// 将新进来的client_fd, 加入完成端口, 来帮助管理完成的请求
			// 第三个参数是用户自定义数据, 可以携带自己的数据结构
			CreateIoCompletionPort((HANDLE)client_fd, iocp, (DWORD)client_fd, 0);
			//投递一个读的请求;
			post_recv(client_fd);
			free(io_data);
			//重新投递一个接受请求;
			post_accept(l_sock);
		}
		break;
		case IOCP_RECV:
		{
			// dwTrans : 读到的数据大小
			// socket : io_data->accept_sock
			// Buf : io_data->wsabuffer.buf/pkg
			io_data->pkg[dwTrans] = 0;
			printf("IOCP recv %d : %s\n", dwTrans, io_data->pkg);

			//直接投递下一个请求
			DWORD dwRecv = 0;
			DWORD dwFlags = 0;

			int ret = WSARecv(io_data->accept_sock, &(io_data->wsabuffer),
				1, &dwRecv, &dwFlags,
				&(io_data->overlapped), NULL);
		}
		break;
		}
	}

failed:
	if (l_sock)
	{
		closesocket(l_sock);
	}

	if (iocp != INVALID_HANDLE_VALUE)
	{
		CloseHandle(iocp);  //释放完成端口
	}
	WSACleanup();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值