网络编程

首先是需要的头文件和库:

#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

Tcp连接的简单示例

一、服务器

主要流程及主要函数:

  1. 网络环境初始化:WSAStartup
  2. 创建服务器套接字:socket
  3. 绑定本机IP和端口:bind
  4. 监听客户端:listen
  5. 等待客户端连接:accept
  6. 发送消息:send
  7. 接收消息:recv
  8. 关闭socket:closesocket
  9. 清除网络环境:WSACleanup

(一)网络环境初始化:WSAStartup

1.说明

当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。

该函数执行成功后返回0。

2.函数原型

int WSAStartup(
WORD wVersionRequested, //版本号,使用MAKEWORD宏生成
LPWSADATA lpWSAData //数据
);
//返回值:0代表成功,否则失败

3.代码示例

   int nRet = ::WSAStartup(MAKEWORD(2, 2), &wsaData);
   
    if (nRet != 0)
    {
        return FALSE;
    }

    // 版本校验
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)

    {
        Cleanup(); // 关闭网络库
        return FALSE;
    }

a、HIBYTE是高位,副版本。

b、LOBYTE是低位,主版本。

c、逻辑,只要一个不是2,就说明系统不支持我们要的2.2版本。

4.WSADATA结构

WSADATA,一种数据结构。这个结构被用来存储被WSAStartup函数调用后返回的Windows Sockets数据。它包含Winsock.dll执行的数据。

5.WSADATA结构原型

typedef struct WSAData {
    WORD        wVersion;
    WORD        wHighVersion;
#ifdef _WIN64
    unsigned short    iMaxSockets;
    unsigned short    iMaxUdpDg;
    char        *lpVendorInfo;
    char        szDescription[WSADESCRIPTION_LEN+1];
    char        szSystemStatus[WSASYS_STATUS_LEN+1];
#else
    char        szDescription[WSADESCRIPTION_LEN+1];
    char        szSystemStatus[WSASYS_STATUS_LEN+1];
    unsigned short    iMaxSockets;
    unsigned short    iMaxUdpDg;
    char        *lpVendorInfo;
#endif
} WSADATA, *LPWSADATA;

wVersion

Windows Sockets DLL期望调用者使用的Windows Sockets规范的版本。 高位字节存储副版本号, 低位字节存储主版本号,可以用WORD MAKEWORD(BYTE,BYTE ) 返回这个值,例如:MAKEWORD(1,1)

wHighVersion

这个DLL能够支持的Windows Sockets规范的最高版本。通常它与wVersion相同。

iMaxSockets

单个进程能够打开的socket的最大数目。Windows Sockets的实现能提供一个全局的socket池,可以为任何进程分配;或者它也可以为socket分配属于进程的资源。

这个数字能够很好地反映Windows Sockets DLL或网络软件的配置方式。应用程序的编写者可以通过这个数字来粗略地指明Windows Sockets的实现方式对应用程序是否有用。

例如,X Windows服务器在第一次启动的时候可能会检查iMaxSockets的值:如果这个值小于8,应用程序将显示一条错误信息,指示用户重新配置网络软件(这是一种可能要使用szSystemStatus文本的场合)。显然无法保证某个应用程序能够真正分配iMaxSockets个socket,因为可能有其它WindowsSockets应用程序正在使用。

iMaxUdpDg

Windows Sockets应用程序能够发送或接收的最大的用户数据包协议(UDP)的数据包大小,以字节为单位。如果实现方式没有限制,那么iMaxUdpDg为零。

在Berkeley sockets的许多实现中,对于UDP数据包有个固有的限制(在必要时被分解),大小为8192字节。Windows Sockets的实现可以对碎片重组缓冲区的分配作出限制。对于适合的WindowsSockets 实现,iMaxUdpDg的最小值为512。

注意不管iMaxUdpDg的值是什么,都不推荐你发回一个比网络的最大传送单元(MTU)还大的广播数据包。(Windows Sockets API 没有提供发现MTU的机制,但是它不会小于512个字节)。WinSock2.0版中已被废弃。

lpVendorInfo

指向销售商的数据结构的指针。这个结构的定义(如果有)超出了WindowsSockets规范的范围。WinSock2.0版中已被废弃。

szDescription

以null结尾的ASCII字符串,Windows Sockets DLL将对Windows Sockets实现的描述拷贝到这个字符串中,包括制造商标识。

文本(最多可以有256个字符)可以包含任何字符,但是要注意不能包含控制字符和格式字符,应用程序对其最可能的使用方式是把它(可能被截断)显示在在状态信息中。

szSystemStatus

以null结尾的ASCII字符串,Windows Sockets DLL把有关的状态或配置信息拷贝到该字符串中。Windows Sockets DLL应当仅在这些信息对用户或支持人员有用时才使用它们,它不应被作为szDescription域的扩展。

6.WSADATA使用样例

用在WSAStartup的前面

    // 启动网络库
    WSADATA wsaData = {0};

    int nRet = ::WSAStartup(MAKEWORD(2, 2), &wsaData);

(二)创建服务器套接字:socket

1.说明

socket用于创建一个socket描述符,它唯一标识一个socket

2.函数原型

SOCKET socket(
int af,    //地址类型,常用IPv4地址:AF_INET,和IPv6地址:AF_INET6
int type, //套接字类型,常用TCP协议:SOCK_STREAM,UDP协议:SOCK_DGRAM
int protocol //协议类型,一般填0,自动选择即可
);
//返回值,INVALID_SOCKET失败,该宏实则定义为-1,否则成功

af:

  • AF_UNIX(本机通信)
  • AF_INET(TCP/IP – IPv4)
  • AF_INET6(TCP/IP – IPv6)

type:

1.SOCK_STREAM 流服务 (表示传输层使用TCP协议)

2.SOCK_UGRAM 数据报 (表示传输层使用UDP协议)

protocol:

常见的协议有IPPROTO_TCP、IPPTOTO_UDP、 IPPROTO_SCTP、IPPROTO_TIPC他们分别对应这TCP传输协议,UDP传输协议,STCP传输协议,TIPC传输协议.当protocol为0时,会自动选择type类型对应的默认协议.

当调用socket函数以后, 返回一个文件描述符, 内核会提供与该文件描述符相对应的读和写缓冲区, 同时还有两个队列, 分别是请求连接队列和已连接队列.

3.代码示例

例1:

m_Socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (m_Socket == INVALID_SOCKET)
{
    return FALSE;
}

例2:

int listener = socket(AF_INET,SOCK_STREAM,0);//创建监听的socket

4.socket

什么是Socket?

举一个例子:Lewis跟Nico两人聊QQ,QQ是一个独立的应用程序,那么它对应了两个Socket,一个在Lewis的电脑上,一个在Nico的电脑上。当Lewis对Nico说:”周末我们去开卡丁车吧!“,这句话就是一段数据,这段数据会先储存在Lewis电脑Socket上,TCP存在于传输层,同时,TCP传输过程(三次握手建立连接,三次握手关闭连接),当Lewis的QQ和Nico的QQ连接成功后,Lewis的Socket将这段话的数据发送到Nico的电脑中,但是Nico暂时还没看到,因为数据会先存放在Nico电脑的Socket当中,然后Socket会把数据呈现给Nico看。

到了这里不禁要问,数据传送过程中为什么要多出Socket这样东西?

答:因为不同的应用程序对应不同的Socket,而Socket保证了QQ的数据不会到处乱跑,不会一冲动跑到MSN上去了。因为QQ和MSN两个应用程序的Socket内容是完全不同的。

那么Socket里面到底是什么?

答:Socket套接字地址!套接字地址是一个数据结构,我们仅基于TCP传输协议作为例子。套接字地址这个数据结构里面包含了:地址类型、端口号、IP地址、填充字节这4种数据。

5.socket结构sockaddr_in

socket的数据结构原型为:

  struct sockaddr_in{
    unsigned short        sin_family;    //sin_family表示地址类型,对于基于TCP/IP传输协议的通信,该值只能是AF_INET;
    unsigned short int    sin_port;      //sin_prot表示端口号,例如:21 或者 80 或者 27015,总之在0 ~ 65535之间;//
    struct in_addr        sin_addr;      //sin_addr表示32位的IP地址,例如:192.168.1.5 或 202.96.134.133;
    unsigned char         sin_zero[8];   //sin_zero表示填充字节,一般情况下该值为0;
  };

Socket数据的赋值实例:

  struct sockaddr_in Lewis;
  Lewis.sin_family     = AF_INET;
  Lewis.sin_port       = htons(80);
  Lewis.sin_addr.S_un.S_addr = inet_addr("202.96.134.133");
  memset(Lewis.sin_zero,0,sizeof(Lewis.sin_zero));

上述代码分析:我们设置了一个名叫Lewis的套接字地址,它基于TCP/IP协议,因此sin_family的值为AF_INET,这个是雷打不动的,只要使用TCP/IP协议簇,该值就是AF_INET;htons是端口函数,以后介绍,这就表示设置了端口号为80;

sin_addr是一个数据结构,原型是:

typedef struct in_addr {
  union {
    struct { u_char  s_b1, s_b2, s_b3, s_b4; } S_un_b;
    struct { u_short s_w1, s_w2; } S_un_w;
    u_long S_addr;
  } S_un;
} IN_ADDR, *PIN_ADDR, *LPIN_ADDR;

因此,Lewis这个套接字地址的IP赋值格式是Lewis.sin_addr.s_addr,这里表示设置IP地址为202.96.134.133;而memset函数在这里起到给sin_zero数组清零的作用,它的原型是:

 memset(void *s, int c, size_t n);

6.sockaddr和sockaddr_in

sockaddr在头文件#include <sys/socket.h>中定义。sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了

struct sockaddr {
    u_short    sa_family;
    char    sa_data[14];
};

sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中

struct sockaddr_in {
    short    sin_family;
    u_short    sin_port;
    struct in_addr    sin_addr;
    char    sin_zero[8];
};

二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。

sockaddr常用于bind、connect、recvfrom、sendto等函数的参数,指明地址信息,是一种通用的套接字地址。

sockaddr_in 是internet环境下套接字的地址形式。

一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。

(三)绑定本机IP和端口:bind

1.说明

 bind函数把一个本地协议地址赋予一个套接字。对于网际协议,协议地址是32位的IPv4地址或是128位的IPv6地址与16位的TCP或UDP端口号的组合。

实现套接字与主机本地IP地址和端口号的绑定

2.函数原型

int bind( 
SOCKET s, //创建的socket
sockaddr * name, //包含地址和端口的结构体
int namelen //sockaddr 结构长度
);
//返回值:返回SOCKET_ERROR失败,该宏被定义为-1,否则成功,返回值为0

选择设置sin_addrsin_port或者sin6_addrsin6_port

IP地址端口结果
通配地址 0 内核选择IP地址和端口
通配地址 非0 内核选择IP地址,进程指定端口
本地IP地址 0 进程指定IP地址,内核指定端口
本地IP地址 非0 进程指定IP地址和端口

3.代码示例

    int nRet = ::bind(m_Socket, (sockaddr *)&si, sizeof(si));

    if (nRet == SOCKET_ERROR)
    {
        printf("WSAGetLastError:%d\n", WSAGetLastError());
        return FALSE;
    }

4.网络字节序和主机字节序

主机字节序:

1)大端存储:低位字节放在内存的高地址端,高位字节放在内存的低地址端

2)小端存储:低位字节放在内存的低地址端,高位字节放在内存的高地址端

网络字节序:

UDP/TCP/IP协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的

(四)监听客户端:listen

1.说明

 listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

调用listen导致套接字从CLOSED状态转换到LISTEN状态。

2.函数原型

int listen(
SOCKET s, //要监听的socket,填写自己的socket
int backlog //等待连接的最大队列长度,用SOMAXCONN则为系统给出的最大值
);
//返回值:返回SOCKET_ERROR失败,该宏被定义为-1,否则成功,返回值为0

3.代码示例

    int nRet = ::listen(m_Socket, SOMAXCONN);

    if (nRet == SOCKET_ERROR)
    {
        return FALSE;
    }

4.TCP 半连接队列和全连接队列

为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:

  • 半连接队列,也称 SYN 队列;
  • 全连接队列,也称 accept 队列;

服务端收到客户端发起的 SYN 请求后,内核会把该连接存储到半连接队列,并向客户端响应 SYN+ACK,接着客户端会返回 ACK,服务端收到第三次握手的 ACK 后,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 accept 队列,等待进程调用 accept 函数时把连接取出来

不管是半连接队列还是全连接队列,都有最大长度限制,超过限制时,内核会直接丢弃,或返回 RST 包。

(五)等待客户端连接:accept

1.说明

既然服务端已经很虔诚了,很真诚了,处于倾听状态,那么该是去尝试接受客户端请求的时候了,别只顾着倾听,不去接纳别人。

接纳客户端请求的函数是accept

2.函数原型

SOCKET accept(
SOCKET s, //接收的socket,这里是自己的socket
sockaddr* addr, //接收到客户端的地址信息
int * addrlen //地址信息长度
);
//返回值:返回INVALID_SOCKET失败,该宏定义为-1,否则成功返回客户端的套接字,可进行发送和接收消息

3.代码示例

例1:
        SOCKADDR_IN si;
        ZeroMemory(&si, sizeof(SOCKADDR_IN));
        SOCKET sClient = m_SockTcp.AcceptSocket(si);    
// 等待连接
SOCKET CSocketTcp::AcceptSocket(SOCKADDR_IN &si)
{
    int nSize = sizeof(si);

    return ::accept(m_Socket, (sockaddr *)&si, &nSize);
}

例2:

int clientfd = accept(listener,(struct sockaddr*)&client_addr,&client_addr_len);//接纳客户端请求

(六)发送消息:send

1.说明

 send函数用于socket通信中发送消息

2.函数原型

int send(
SOCKET s,//不是自己的socket,是目标的socket
char * buf,//要发送的内容
int len, //内容长度
int flags //一般为0,拷贝到程序中就立即删除内核中的数据,或MSG_DONTROUTE:要求传输层不要将数据路由出去,MSG_OOB:标志数据应该被带外发送
);
//返回值:-1(或宏SOCKET_ERROR)表示发送失败,否则返回发送成功的字节数

3.代码示例

//协议头信息
typedef struct _HEAD_INFO
{
    _HEAD_INFO()
    {
        m_Cmd = -1;
        m_Len = 0;
    }

    int m_Cmd;  //命令
    int m_Len;  //长度
}HEAD_INFO, * P_HEAD_INFO;
HEAD_INFO SendHead;
        // 发送操作成功
        SendHead.m_Cmd = OP_QUERYOK;
        SendHead.m_Len = 0;

        // 发送头
        pSockTcp->Send(sock, (char *)&SendHead, sizeof(HEAD_INFO));    

4.TCP socket中的buffer

每个TCP socket在内核中都有一个发送缓冲区和一个接受缓冲区,TCP的全双工工作模式以及TCP的流量和拥塞控制便依赖于这两个独立的buffer以及buffer的填充状态。

接受缓冲区把数据缓存入内核,如果没有调用read()系统调用的话,数据会一直缓存在socket的接受缓冲区内。不管进程是否调用recv()读取socket,对等端发来的数据都会经由内核接受并且缓存到socket的内核接受缓冲区之中。recv()所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,并返回拷贝的字节数。(注意:是拷贝,不是像read那样读取之后,清空接受缓冲区内的数据。)

进程调用send()发送数据的时候,将数据拷贝到socket的内核发送缓冲区之中,然后返回拷贝的字节数。send()返回之时,数据不一定会发送到对等端去,send()仅仅是把应用层buffer的数据拷贝到socket的内核发送缓冲区中,发送是TCP的事情。(注意:这里也是拷贝,不是像write那样发送之后,清空发送缓冲区内的数据。)

接受缓冲区被TCP用来缓存网络上接收到的数据,一直保存到应用进程读走为止。如果应用进程一直没有读取,接受缓冲区满了以后,发生的动作是:接收端通知发送端,接收窗口关闭(win=0)。这个便是滑动窗口上的实现。保证TCP套接口接受缓冲区不会溢出,从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。

(七)接收消息:recv

1.说明

recv函数用于socket通信中接收消息

2.函数原型

int recv(
SOCKET s, //套接字,不是自己的socket
char * buf, //接受数据的缓存区
int len, //缓存区大小
int flags     //标志,一般填0,将消息拷贝到应用程序中,将内核中的数据删除,
            //还可以填MSG_PEEK,只取数据,不从内核中删除数据,MSG_OOB:处理带外数据
);
//返回值:小于等于0都表示出错,大于0则表示接收成功的数据大小    

当recv返回值等于0时,表示此时connect已经关闭,没有接收到数据。

3.代码示例

    //申请空间
    char* pBuff = new char[head.m_Len];
    //初始化申请的空间
    ZeroMemory(pBuff, head.m_Len);

    //接收数据
    int nRet = m_pSockTcp->Recv(m_ClientSocket, pBuff, head.m_Len);

(八)关闭socket:closesocket

1.说明

closesocket:对此函数的调用会释放套接字的描述

2.函数原型

int closesocket(
SOCKET s //要关闭的socket,自己的socket
);

3.代码示例

 if (m_Socket != INVALID_SOCKET)
    {
        closesocket(m_Socket);
        m_Socket = INVALID_SOCKET;
    }

4.closesocket探讨

真正释放一个已经打开的套接字句柄的资源直接调用closesocket即可,但要明白closesocket的调用可能会带来负面影响,具体的影响和如何调用有关,最明显的影响是数据丢失,因此一般都要在closesocket之前调用shutdown来关闭套接字。

调用closesocket函数后,再是用此套接字就会发生调用失败,通常返回的错误是WSAENOTSOCK。此时与被closesocket的套接字描述符相关联的资源都会被释放,包括丢弃传输队列中的数据!对于当前进程中的线程来讲,所有被关起的操作,或者是被挂起的重叠操作以及与其关联的任何事件,完成例程或完成端口的执行都将调用失败!

下面从tcp协议上来分析shutdown和closesocket的行为(behavior):

closesocket或shutdown(使用SD_SEND当作参数时),会向通信对方发出一个fin包,而此时套接字的状态会由ESTABLISHED变成FIN_WAIT_1,然后对方发送一个ACK包作为回应,套接字又变成FIN_WAIT_2,如果对方也关闭了连接则对方会发出FIN,我方会回应一个ACK并将套接字置为TIME_WAIT。

因此可以看出closesocket,shutdown所进行的TCP行为是一样的,所不同的是函数部分,shutdown会确保windows建立的数据传输队列中的数据不被丢失,而closesocket会冒然的抛弃所有的数据,因此如果你愿意closesocket完全可以取代shutdown,然而在数据交互十分复杂的网络协议程序中,最好还是shutdown稳妥一些!

(九)清除网络环境:WSACleanup

1.说明

一旦应用程序或DLL进行了一次成功的WSAStartup()调用,它就可以继续进行其它所需的Windows Sockets API调用.当它完成了使用该Windows Sockets DLL的服务后,应用程序或DLL必须调用WSACleanup()以允许Windows Sockets DLL释放任何该应用程序的资源.

2.函数原型

无任何参数

WSACleanup();

3.代码示例

    int nRet = ::WSACleanup();

    if (nRet != 0)
    {
        return FALSE;
    }

二、客户端

主要流程和函数:

  1. 初始化网络环境:WSAStartup
  2. 创建套接字:socket
  3. 连接服务器:connect
  4. 发送数据:send
  5. 接收数据:recv
  6. 清理网络环境:WSACleanup

其它三个函数与服务器一样,只是多出个connect函数,使用方法也与bind函数类似

(一)连接服务器:connect

1.说明

connect函数通常用于客户端建立tcp连接。

connect()用于建立与指定 socket 的连接

2.函数原型

int connect(
SOCKET s, //与服务器连接的socket,自己(客户端)的socket
sockaddr* name, //服务器的地址端口
int namelen //上个参数结构体的长度
);
//返回值:-1失败,否则成功

3.代码示例

SOCKADDR_IN si = { 0 };

一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。

    int nRet = connect(m_Socket, (sockaddr*)&si, sizeof(si));

    if (nRet != 0)
    {
        return FALSE;
    }

(二)send&recv

1.服务端&客户端的socket

服务端的send和recv的socket都不是自己的

send(acceptfd,buf,N,0)

客户端send和recv的socket都是自己的

send(sockfd,buf,N,0)

2.socket缓冲区

每一个socket在被创建之后,系统都会给它分配两个缓冲区,即输入缓冲区和输出缓冲区。 

(1)send函数并不是直接将数据传输到网络中,而是负责将数据写入输出缓冲区,数据从输出缓冲区发送到目标主机是由TCP协议完成的。数据写入到输出缓冲区之后,send函数就可以返回了,数据是否发送出去,是否发送成功,何时到达目标主机,都不由它负责了,而是由协议负责。

(2)recv函数也是一样的,它并不是直接从网络中获取数据,而是从输入缓冲区中读取数据。

输入输出缓冲区,系统会为每个socket都单独分配,并且是在socket创建的时候自动生成的。一般来说,默认的输入输出缓冲区大小为8K。套接字关闭的时候,输出缓冲区的数据不会丢失,会由协议发送到另一方;而输入缓冲区的数据则会丢失。

三、其它网络相关函数

(一)ntohs, ntohl, htons,htonl

这种函数名有固定的意义:

  • h:home
  • n:network
  • s:short
  • l:long

htons:意思就是本机字节序转到网络字节序,short类型的长度
ntohs:意思就是网络字节序转到本机字节序,short类型的长度

(二)inet_addr,inet_ntoa和inet_pton(),inet_ntop()

1.inet_addr,inet_ntoa

  • inet_addr:负责将我们平时看到的网络地址127.0.0.1等转化为网络字节序
  • inet_ntoa:负责将网络字节序还原为我们平时看到的字符串127.0.0.1等

使用方法:

sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //将127.0.0.1转换为网络字节序
char* c_IP = inet_ntoa(addr.sin_addr);//将网络字节序转换为127.0.0.1字符串

2.inet_pton(),inet_ntop()

这两个函数是随IPv6出现的函数,对于IPv4地址和IPv6地址都适用,函数中p和n分别代表表达(presentation)和数值(numeric)

inet_pton()

功能:将标准文本表示形式的IPv4或IPv6 Internet网络地址转换为数字二进制形式。

INT WSAAPI inet_pton(
  INT   Family,  //地址家族  IPV4使用AF_INET  IPV6使用AF_INET6 
  PCSTR pszAddrString, //指向以NULL为结尾的字符串指针,该字符串包含要转换为数字的二进制形式的IP地址文本形式。
  PVOID pAddrBuf//指向存储二进制表达式的缓冲区
);
//返回值:
//1.若无错误发生,则inet_pton()返回1,pAddrBuf参数执行的缓冲区包含按网络字节顺序的二进制数字IP地址。
//2.若pAddrBuf指向的字符串不是一个有效的IPv4点分十进制字符串或者不是一个有效的IPv6点分十进制字符串,则返回0。否则返回-1。
//3.可以通过WSAGetLastError()获得错误错误代码

使用方法:

    sockaddr_in addrServer;
    inet_pton(AF_INET, "127.0.0.1", &addrServer.sin_addr);
    addrServer.sin_port = htons(6000);
    addrServer.sin_family = AF_INET;

inet_ntop()

功能:将IPv4或IPv6 Internet网络地址转换为 Internet标准格式的字符串。

PCWSTR WSAAPI InetNtopW(
  INT        Family,  //地址家族  IPV4使用AF_INET  IPV6使用AF_INET6 
  const VOID *pAddr,  //指向网络字节中要转换为字符串的IP地址的指针
  PWSTR      pStringBuf,//指向缓冲区的指针,该缓冲区用于存储IP地址的以NULL终止的字符串表示形式。
  size_t     StringBufSize//输入时,由pStringBuf参数指向的缓冲区的长度(以字符为单位)
);
//返回值:
//1.若无错误发生,Inet_ntop()函数返回一个指向缓冲区的指针,该缓冲区包含标准格式的IP地址的字符串表示形式。
//2.否则返回NULL
//3.获取错误码:WSAGetLastError()

使用方法:

char buf[20] = { 0 };
inet_ntop(AF_INET, &recvAddr.sin_addr, buf, sizeof(buf));//其中recvAddr为SOCKADDR_IN类型
cout << "客户端地址:" << buf << endl;

(三)gethostbyname

通过域名获取ip地址,比如我们常见的www.baidu.com的ip地址是多少呢?就可以通过这个函数获取

使用方法:

//获取主机ip
HOSTENT* host = gethostbyname("www.baidu.com"); //如获取网站IP地址,参数填写域名即可,不需加"http://"
if (host == NULL)
{
    return false;
}
//转化为char*并拷贝返回
cout << inet_ntoa(*(in_addr*)*host->h_addr_list);

(四)套接字编程流程

 

posted @ 2023-02-26 13:59  ImreW  阅读(21)  评论(0编辑  收藏  举报