套接字编程部分库函数


前言

本篇文章记录了一些套接字编程当中的常用函数,在参数、功能、返回值等方面进行了具体的说明。
使用套接字函数需要在头文件加入
#include <winsock2.h>
#pragma comment(lib, “Ws2_32.lib”)


WSAStartup()<流式/数据报>

【函数原型】

int WSAStartup (WORD wVersionRequested,LPWSADATA lpWSAData );

【参数】
wVersionRequested
[in] 表示欲使用的Windows Sockets API版本;这是个WORD类型的整数,高字节定义的是次版本号,低字节定义的是主版本号。

lpWSAData
[in] 指向WSAData资料的指针。WSAData是结构数据类型,描述了关于Windows Sockecs底层实现的相关信息。

【返回值】
函数执行成功返回0,失败则返回如下错误代码:
WSASYSNOTREADY: 底层网络子系统没有准备好。
WSAVERNOTSUPPORTED:Winsock版本信息号不支持。WSAEINPROGRESS: 阻塞式Winsock1.1存在于进程中。
WSAEPROCLIM: 已经达到Winsock使用量的上限。
WSAEFAULT: lpWSAData不是一个有效的指针。

【函数功能】
这个函数是应用程序应该第一个调用的Winsock API函数,以完成一系列初始化的工作。

【相关数据结构】
WSADATA的定义如下:

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

其中,各结构成员的含义为:
wVersion
应用程序应该使用的Winsock版本号。
wHighVersion
DLL所支持的最高版本号。通常应该等于wVersion。
szDescription
以0结尾的ASCII字符串,关于Winsock底层实现的描述信息。
szSystemStatus
以0结尾的ASCII字符串,关于Winsock底层状态或者配置信息。
iMaxSockets
一个进程最多可使用的套接字数,仅用于Winsock1.1,Winsock 2.0应该忽略该成员。
iMaxUdpDg
最大的UDP报文大小,仅用于Winsock1.1,Winsock 2.0应该忽略该成员。对于Winsock 2.0,应该使用getsockopt函数取得SO_MAX_MSG_SIZE。
lpVendorInfo
Winsock开发厂商信息,,仅用于Winsock1.1,Winsock 2.0应该忽略该成员。对于Winsock 2.0,应该使用getsockopt函数取得PVD_CONFIG。

【示例代码】

#include <winsock.h>
//对于Winsock 2, include <winsock2.h>
WSADATA wsaData;
int nRc = WSAStartup(0x0101, & wsaData);
if(nRc)
{
    
    
//Winsock初始化错误
return;
}
if(wsaData.wVersion != 0x0101)
{
    
    
//版本支持不够
//报告错误给用户,清除Winsock,返回
WSACleanup();
return;
}

socket()<流式/数据报>

【函数原型】

SOCKET socket(int af, int type, int protocol);

【参数】
af
指定地址族(address family),一般填AF_INET(使用Internet地址)。
type
指定SOCKET的类型:SOCK_STREAM(流类型),SOCK_DGRAM(数据报类型)。
protocol
指定af参数指定的地址族所使用的具体一个协议。建议设为0,那么它就会根据地址格式和SOCKET类型,自动为你选择一个合适的协议。另外2个常用的值为:IPPROTO_UDPIPPROTO_TCP

【返回值】
函数执行成功返回一个新的SOCKET,失败则返回INVALID_SOCKET。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
所有的通信在建立之前都要创建一个SOCKET。
【示例代码】

//创建数据报socket
SCOKET udpSock = socket(AF_INET,
SOCK_DGRAM, IPPROTO_UDP);
//创建流socket
SCOKET tcpSock = socket(AF_INET,
SOCK_STREAM, IPPROTO_TCP);

bind()<流式/数据报>

【函数原型】

INT bind(SOCKET s, const struct sockaddr FAR* name, int namelen);

【参数】
s
一个需要绑定的SOCKET,例如用socket函数创建的SOCKET。
name
指向描述通信对象地址信息的结构体sockaddr的指针。在该结构体中可以指定地址族(一般为 AF_INET)、主机的地址和端口。通常把主机地址指定为INADDR_ANY(一个主机可能有多个网卡)。
namelen
name指针指向的结构体的长度。

【返回值】
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
成功地创建了一个SOCKET后,用bind函数将SOCKET和主机地址绑定。

【相关数据结构】

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

sa_family
地址族,比如AF_INET,2个字节大小。
sa_data
用来存放地址和端口,14个字节大小。
sockaddr结构是一个通用的结构(因为Winsock支持的协议族不只是TCP/IP)。对TCP/IP协议,用如下结构来定义地址和端口。

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

sin_family
地址族,设为AF_INET。
sin_port
端口号。如果端口号为0,Winsock会自动为应用程序分配一个值在1024-5000间的一个端口号,所以客户端一般把sin_port设为0。
sin_addr
为in_addr结构类型,用来指定IP地址。通常把主机地址指定为INADDR_ANY(一个主机可能有多个网卡)。结构in_addr下面介绍。
sin_zero
8字节的数组,值全为0。这个8个字节用来填充结构sockaddr_in,使其大小等于结构sockaddr(16字节)。
in_addr
用来指定IP地址,其定义为:

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;
};

对于IP地址10.14.25.90,sockaddr_in结构中的sin_addr可以这样赋值:
sin_addr. S_un .S_un_b. s_b1 = 10;
sin_addr. S_un .S_un_b. s_b2 = 14;
sin_addr. S_un .S_un_b. s_b3 = 25;
sin_addr. S_un .S_un_b. s_b4 = 90;
或者
sin_addr. S_un . S_un_w. s_w1 = (14<<8)|10;
sin_addr. S_un . S_un_w. s_w2 = (90<<8)|25;
或者
sin_addr. S_un . S_addr = (90<<24)|(25<<16)|(14<<8)|10;
或者
sin_addr. S_un . S_addr = inet_addr(“10.14.25.90”);
这里的inet_addr函数可以将字符串形式的IP地址转换为unsigned long形式的值。

【示例代码】

SOCKET sServSock;
sockaddr_in addr;
//创建socket
sServSock = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
//htons和htonl函数把主机字节顺序转换为网络字节顺序,分别用于//短整型和长整型数据
addr.sin_port = htons(5050);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
// LPSOCKADDR类型转换是必须的
int nRc = bind(sServSock, (LPSOCKADDR)&addr, sizeof(addr) );

listen()<流式>

【函数原型】

int listen (SOCKET s, int backlog);

【参数】
s
一个已经绑定但未连接的SOCKET。
backlog
等待连接的队列的长度,可取SOMAXCONN。如果某个客户程序要求连接的时候,服务器已经与其他客户程序连接,则后来的连接请求会放在等待队列中,等待服务器空闲时再与之连接。当等待队列达到最大长度(backlog指定的值)时,再来的连接请求都将被拒绝。

【返回值】
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
对于服务器的程序,当申请到SOCKET,并将通信对象指定为INADDR_ANY之后,就应该等待一个客户机的程序来要求连接,listen函数就是把一个SOCKET设置为这个状态。


accept()<流式>

【函数原型】

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

【参数】
s
一个已经处于listen状态的SOCKET。
addr
指向sockaddr结构体的指针,里面包含了客户端的地址和端口。
addrlen
int型指针,指向的内容为addr指针指向的结构体的长度。

【返回值】
如果函数执行成功,会建立并返回一个新的SOCKET来与对方通信,新建的SOCKET与原来的SOCKET(函数的第一个参数s)有相同的特性,包括端口号。原来的SOCKET继续等待其他的连接请求。
而新生成的SOCKET才是与客户端通信的实际SOCKET。所以一般将参数中的SOCKET称作“监听”SOCKET,它只负责接受连接,不负责通话;而对于函数返回的SOCKET,把它称作“会话”SOCKET,它负责与客户端通话。
如果失败则返回INVALID_SOCKET。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
accept函数从等待连接的队列中取第一个连接请求,并且创建一个新的SOCKET来负责与客户端会话。

【示例代码】

SOCKET sServSock; //服务器监听socket
sockaddr_in addr;
int nSockErr;
int nNumConns = 0; //当前请求连接数
SOCKET sConns[5]; //会话SOCKET数组
sockaddr ConnAddrs[5];//请求连接的客户端地址
int nAddrLen;
//创建服务器监听socket
sServSock = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_port = htons(5050);
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
if( bind(sServSock,(LPSOCKADDR)&addr,sizeof(addr)) ==
SOCKET_ERROR )
{
    
    
nSockErr = WSAGetLastError();
//绑定出错处理
}

//监听客户端请求连接
if( listen(sServSock, 2) == SOCKET_ERROR)
{
    
    
nSockErr = WSAGetLastError();
//出错处理
}
while( nNumConns < 5){
    
    
//每当收到客户端连接请求,创建新的会话SOCKET,保存在/ //sConns数组中
//客户端地址保存在ConnAddrs数组中
sConns[nNumConns] = accept(sServSock,
ConnAddrs[nNumConns], &nAddrLen);
if(sConns[nNumConns] == INVALID_SOCKET)
{
    
    
nSockErr = WSAGetLastError();
//创建会话SOCKET出错处理
}
else
{
    
    
//创建会话SOCKET成功,启动新的线程与客户端会话
StartNewHandlerThread(sConns[nNumConns]);
//当前请求连接数+1
nNumConns ++;
}
}

connect()<流式>

【函数原型】

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

【参数】
s
一个未连接SOCKET,一般是由socket函数建立的。
name
同bind函数。
namelen
同bind函数。

【返回值】
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
向对方主动提出连接请求。


send()<流式>

【函数原型】

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

【参数】

s
一个已经连接的SOCKET。
buf
指向要传输的数据的缓冲区的指针。
len
buf的长度。
flags
指定函数调用的方式。一般取0。

【返回值】
函数执行成功返回发送的字节数(可能小于len),失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
通过已经连接的SOCKET发送数据。


recv()<流式>

【函数原型】

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

【参数】
s
一个已经连接的SOCKET。
buf
指向接收数据的缓冲区的指针。
len
buf的长度。
flags
指定函数调用的方式。一般取0。

【返回值】
函数执行成功返回接收到数据的字节数。如果失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
通过已经连接的SOCKET接收数据。当读到的数据字节少于规定接受的数目(len)时,就把数据全部接收,并返回实际接收到的字节数;当读到的数据多于规定的值时,在流方式下剩余的数据由下个recv读出,在数据报方式下多余的数据被丢弃。


sendto()<数据报>

【函数原型】

int sendto (SOCKET s, char * buf, int len ,
int flags,struct sockaddr_in * to, int tolen);

【参数】
s
一个SOCKET(可能已连接)。
buf
指向要传输的数据的缓冲区的指针。
len
buf的长度。
flags
指定函数调用的方式。一般取0。
to
指向目标地址结构体的指针。
tolen
目标地址结构体的长度。

【返回值】
函数执行成功返回发送的字节数(可能小于len),失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
该函数一般用于通过无连接的SOCKET发送数据报文,报文的接受者由to参数指定。


recvfrom()<数据报>

【函数原型】

int recvfrom (SOCKET s, char * buf, int len ,int flags,
struct sockaddr_in * from, int * fromlen);

【参数】
s
一个已经绑定的SOCKET。
buf
指向接收数据的缓冲区的指针。
len
buf的长度。
flags
指定函数调用的方式。一般取0。
from
指向源地址结构体的指针。
fromlen
源地址结构体的长度。

【返回值】
函数执行成功返回发送的字节数(可能小于len),失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
该函数一般用于通过无连接的SOCKET接收数据报文,报文的发送者由from参数指定。


closesocket()<流式/数据报>

【函数原型】

int closesocket (SOCKET s,);

【参数】
s
要关闭的SOCKET

【返回值】
函数执行成功返回0,失败则返回SOCKET_ERROR。这时可以调用WSAGetLastError函数取得具体的错误代码。

【函数功能】
关闭指定的SOCKET。


备注

有任何问题,都可在评论区进行交流~~

猜你喜欢

转载自blog.csdn.net/qq_45740212/article/details/113076725