C++网络编程

网络编程基本原理

Socket通信函数

soket

int socket(
        _In_ int af, 		//AF_INET
        _In_ int type,		//socket类型,SOCK_STREAM(Tcp),SOCK_DGRAM(UDP)
        _In_ int protocol	//协议
        );

返回:非负描述字──成功, -1──出错

  • 参数family

    这个参数指定一个协议簇,也往往被称为协议域。系统存在许多可以的协议簇,常见有AF_INET──指定为IPv4协议,AF_INET6──指定为IPv6,AF_LOCAL──指定为UNIX 协议域等等。它值都是系统预先定义的宏,系统支持哪些协议我们才可以使用,否则会调用失败。协议簇是网络层的协议。

  • 参数type

    这个参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW等等,它们分别表明字节流、数据报、有序分组、原始套接口。这实际上是指定内核为我们提供的服务抽象,比如我们要一个字节流。需要注意的,并不是每一种协议簇都支持这里的所有的类型,所以类型与协议簇要匹配。

  • 参数protocol

    指定相应的传输协议,也就是诸如TCP或UDP协议等等,系统针对每一个协议簇与类型提供了一个默认的协议,我们通过把protocol设置为0来使用这个默认的值。注意这里的协议与上面的协议簇是两个不同的概念,前者是指网络层的协议,由于它对于到传输层会出现许多协议,比如IPv4可以用来实现TCP或UDP等等传输层协议,所以称为协议簇。相应的传输层的协议就简单地称为协议。常见的协议有TCP、UDP、SCTP,要指定它们分别使用宏IPPROTO_TCP、IPPROTO_UPD、IPPROTO_SCTP来指定。

  • 返回值

    socket函数返回一个套接字,即套接口描述字。如果出现错误,它返回-1,并设置errno为相应的值,用户应该检测以判断出现什么错误。

bind

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

返回0表示成功,-1表示失败。

  绑定的时候要用到socket的各种数据,这些数据存储在名叫SOCK_ADDR的结构体中:
SOCK_ADDR

typedef struct sockaddr {

#if (_WIN32_WINNT < 0x0600)
    u_short sa_family;
#else
    ADDRESS_FAMILY sa_family;           // Address family.
#endif //(_WIN32_WINNT < 0x0600)

    CHAR sa_data[14];                   // Up to 14 bytes of direct address.
} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;

sa_family是地址家族,一般都是“AF_xxx”的形式。好像通常大多用的是都是AF_INET。
sa_data是14字节协议地址。
此数据结构用做bind、connect、recvfrom、sendto等函数的参数,指明地址信息。

但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构

typedef struct sockaddr_in {

#if(_WIN32_WINNT < 0x0600)		//sin_family指代协议族,在socket编程中只能是AF_INET
    short   sin_family;
#else //(_WIN32_WINNT < 0x0600)
    ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)

    USHORT sin_port;  			//存储端口号(使用网络字节顺序)
    IN_ADDR sin_addr;			//Internet 地址,存储IP地址
    CHAR sin_zero[8];			//为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
} SOCKADDR_IN, *PSOCKADDR_IN;

本机转换

在实现过程中,我们需要将ip 本机字节序列转换为 网络字节序列,这里涉及到多个函数

htons()--"Host to Network Short"

htonl()--"Host to Network Long"

ntohs()--"Network to Host Short"

ntohl()--"Network to Host Long"

将数据放到网络上时,确保它是网络字节序列

为什么在数据结构 struct sockaddr_in 中, sin_addr 和 sin_port 需要转换为网络字节顺序,而sin_family 需不需要呢?

答案是: sin_addr 和 sin_port 分别封装在包的 IP 和 UDP 层。因此,它们必须要 是网络字节顺序。但是 sin_family 域只是被内核 (kernel) 使用来决定在数 据结构中包含什么类型的地址,所以它必须是本机字节顺序。同时, sin_family 没有发送到网络上,它们可以是本机字节顺序。

inet_addr("192.168.175.1");		//将本机ip地址转换为网络字节序

listen

int	listen(
    	_In_ SOCKET s,	//监听的socket
	    _In_ int backlog	//等待请求队列的最大长度
    	);

第一个参数是服务端套接字,你要聆听,总得出来说个话啊,好,就指定你了;第二个参数是等待连接队列的最大长度,比方说,你将backlog定为10, 当有15个连接请求的时候,前面10个连接请求就被放置在请求队列中,后面5个请求被拒绝。****千千万万要注意:****这个10并不是表示客户端最大的连接数为10, 实际上可以有很多很多的客户端(实践证明也是如此)。

返回值:0——成功,1——失败

accept

SOCKET accept(
        _In_ SOCKET s,		//监听套接字		
        _Out_writes_bytes_opt_(*addrle) struct sockaddr FAR * addr,	//
        _Inout_opt_ int FAR * addrlen
        );

返回:非负描述字——成功, -1——失败

accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。

此时我们需要区分两种套接字,一种套接字正如accept的参数s,它是监听套接字,在调用listen函数之后,一个套接字会从主动连接的套接字变身为一个监听套接字;而accept返回是一个连接套接字,它代表着一个网络已经存在的点点连接。自然要问的是:为什么要有两种套接字?原因很简单,如果使用一个描述字的话,那么它的功能太多,使得使用很不直观,同时在内核确实产生了一个这样的新的描述字。

  • 参数s

    参数s就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。

  • 参数addr

    这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。

  • 参数addrlen

    如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。

如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。

connect

int
WSAAPI
connect(
    _In_ SOCKET s,
    _In_reads_bytes_(namelen) const struct sockaddr FAR * name,
    _In_ int namelen
    );
参数1

服务器的socket

参数2

服务器ip地址、端口号结构体
注意参数2的调用需要先定义一个结构体,放入需要请求连接的服务器的ip地址、端口号,这个结构体为struct类型, SOCKADDR* ,可以使用 SOCKADDR_IN 类型转换过来

参数3

参数2的结构体大小

返回值
  • 成功的话返回0
  • 执行失败返回SOCKET_ERROR,用函数WSAGetLastError()得到错误码,根据得到的错误码作相应处理
int a = connect(sky_Server, &serverMsg, sizeof(serverMsg));
	if (a == SOCKET_ERROR) {
		printf("connect错误,错误码:%d\n", WSAGetLastError()); //检验错误原因
		closesocket(sky_Server);
		WSACleanup();	//不成功需要关闭网络库
		return 0;
	}

send

向目标发送数据,本质上就是将数据复制粘贴进系统的协议发送缓冲区,计算机伺机发送出去

int
WSAAPI
send(
    _In_ SOCKET s,	//标识已连接套接字的描述符。
    _In_reads_bytes_(len) const char FAR * buf, //指向包含要传输的数据的缓冲区的指针。
    _In_ int len,	//buf参数指向的缓冲区中数据的长度(以字节为单位)。
    _In_ int flags	//一组标志,指定进行呼叫的方式。通过将按位或运算符与以下任何值一起使用来构造此参数。
    );
参数1

目标的socket,每个客户端对应唯一的socket

参数2

给对方发送的字节串
这个一般不超过1500个字节,也是网络传输的最大单元,也就是客户端发过来的数据,是协议规定的,这个数据也是根据很多情况总结出来的最优值
1500的相关知识文末进一步说明

参数3

要发送的字节个数,决定发送的个数,如果比参数2中的要发送的字节数短,则只发送参数3决定的个数,后面的就不管了,大了就发送过多的字节,内存可能泄漏
一般与参数2一样大小

参数4

一般直接写0就行

recv

int
WSAAPI
recv(
    _In_ SOCKET s,
    _Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
    _In_ int len,
    _In_ int flags
    );
参数1

目标的socket,每个客户端对应唯一的socket

参数2

接收缓存区

参数3

接收缓存区长度

参数4

一般直接写0就行

recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

WSAAsyncSelect 将网络中的某事件关联到窗口的某个消息中

int
WSAAPI
WSAAsyncSelect(
  _In_  SOCKET s,//我们感兴趣的套接字
  _In_  HWND hWnd,//窗口的句柄,对于网络事件繁盛后,想要接收到的通知的那个窗口
  _In_  unsigned int wMsg,//指定在发生网络事件时,打算接收的消息。
  _In_  long lEvent//指定一个位掩码,对应于一系列网络事件的组合。
);

closesocket

int PASCAL FAR closesocket ( IN SOCKET s);

WSACleanup 清理协议版本信息

int
WSAAPI
WSACleanup(
    void
    );

ioctlsocket 设置套接字的I/O模式

int
WSAAPI
ioctlsocket(
    _In_ SOCKET s,
    _In_ long cmd,
    _When_(cmd != FIONREAD, _Inout_)
    _When_(cmd == FIONREAD, _Out_)
    u_long FAR * argp
    );

s为I/O操作的套接字。
cmd为对套接字的操作命令。
argp为命令所带参数的指针。

常见的命令:

//确定套接字自动读入的数据量
#define FIONREAD _IOR(’’’‘f’’’’, 127, u_long) /* get # bytes to read /
//允许或禁止套接字的非阻塞模式,允许为非0,禁止为0
#define FIONBIO _IOW(’’’‘f’’’’, 126, u_long) /
set/clear non-blocking i/o /
//确定是否所有带外数据都已被读入
#define SIOCATMARK _IOR(’’’‘s’’’’, 7, u_long) /
at oob mark? */
本函数可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数,
而与具体协议或通讯子系统无关。支持下列命令:

FIONBIO:允许或禁止套接口s的非阻塞模式。argp指向一个无符号长整型。如
允许非阻塞模式则非零,如禁止非阻塞模式则为零。当创建一个套接口时,它就
处于阻塞模式(也就是说非阻塞模式被禁止)。这与BSD套接口是一致的。WSAAs
ynSelect()函数将套接口自动设置为非阻塞模式。如果已对一个套接口进行了WS
AAsynSelect() 操作,则任何用ioctlsocket()来把套接口重新设置成阻塞模式的
试图将以WSAEINVAL失败。为了把套接口重新设置成阻塞模式,应用程序必须首先
用WSAAsynSelect()调用(IEvent参数置为0)来禁至WSAAsynSelect()。

**FIONREAD:**确定套接口s自动读入的数据量。argp指向一个无符号长整型,其中
存有ioctlsocket()的返回值。如果s是SOCKET_STREAM类型,则FIONREAD返回在一
次recv()中所接收的所有数据量。这通常与套接口中排队的数据总量相同。如果
S是SOCK_DGRAM 型,则FIONREAD返回套接口上排队的第一个数据报大小。

SIOCATMARK:确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_S
TREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINL
INE)。如无带外数据等待读入,则该操作返回TRUE真。否则的话返回FALSE假,
下一个recv()或recvfrom()操作将检索“标记”前一些或所有数据。应用程序可
用SIOCATMARK操作来确定是否有数据剩下。如果在“紧急”(带外)数据前有常
规数据,则按序接收这些数据(请注意,recv()和recvfrom()操作不会在一次调
用中混淆常规数据与带外数据)。argp指向一个BOOL型数,ioctlsocket()在其中
存入返回值。

IP处理函数

接口上排队的第一个数据报大小。

SIOCATMARK:确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_S
TREAM类型的套接口,且该套接口已被设置为可以在线接收带外数据(SO_OOBINL
INE)。如无带外数据等待读入,则该操作返回TRUE真。否则的话返回FALSE假,
下一个recv()或recvfrom()操作将检索“标记”前一些或所有数据。应用程序可
用SIOCATMARK操作来确定是否有数据剩下。如果在“紧急”(带外)数据前有常
规数据,则按序接收这些数据(请注意,recv()和recvfrom()操作不会在一次调
用中混淆常规数据与带外数据)。argp指向一个BOOL型数,ioctlsocket()在其中
存入返回值。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值