WinSock 编程:Windows 网络编程的基础

目录

概述

[WinSock 库函数](#WinSock 库函数)

[WinSock 的注册与注销](#WinSock 的注册与注销)

[注册 WinSock](#注册 WinSock)

[注销 WinSock](#注销 WinSock)

[WinSock 错误处理函数](#WinSock 错误处理函数)

[主要的 WinSock 函数](#主要的 WinSock 函数)

[1. socket():创建一个套接字](#1. socket():创建一个套接字)

[2. bind():将套接字绑定到一个本地地址](#2. bind():将套接字绑定到一个本地地址)

[3. listen():监听来自客户端的连接请求](#3. listen():监听来自客户端的连接请求)

[4. accept():接受来自客户端的连接请求](#4. accept():接受来自客户端的连接请求)

[5. connect():连接到一个远程服务器](#5. connect():连接到一个远程服务器)

[6. send() 和 recv():发送和接收数据](#6. send() 和 recv():发送和接收数据)

[7. closesocket():关闭一个套接字](#7. closesocket():关闭一个套接字)

[WinSock 辅助函数](#WinSock 辅助函数)

[WinSock 信息查询函数](#WinSock 信息查询函数)

[1. WSAEnumProtocols():枚举支持的网络协议](#1. WSAEnumProtocols():枚举支持的网络协议)

[2. WSAGetLastError():获取上一个 WinSock 函数调用的错误代码](#2. WSAGetLastError():获取上一个 WinSock 函数调用的错误代码)

[3. WSAGetOverlappedResult():获取重叠 I/O 操作的结果](#3. WSAGetOverlappedResult():获取重叠 I/O 操作的结果)

网络应用程序的运行环境

网络协议

网络硬件

操作系统

操作系统提供的网络功能

常见的网络操作系统

应用程序访问网络功能

网络性能

总结


概述

WinSock(Windows Sockets)是 Microsoft Windows 操作系统中用于网络编程的一套 API(应用程序编程接口)。它提供了一组函数库,允许程序员在 Windows 平台上开发网络应用程序,实现网络通信和数据传输。WinSock 抽象了底层网络协议和硬件细节,为程序员提供了一套统一的接口,使他们能够更容易地实现网络功能。WinSock 广泛应用于各种 Windows 网络应用程序中,包括网页浏览器、即时通讯软件、网络游戏等。

WinSock 库函数

WinSock 库提供了丰富的功能和接口,涵盖了网络编程的各个方面。下面介绍一些常见的 WinSock 库函数。

WinSock 的注册与注销

WinSock(Windows Sockets)是微软公司为 Windows 操作系统提供的网络编程 API。在使用 WinSock 库函数之前,需要先注册 WinSock DLL(动态链接库),在应用程序退出时注销 WinSock DLL。

注册 WinSock

要注册 WinSock,需要调用 WSAStartup() 函数。该函数需要两个参数:

  • lpWSAData:指向 WSADATA 结构体的指针。WSADATA 结构体包含有关 WinSock 版本和实现的信息。
  • wVersionRequested:指定所需的 WinSock 版本号。

例如,以下代码注册 WinSock 2.2:

cpp 复制代码
#include <winsock2.h>
#include <stdio.h>

int main() {
    WSADATA wsaData;
    // 初始化winsock,版本为2.2
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) {
        printf("WSAStartup调用失败,错误码: %d\n", iResult);
        return 1;
    }

    // 创建套接字
    SOCKET m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_socket == INVALID_SOCKET) {
        printf("创建套接字失败,错误码: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // 设置服务器的信息
    sockaddr_in service;
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr("127.0.0.1"); // localhost
    service.sin_port = htons(8888); // 目标端口

    // 开始在套接字上监听
    if (bind(m_socket,  (SOCKADDR*)&service, sizeof(service)) == SOCKET_ERROR) {
        printf("bind() 调用失败。\n");
        closesocket(m_socket);
        return 1;
    }

    // 设置套接字为监听状态
    if (listen(m_socket, 1) == SOCKET_ERROR) {
        printf("套接字监听状态设置失败。\n");
    }

    // 接受传入的连接请求。
    SOCKET acceptSocket = INVALID_SOCKET;
    acceptSocket = accept(m_socket, NULL, NULL);
  
    if (acceptSocket == INVALID_SOCKET) {
        printf("accept() 调用失败, 错误码: %d\n", WSAGetLastError());
        return 1;
    } else {
        printf("客户端已连接。\n");
        //用acceptSocket去做一些事情
    }

    // 在应用程序处理完连接后,需要调用shutdown.
    iResult = shutdown(m_socket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown 调用失败,错误码: %d\n", WSAGetLastError());
        closesocket(m_socket);
        WSACleanup();
        return 1;
    }

    // 准备进行善后处理
注销 WinSock

在应用程序退出时,需要调用 WSACleanup() 函数注销 WinSock。该函数没有参数。

例如,以下代码注销 WinSock:

cpp 复制代码
#include <winsock2.h>

int main() {
  // 在此之前你的代码可能已经初始化WinSock并进行了一些网络操作...

  // 现在,你想要注销WinSock库
  int iResult = WSACleanup();
  if (iResult == SOCKET_ERROR) {
    printf("Error at WSACleanup: %ld\n", WSAGetLastError());
    return 1;
  }

  return 0;
}

WinSock 错误处理函数

WinSock 错误处理函数在使用 WinSock 库进行网络编程时非常重要。这些函数可帮助程序员有效地检测和处理网络操作中可能出现的错误。

  1. WSAGetLastError():这个函数用于获取上一个 WinSock 函数调用产生的错误代码。每当调用一个 WinSock 函数时,都应该立即检查这个函数的返回值,以确定是否发生了错误。如果有错误发生,就可以使用 WSAGetLastError() 获取相应的错误代码,进而采取适当的错误处理措施。

  2. WSASetLastError():虽然这个函数的使用场景相对较少,但在某些情况下,它可以用于设置 WinSock 函数的错误代码。通常情况下,错误代码会被自动设置,但在一些特殊的情况下,程序员可能需要手动设置错误代码。

  3. WSAIsBlocking():这个函数用于检查 WinSock 函数是否处于阻塞模式。在网络编程中,阻塞和非阻塞模式是两种常见的操作模式。WSAIsBlocking() 函数可以帮助程序员确定当前的套接字是否处于阻塞模式,从而更好地控制程序的行为。

主要的 WinSock 函数

WinSock 库提供了许多函数来实现网络通信和数据传输。以下是一些常用的函数:

1. socket():创建一个套接字

socket() 函数用于创建一个套接字。套接字是网络通信的基本单元,它代表一个通信端点。socket() 函数的三个参数:

  • af: 指定套接字的地址族,例如 AF_INET 表示 IPv4 地址族,AF_INET6 表示 IPv6 地址族。
  • type: 指定套接字的类型,例如 SOCK_STREAM 表示面向连接的套接字,SOCK_DGRAM 表示无连接的套接字。
  • protocol: 指定套接字使用的协议,通常为 0,表示使用默认协议。

例如,以下代码创建一个 IPv4 面向连接的套接字:

cpp 复制代码
#include <winsock2.h>

int main() {
  WSADATA wsaData;
  int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if (iResult != 0) {
    printf("WSAStartup failed: %d\n", iResult);
    return 1;
  }

  SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
  if (s == INVALID_SOCKET) {
    printf("socket failed: %d\n", WSAGetLastError());
    WSACleanup();
    return 1;
  }

  // ... 使用套接字 ...

  closesocket(s);
  WSACleanup();
  return 0;
}
2. bind():将套接字绑定到一个本地地址

bind() 函数用于将套接字绑定到一个本地地址。本地地址由 IP 地址和端口号组成。bind() 函数的两个参数:

  • s: 要绑定的套接字。
  • sockaddr: 指向 sockaddr_in 结构体的指针,包含要绑定的本地地址信息。

例如,以下代码将套接字绑定到本地地址 127.0.0.1:8080

cpp 复制代码
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);

int iResult = bind(s, (struct sockaddr *)&addr, sizeof(addr));
if (iResult != 0) {
  printf("bind failed: %d\n", WSAGetLastError());
  closesocket(s);
  WSACleanup();
  return 1;
}
3. listen():监听来自客户端的连接请求

listen() 函数用于将套接字设置为监听状态,以便接收来自客户端的连接请求。listen() 函数的一个参数:

  • s: 要监听的套接字。
  • backlog: 指定排队等待连接的客户端的最大数量。

例如,以下代码将套接字设置为监听状态,并允许最多 10 个客户端排队等待连接:

cpp 复制代码
int iResult = listen(s, 10);
if (iResult != 0) {
  printf("listen failed: %d\n", WSAGetLastError());
  closesocket(s);
  WSACleanup();
  return 1;
}
4. accept():接受来自客户端的连接请求

accept() 函数用于接受来自客户端的连接请求,并返回一个新的套接字,用于与该客户端通信。accept() 函数的两个参数:

  • s: 要监听的套接字。
  • clientaddr: 指向 sockaddr_in 结构体的指针,用于接收客户端的地址信息。

例如,以下代码接受来自客户端的连接请求,并返回新的套接字 clientSocket

cpp 复制代码
struct sockaddr_in clientaddr;
int clientaddrlen = sizeof(clientaddr);
SOCKET clientSocket = accept(s, (struct sockaddr *)&clientaddr, &clientaddrlen);
if (clientSocket == INVALID_SOCKET) {
  printf("accept failed: %d\n", WSAGetLastError());
  closesocket(s);
  WSACleanup();
  return 1;
}

// ... 使用 clientSocket 与客户端通信 ...
5. connect():连接到一个远程服务器

connect() 函数用于连接到一个远程服务器。connect() 函数的两个参数:

  • s: 要连接的套接字。
  • sockaddr: 指向 sockaddr_in 结构体的指针,包含远程服务器的地址信息。

例如,以下代码连接到远程服务器 127.0.0.1:8080

cpp 复制代码
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serveraddr.sin_port = htons(8080);

int iResult = connect(s, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (iResult != 0) {
  printf("connect failed: %d\n", WSAGetLastError());
  closesocket(s);
  WSACleanup();
  return 1;
}
6. send() 和 recv():发送和接收数据

send() 函数用于发送数据,recv() 函数用于接收数据。这两个函数都需要以下参数:

  • s: 要使用的套接字。
  • buf: 指向要发送或接收数据的缓冲区的指针。
  • len: 要发送或接收的数据长度。
  • flags: 指定发送或接收数据的标志。

例如,以下代码发送数据到套接字 s

cpp 复制代码
char buf[] = "Hello, world!";
int iResult = send(s, buf, sizeof(buf), 0);
if (iResult == SOCKET_ERROR) {
  printf("send failed: %d\n", WSAGetLastError());
  closesocket(s);
  WSACleanup();
  return 1;
}
7. closesocket():关闭一个套接字

closesocket() 函数用于关闭一个套接字。closesocket() 函数的一个参数:

  • s: 要关闭的套接字。

例如,以下代码关闭套接字 s

cpp 复制代码
closesocket(s);

WinSock 辅助函数

除了错误处理函数外,WinSock 库还提供了一系列辅助函数,用于处理网络地址、主机名解析、套接字选项等。以下是其中一些常用的辅助函数:

  1. gethostbyname():根据主机名获取对应的 IP 地址。这个函数可用于将主机名转换为 IP 地址,从而使程序能够连接到指定的主机。不过,注意到这个函数在 IPv6 环境下可能会过时,因为 IPv6 的引入使得需要更为通用的地址解析方式。

  2. getpeername() 和 getsockname():这两个函数用于获取套接字的远程地址(对端地址)和本地地址。getpeername() 通常在服务器端用于获取连接到服务器的客户端地址,而 getsockname() 则用于获取套接字绑定的本地地址。

  3. setsockopt() 和 getsockopt():这两个函数用于设置和获取套接字选项。套接字选项是一些控制套接字行为的参数,如超时设置、缓冲区大小等。通过 setsockopt() 和 getsockopt() 函数,程序可以对这些选项进行灵活的控制和配置。

  4. ioctlsocket():这个函数用于控制套接字的输入/输出选项。它提供了一种通用的机制,用于执行各种与套接字 IO 相关的操作,如非阻塞 IO 设置、异步 IO 设置等。

WinSock 信息查询函数

WinSock 库提供了函数来获取有关网络接口、协议和错误的信息。以下是一些常用的信息查询函数:

1. WSAEnumProtocols():枚举支持的网络协议

WSAEnumProtocols() 函数用于枚举系统支持的网络协议。该函数的三个参数:

  • lpProtoEnum: 指向 WSAPROTOCOL_INFO 结构体的数组的指针。每个 WSAPROTOCOL_INFO 结构体包含有关一个网络协议的信息。
  • lpiBufferLength: 指向 lpProtoEnum 数组大小的变量。
  • lpProtocolBufferLength: 指向 lpProtoEnum 数组中每个 WSAPROTOCOL_INFO 结构体所需空间大小的变量。

例如,以下代码枚举系统支持的网络协议:

cpp 复制代码
#include <winsock2.h>

int main() {
  WSADATA wsaData;
  int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
  if (iResult != 0) {
    printf("WSAStartup failed: %d\n", iResult);
    return 1;
  }

  WSAPROTOCOL_INFO wsaProtocolInfo[10];
  DWORD dwBufferSize = sizeof(wsaProtocolInfo);
  DWORD dwEntries = 0;

  iResult = WSAEnumProtocols(wsaProtocolInfo, &dwBufferSize, &dwEntries);
  if (iResult != 0) {
    printf("WSAEnumProtocols failed: %d\n", iResult);
    WSACleanup();
    return 1;
  }

  printf("Number of protocols: %d\n", dwEntries);
  for (DWORD i = 0; i < dwEntries; i++) {
    printf("Protocol name: %s\n", wsaProtocolInfo[i].szProtocolName);
    printf("Protocol version: %d.%d\n", wsaProtocolInfo[i].dwServiceFlags1, wsaProtocolInfo[i].dwServiceFlags2);
  }

  WSACleanup();
  return 0;
}
2. WSAGetLastError():获取上一个 WinSock 函数调用的错误代码

WSAGetLastError() 函数用于获取上一个 WinSock 函数调用的错误代码。该函数没有参数,并返回一个错误代码。

例如,以下代码获取 connect() 函数调用的错误代码:

cpp 复制代码
int iResult = connect(s, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (iResult != 0) {
  int errorCode = WSAGetLastError();
  printf("connect failed: %d\n", errorCode);

  switch (errorCode) {
    case WSAECONNREFUSED:
      printf("Connection refused\n");
      break;
    case WSATIMEOUT:
      printf("Connection timed out\n");
      break;
    default:
      printf("Unknown error\n");
  }

  closesocket(s);
  WSACleanup();
  return 1;
}
3. WSAGetOverlappedResult():获取重叠 I/O 操作的结果

WSAGetOverlappedResult() 函数用于获取重叠 I/O 操作的结果。该函数的五个参数:

  • hHandle: 要等待的句柄。
  • lpOverlapped: 指向 OVERLAPPED 结构体的指针,包含有关重叠 I/O 操作的信息。
  • cbTransfer: 传输的数据量。
  • dwFlags: 指定要设置或清除的标志。
  • lpOverlappedResult: 指向 LPDWORD 变量的指针,用于接收重叠 I/O 操作的结果。

例如,以下代码获取 recv() 函数的重叠 I/O 操作的结果:

cpp 复制代码
OVERLAPPED overlapped;
overlapped.hEvent = NULL;

DWORD dwBytesTransferred;
LPDWORD lpBytesTransferred = &dwBytesTransferred;

int iResult = WSAGetOverlappedResult(s, &overlapped, lpBytesTransferred, NULL, WSAGET_FLAG_PEEK);
if (iResult == FALSE) {
  int errorCode = WSAGetLastError();
  printf("WSAGetOverlappedResult failed: %d\n", errorCode);

  closesocket(s);
  WSACleanup();
  return 1;
}

printf("Received: %d bytes\n", dwBytes

网络应用程序的运行环境

WinSock 库函数为网络应用程序提供了强大的功能,但要实现一个完整的网络应用程序,还需要考虑其他因素和组件。下面介绍网络应用程序的运行环境。

网络协议

网络协议是定义网络设备如何进行通信和数据传输的规则和标准。网络应用程序需要使用网络协议来进行通信和数据传输。常见的网络协议有 TCP/IP、UDP、HTTP 等。

WinSock 库函数支持多种网络协议,包括 TCP/IP、UDP、HTTP 等。程序员需要根据应用程序的需求选择合适的协议。

  • TCP/IP 协议 :WinSock 库函数支持 TCP、IP、UDP、ICMP 等 TCP/IP 协议。程序员可以使用 socket() 函数创建一个套接字,并指定要使用的协议。

  • UDP 协议 :WinSock 库函数支持 UDP 协议。程序员可以使用 socket() 函数创建一个套接字,并指定协议为 SOCK_DGRAM

  • HTTP 协议:WinSock 库函数支持 HTTP 协议。程序员可以使用 WinHttp API 或其他 HTTP 库来实现 HTTP 通信。

网络硬件

网络应用程序需要运行在具备网络功能的硬件平台上,如个人电脑、服务器、移动设备等。这些设备需要配备网络接口卡(NIC)或无线网络适配器,连接到网络中。

操作系统

网络应用程序需要运行在支持网络功能的操作系统上,如 Windows、Linux、iOS 等。操作系统提供网络协议栈和网络接口,应用程序通过操作系统提供的 API 来访问网络功能。

操作系统提供的网络功能
  • 网络协议栈:网络协议栈是一系列用于传输数据的协议,它负责将应用程序的数据封装成网络数据包,并在网络上传输。操作系统通常提供一个默认的网络协议栈,例如 TCP/IP 协议栈。

  • 网络接口:网络接口是操作系统与物理网络之间的连接。操作系统通常提供一个或多个网络接口,每个网络接口对应一个物理网络设备。

  • 网络 API:网络 API 是操作系统提供的用于访问网络功能的接口。应用程序通过网络 API 来创建套接字、发送和接收数据等。

常见的网络操作系统
  • Windows:Windows 是微软公司开发的图形用户界面操作系统。Windows 提供了丰富的网络功能,包括 TCP/IP 协议栈、网络接口和网络 API。

  • Linux:Linux 是一个开源的 Unix 操作系统。Linux 提供了强大的网络功能,包括 TCP/IP 协议栈、网络接口和网络 API。

  • iOS:iOS 是苹果公司开发的移动操作系统。iOS 提供了完整的网络功能,包括 TCP/IP 协议栈、网络接口和网络 API。

应用程序访问网络功能

应用程序通过操作系统提供的网络 API 来访问网络功能。常见的网络 API 包括:

  • socket API:socket API 是用于创建套接字、发送和接收数据等的基本网络 API。

  • WinHttp API:WinHttp API 是微软公司提供的高级网络 API,它用于简化 HTTP 通信。

  • libcurl:libcurl 是一个开源的网络库,它支持多种网络协议,包括 HTTP、FTP、HTTPS 等。

网络性能

网络应用程序需要考虑网络性能问题,确保数据传输的高效和可靠。常见的网络性能优化措施有连接池、数据压缩、负载均衡等。WinSock 库函数提供了连接池功能,如 WSAEventSelect(),程序员可以利用这些功能来优化网络性能。

总结

WinSock 编程是 Windows 网络编程的基础,它提供了一套强大的 API,允许程序员在 Windows 平台上实现网络通信和数据传输。WinSock 库函数涵盖了网络编程的各个方面,包括套接字管理、数据传输、网络地址处理等。此外,网络应用程序的运行环境,如网络协议、网络硬件、操作系统、网络安全和网络性能,也需要考虑和优化,以实现高效、可靠的网络应用程序。WinSock 编程为 Windows 平台上的网络应用程序开发提供了坚实的基础和强大的工具。

相关推荐
梓仁沐白1 小时前
ubuntu+windows双系统切换后蓝牙设备无法连接
windows·ubuntu
速盾cdn1 小时前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin5211231 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin1 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
PersistJiao3 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
九鼎科技-Leo5 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
黑客Ash6 小时前
【D01】网络安全概论
网络·安全·web安全·php
->yjy6 小时前
计算机网络(第一章)
网络·计算机网络·php
摘星星ʕ•̫͡•ʔ7 小时前
计算机网络 第三章:数据链路层(关于争用期的超详细内容)
网络·计算机网络