Windows下的网络编程

socket 接口已普遍存在于现代操作系统中

Windows 下的 socket 编程接口与 Linux 中几乎相同

不同之处

  • 返回值类型不同 (句柄类型)
  • 句柄不是文件描述符,Windows 中并不是一切皆文件

Windows 下 socket() 的用法

Windows下的网络编程_第1张图片

 Windows 网络编程接口

Windows下的网络编程_第2张图片

Windows下的网络编程_第3张图片

几点细微差异

通过 WSAStartup() 初始化系统环境 (最先调用)

socket(),accept() 错误返回 INVALID_SOCKET (不可默认为 -1)

bind(),listen() 错误返回 SOCKET_ERROR (不可默认为 -1)

connect(),send(),recv() 错误返回 SOCKET_ERROR (不可默认为 -1)

通过 WSACleanup() 清除系统环境 (最后调用)

Windows 网络编程的特殊说明

在工程属性中设置链接 ws2_32.lib

定义变量 WSADATA wd;

选择 socket 版本并初始化 WSAStartup(MAKEWORD(2,2),&wd);

  • Windows 中存在多个 socket 版本

Windows下的网络编程_第4张图片

Windows 客户端编程示例

Windows下的网络编程_第5张图片

Windows 服务端编程示例

Windows下的网络编程_第6张图片

Windows 网络编程示例

client.c


#include 
#include 

#pragma warning(disable : 4996)

int main()
{
	SOCKET sock = 0;
	struct sockaddr_in addr = { 0 };
	char input[32] = { 0 };
	char buf[128] = { 0 };
	int n = 0;

	WSADATA wd = { 0 };

	if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
	{
		printf("startup error\n");

		return -1;
	}

	sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (sock == INVALID_SOCKET)
	{
		printf("socker error\n");

		return -1;
	}

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	addr.sin_port = htons(8888);

	if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
	{
		printf("connect error\n");

		return -1;
	}

	printf("connect succeed\n");

	while (1)
	{
		printf("Input: ");

		scanf("%s", input);

		send(sock, input, strlen(input) + 1, 0);

		n = recv(sock, buf, sizeof(buf), 0);

		if (n > 0)
		{
			printf("Receive data: %s\n", buf);
		}
		else
		{
			break;
		}
	}

	closesocket(sock);

	WSACleanup();

	return 0;
}

server.c


#include 
#include 

#pragma warning(disable : 4996)

int main()
{
	SOCKET server = 0;
	struct sockaddr_in saddr = { 0 };
	SOCKET client = 0;
	struct sockaddr_in caddr = { 0 };
	int csize = 0;
	char buf[32] = { 0 };
	int r = 0;

	WSADATA wd = { 0 };

	if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
	{
		printf("startup error\n");

		return -1;
	}

	server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (server == INVALID_SOCKET)
	{
		printf("server socket error\n");

		return -1;
	}

	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	saddr.sin_port = htons(8888);

	if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR)
	{
		printf("server bind error\n");

		return -1;
	}

	if (listen(server, 1) == SOCKET_ERROR)
	{
		printf("server listen error\n");

		return -1;
	}

	printf("start to accept\n");

	while (1)
	{
		csize = sizeof(caddr);

		client = accept(server, (struct sockaddr*)&caddr, &csize);

		if (client == INVALID_SOCKET)
		{
			printf("server accept error\n");

			return -1;
		}

		printf("client = %d\n", client);

		do
		{
			r = recv(client, buf, sizeof(buf), 0);

			if (r > 0)
			{
				if (strcmp("quit", buf) == 0)
				{
					break;
				}
				else
				{
					printf("Receive data: %s\n", buf);

					send(client, buf, strlen(buf) + 1, 0);
				}
			}

		} while (r > 0);

		closesocket(client);
	}

	closesocket(server);

	WSACleanup();

	return 0;
}

程序运行结果如下:

Windows下的网络编程_第7张图片

问题

select() 是 Linux 系统特有的吗?

Windows 中的 select() 函数

Windows 中同样提供 select() 函数,且参数和 Linux 的版本完全相同

注意:Windows 中的 select() 函数,第一个参数没有任何意义 (仅为了兼容)

Windows下的网络编程_第8张图片

一个细微差异

Windows 中的 select() 专门为套接字而设计

  • fd_count 用于记录感兴趣的 socket 数量
  • fd_array 用于记录感兴趣的 socket 句柄值

Windows下的网络编程_第9张图片

Windows 中 select() 函数使用示例

Windows下的网络编程_第10张图片

Windows 中的多路复用服务端

 select-server.c


#include 
#include 

#pragma warning(disable : 4996)

SOCKET server_handler(SOCKET server)
{
	struct sockaddr_in addr = { 0 };
	int asize = sizeof(addr);

	return accept(server, (struct sockaddr*)&addr, &asize);
}

int client_handler(SOCKET client)
{
	int ret = -1;
	char buf[32] = { 0 };

	ret = recv(client, buf, sizeof(buf) - 1, 0);

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

	if (ret > 0)
	{
		buf[ret] = '0';

		if (strcmp(buf, "quit") != 0)
		{
			ret = send(client, buf, strlen(buf), 0);
		}
		else
		{
			ret = -1;
		}
	}

	return ret;
}

int main()
{
	SOCKET server = 0;
	struct sockaddr_in saddr = { 0 };
	int num = 0;
	fd_set reads = { 0 };
	fd_set temps = { 0 };
	struct timeval timeout = { 0 };

	WSADATA wd = { 0 };

	if (WSAStartup(MAKEWORD(2, 2), &wd) != 0)
	{
		printf("startup error\n");

		return -1;
	}

	server = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (server == INVALID_SOCKET)
	{
		printf("server socket error\n");

		return -1;
	}

	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	saddr.sin_port = htons(8888);

	if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == SOCKET_ERROR)
	{
		printf("server bind error\n");

		return -1;
	}

	if (listen(server, 1) == SOCKET_ERROR)
	{
		printf("server listen error\n");

		return -1;
	}

	FD_ZERO(&reads);
	FD_SET(server, &reads);

	printf("start to accept\n");

	while (1)
	{
		timeout.tv_sec = 0;
		timeout.tv_usec = 10000;

		temps = reads;

		num = select(0, &temps, NULL, NULL, &timeout);

		if (num > 0)
		{
			for (int i = 0; i <= reads.fd_count; i++)
			{
				SOCKET sock = reads.fd_array[i];

				if (FD_ISSET(sock, &temps))
				{
					if (sock == server)
					{
						SOCKET client = server_handler(server);

						if (client != INVALID_SOCKET)
						{
							FD_SET(client, &reads);

							printf("accept client: %d\n", client);
						}
					}
					else
					{
						if (client_handler(sock) == -1)
						{
							FD_CLR(sock, &reads);

							closesocket(sock);
						}
					}
				}
			}
		}
	}

	closesocket(server);

	WSACleanup();

	return 0;
}

Windows 下的 select() 函数,根据所监听文件描述符的个数遍历,相较于 Linux 的实现,遍历的次数更少,效率更高。

程序运行结果如下:

Windows下的网络编程_第11张图片

思考

 如何编写跨平台编译运行的网络程序?

你可能感兴趣的:(网络编程,windows,网络)