Windows网络编程之Client与Server模型(一)

一、WSAStartup函数

WSAStartup 是 Windows 套接字 API 中的一个函数,它用于初始化 Winsock 库,这是 Windows 中用于网络编程的库。使用 Winsock,您可以创建客户端和服务器应用程序,以便它们能够通过网络进行通信。

函数的原型如下:

1
2
3
4
int WSAStartup(
  WORD      wVersionRequested,
  LPWSADATA lpWSAData
);

参数说明:

  • wVersionRequested:指定要使用的 Winsock 版本,通常使用 MAKEWORD(majorVersion, minorVersion) 来设置。例如,要使用版本 2.2,您可以使用 MAKEWORD(2, 2)
  • lpWSAData:指向 WSADATA 结构的指针,用于接收关于 Winsock 初始化的信息。

函数返回值:

  • 如果函数成功初始化 Winsock 库,它将返回 0。
  • 如果发生错误,它将返回一个错误代码

错误码:

注意:在使用 Winsock 库中的其他函数之前,必须调用 WSAStartup 来初始化库,而且在应用程序退出时调用 WSACleanup 来释放相关资源。

 二、socket函数

Windows中,socket() 函数用于创建套接字,并接受一些参数来指定套接字的类型、协议族和选项。

函数的原型如下:

1
2
3
4
5
SOCKET socket(
    int af,          // 地址族(Address Family)
    int type,        // 套接字类型(Socket Type)
    int protocol     // 协议(Protocol)
);

参数说明:

af(地址族 - Address Family):

  • AF_INET:IPv4 地址族。
  • AF_INET6:IPv6 地址族。
  • AF_UNIX 或 AF_LOCAL:本地(Unix 域)套接字。
  • AF_BLUETOOTH:蓝牙地址族。用于创建蓝牙套接字,用于蓝牙设备之间的通信。
  • AF_IRDA:红外线地址族。用于创建红外线套接字,通常用于红外线设备之间的通信。
  • AF_SNA:IBM SNA(Systems Network Architecture)地址族。用于创建SNA网络中的套接字。
  • AF_DECnet:DECnet地址族。用于创建DECnet网络中的套接字,通常在老式DEC设备之间使用。

type(套接字类型 - Socket Type):

  • SOCK_STREAM:一种套接字类型,提供带有OOB数据传输机制的顺序,可靠,双向,基于连接的字节流,此套接字类型使用传输控制协议(TCP)作为Internet地址系列(AF_INET或AF_INET6)。
  • SOCK_DGRAM:一种支持数据报的套接字类型,它是固定(通常很小)最大长度的无连接。不可靠的缓冲区,此套接字类型使用用户数据报协议(UDP)作为Internet地址系列(AF_INET或AF_INET6)。
  • SOCK_RAW:一种套接字类型,提供允许应用程序操作下一个上层协议头的原始套接字,要操作IPv4标头,必须在套接字上设置IP_HDRINCL套接字选项,要操作IPv6标头,必须在套接字上设置IPV6。
  • SOCK_RDM:一种套接字类型,提供可靠的消息数据报,这种类型的一个示例是Windows中的实用通用多播(PGM)多播协议实现,通常称为可靠多播节目,仅在安装了可靠多播协议时才支持此类型值。
  • SOCK_SEQPACKET:一种套接字类型,提供基本数据报的伪流数据包。

protocol(协议 - Protocol):

  • IPPROTO_TCP:传输控制协议(TCP),当af参数为AF_INET或AF_INET6且类型参数为SOCK_STREAM时,这是一个可能的值
  • IPPROTO_UDP:用户数据报协议(UDP),当af参数为AF_INET或AF_INET6且类型参数为SOCK_DGRAM时,这是一个可能的值
  • IPPROTO_ICMP:Internet控制消息协议(ICMP),当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这是一个可能的值
  • IPPROTO_IGMP:Internet组管理协议(IGMP),当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这是一个可能值
  • 0:通常根据 af 和 type 参数自动选择合适的协议。对于 AF_INET 和 SOCK_STREAM,通常会选择 TCP 协议。

返回值说明:

  • socket() 函数的返回值是一个 SOCKET 类型的套接字描述符(socket descriptor)。这个描述符是一个整数值,它代表了创建的套接字。如果socket不使用了,需要销毁套接字:closesocket(socketServer);
  • 如果没有发生错误,套接字将返回引用新套接字的描述符。否则,返回 INVALID_SOCKET 值,并且可以通过调用 WSAGetLastError 检索特定的错误代码。

三、bind函数  

在Windows中,bind() 函数用于将一个套接字绑定到一个特定的本地地址和端口号,以便该套接字可以监听指定的地址和端口或用于连接到远程主机。

函数的原型如下:

1
2
3
4
5
int bind(
    SOCKET s,                   // 套接字描述符
    const struct sockaddr* name, // 指定要绑定的地址和端口
    int namelen                 // 地址结构的长度
);

参数说明:

  • s:要绑定的套接字的描述符,即通过 socket() 函数创建的套接字。
  • name:指向包含要绑定的本地地址和端口信息的 sockaddr 结构体的指针。在IPv4中,通常使用 sockaddr_in 结构体。在IPv6中,使用 sockaddr_in6 结构体。
  • namelen:指定 name 参数所指向的地址结构的长度,以字节为单位。通常使用 sizeof(struct sockaddr_in) 或 sizeof(struct sockaddr_in6) 来获取长度。

返回值:

  • bind() 函数的返回值为整数,通常是一个错误码。如果绑定成功,返回值为0;如果绑定失败,返回值为SOCKET_ERROR,可以使用 WSAGetLastError() 函数获取错误码,然后根据错误码来处理错误情况。
1
2
3
4
5
6
7
8
9
10
11
struct sockaddr {
        ushort  sa_family;
        char    sa_data[14];
};
 
struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};

四、listen()函数

listen() 函数通常用于编程中处理网络套接字,特别是在服务器端编程中。它是一个阻塞函数,用于监听指定的网络地址和端口,以便接受传入的连接请求。一旦有连接请求到达,listen() 将其接受并创建一个新的套接字来处理与客户端的通信。

函数原型:

1
int listen(SOCKET s, int backlog);

参数说明: 

  • s:服务器端的socket,也就是socket函数创建的。
  • backlog:指定了操作系统在接受连接请求时可以排队的最大连接数。如果传递的连接请求超过了这个限制,新的连接请求将被拒绝。一般填写SOMXCONN,其作用是让系统自动选择最合适的个数

返回值:

  • 成功:返回0,失败:SOCKET_ERROR;具体错误码,由WSAGetLastError()获取。

五、accept函数  

accept() 函数用于在服务器套接字上监听传入的连接请求,并在有连接请求到达时创建一个新的套接字,该新套接字用于与客户端进行通信。

函数原型:

1
SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);

参数介绍:  

  • s:这是服务器套接字,用于监听传入连接请求的套接字。
  • addr:这是一个指向 sockaddr 结构的指针,用于存储连接客户端的地址信息。你可以将其设置为 NULL,如果你不关心客户端的地址信息。
  • addrlen:这是一个指向整数的指针,用于指定 addr 缓冲区的大小。通常情况下,你可以将其设置为 sizeof(struct sockaddr)

返回值:

  • 成功:返回值就是给客户端包好的socket,与客户端通信就靠这个;失败:SOCKET_ERROR;具体错误码,由WSAGetLastError()获取。

六、recv函数

recv() 函数是在Windows中用于接收数据的函数,通常与套接字(socket)一起使用。它允许你从一个连接的套接字接收数据。

函数原型:

1
int recv(SOCKET s, char* buf, int len, int flags);

参数介绍: 

  • s:这是要接收数据的套接字。
  • buf:这是一个指向接收数据的缓冲区的指针。
  • len:这是要接收的最大字节数。
  • flags:这是一个标志参数,通常可以设置为0,表示默认行为。

返回值:

  • 读出来的字节数大小,如果没有可读数据,recv则会阻塞等待,直到客户端发来数据。
  • 如果客户端断开连接,则返回0。
  • 执行失败,返回SOCKET_ERROR,根据WSAGetLastError()获取错误码。

七、send()函数

send() 函数是在Windows中用于发送数据的函数,用于将数据从指定的缓冲区发送到已连接的套接字

函数原型:

1
int send(SOCKET s, const char* buf, int len, int flags);

参数介绍:  

  • s:这是要发送数据的套接字。
  • buf:这是一个指向包含要发送数据的缓冲区的指针。
  • len:这是要发送的数据的字节数。
  • flags:这是一个标志参数,通常可以设置为0,表示默认行为。

返回值:

  • 成功返回写入的字节数,执行失败,返回SOCKET_ERROR;通过WSAGetLastError获取错误码

八、Server模型完整示例源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <WinSock2.h>
#include <stdio.h>
 
#pragma comment(lib, "ws2_32.lib")
 
int main() {
    WSADATA wsaData; // 创建一个 WSADATA 结构
 
    // 初始化 Winsock 库,指定要使用的版本
    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (ret != 0) {
        printf("WSAStartup 失败,错误码: %d\n", ret);
         
        return 0;
    }
 
    //校验版本
    if (HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2) {
        printf("版本不符合");
        WSACleanup();
 
        return 0;
    }
 
    // 在这里进行网络编程操作
    SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketServer == INVALID_SOCKET) {
        int errorCode = WSAGetLastError();
        printf("socket创建失败,错误码:%u\n", errorCode);
         
        closesocket(socketServer);
        WSACleanup();
    }
 
    sockaddr_in si;
    si.sin_family = AF_INET;
    si.sin_addr.s_addr = inet_addr("127.0.0.1");
    //si.sin_addr.s_addr = INADDR_ANY; // 使用0.0.0.0监听所有可用端口
    si.sin_port = htons(1234);
 
    ret = bind(socketServer, (SOCKADDR*)&si, sizeof(si));
    if (ret == SOCKET_ERROR) {
        printf("bind绑定失败,错误码:%u\n", WSAGetLastError());
         
        closesocket(socketServer);
        WSACleanup();
        return 0;
    }
 
    ret = listen(socketServer, SOMAXCONN);
    if (ret == SOCKET_ERROR) {
        printf("listen监听失败,错误码:%u\n", WSAGetLastError());
 
        closesocket(socketServer);
        WSACleanup();
        return 0;
    }
 
    //创建客户端链接
    sockaddr_in siClient;
    int nLen = sizeof(siClient);
 
    SOCKET socketClient = accept(socketServer, (SOCKADDR*)&siClient, &nLen);
    if (socketClient == INVALID_SOCKET) {
        printf("accept失败,错误码:%u\n", WSAGetLastError());
         
        closesocket(socketServer);
        WSACleanup();
        return 0;
    }
 
    printf("客户端连接成功\n");
 
    while (true) {
        Sleep(2000);
 
        char buffer[1024] = { 0 };
        ret = recv(socketClient, buffer, sizeof(buffer), 0);
        if (ret == 0) {
            printf("客户端连接中断\n");
        }
        else if (ret == SOCKET_ERROR) {
            printf("recv失败,错误码:%u\n", WSAGetLastError());       
        }
        else {
            printf("recv_len:%d,recv_data:%s\n", ret, buffer);
        }
 
        const char send_buff[] = "hello, I'm is server";
        ret = send(socketClient, send_buff, strlen(send_buff), 0);
        if (ret == SOCKET_ERROR) {
            printf("send失败,错误码:%u\n", WSAGetLastError());
        }
    }
     
    closesocket(socketClient);
    closesocket(socketServer);
    // 当程序完成网络编程后,确保调用 WSACleanup 来释放 Winsock 资源
    WSACleanup();
 
    return 1;
}

 九、connect函数 

connect 函数通常用于在网络编程中建立客户端与服务器之间的连接。

函数原型:

1
int connect(SOCKET s, const sockaddr* name, int namelen);

 参数介绍:

  • s:要连接的套接字。
  • name:一个指向 sockaddr 结构体的指针,其中包含要连接的目标服务器的地址信息(IP 地址和端口号)。
  • namelenname 结构体的长度。

返回值:

connect 函数的返回值表示连接的状态,具体含义如下:

  • 如果连接成功,connect 函数返回值为 0(0 表示成功),表示客户端套接字已成功与服务器建立连接。

  • 如果连接失败,connect 函数返回值为 SOCKET_ERROR(通常定义为 -1),表示连接尝试失败。此时,可以使用 WSAGetLastError 函数获取详细的错误码来诊断连接失败的原因。

十、Client模型完整示例源码  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <WinSock2.h>
#include <stdio.h>
 
#pragma comment(lib, "ws2_32.lib")
 
int main() {
    WSADATA wsaData; // 创建一个 WSADATA 结构
 
    // 初始化 Winsock 库,指定要使用的版本
    int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (ret != 0) {
        printf("WSAStartup 失败,错误码: %d\n", ret);
 
        return 0;
    }
 
    //校验版本
    if (HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2) {
        printf("版本不符合");
        WSACleanup();
 
        return 0;
    }
 
    // 在这里进行网络编程操作
    SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketServer == INVALID_SOCKET) {
        int errorCode = WSAGetLastError();
        printf("socket创建失败,错误码:%u\n", errorCode);
 
        closesocket(socketServer);
        WSACleanup();
    }
 
    //链接服务器
    sockaddr_in si;
    si.sin_family = AF_INET;
    si.sin_addr.s_addr = inet_addr("127.0.0.1");
    si.sin_port = htons(1234);
 
    ret = connect(socketServer, (SOCKADDR*)&si, sizeof(si));
    if (ret == SOCKET_ERROR) {
        printf("connect失败,错误码:%u\n", WSAGetLastError());
 
        closesocket(socketServer);
        WSACleanup();
        return 0;
    }
 
    while (true) {
        const char send_buff[] = "hello, I'm is client";
        ret = send(socketServer, send_buff, sizeof(send_buff), 0);
        if (ret == SOCKET_ERROR) {
            printf("send失败,错误码:%u\n", WSAGetLastError());
        }
 
        Sleep(1000);
 
        char buffer[1024] = { 0 };
        ret = recv(socketServer, buffer, sizeof(buffer), 0);
        if (ret == 0) {
            printf("客户端连接中断\n");
        }
        else if (ret == SOCKET_ERROR) {
            printf("recv失败,错误码:%u\n", WSAGetLastError());
        }
        else {
            printf("recv_len:%d,recv_data:%s\n", ret, buffer);
        }
    }
     
    closesocket(socketServer);
    WSACleanup();
 
    return 1;
}

 

 

posted @   TechNomad  阅读(190)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示