Socket编程——常用函数笔记

常用函数

1 htonl、htons、ntohl、ntohs

  • 函数原型:
uint32_t htonl(uint32_t hostlong); --"Host to Network Long"
uint16_t htons(uint16_t hostshort);--"Host to Network Short"
uint32_t ntohl(uint32_t netlong);  --"Network to Host Long"
uint16_t ntohs(uint16_t netshort); --"Network to Host Short"
  • 使用方法:
serv_addr.sin_port = htons(SERV_PORT);
  • 函数功能

之所以需要这些函数是因为计算机数据表示存在两种字节顺序:NBO与HBO

网络字节顺序NBO(Network Byte Order)
按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。

主机字节顺序(HBO,Host Byte Order)
不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。
如 Intelx86结构下,short型数0x1234表示为34 12, int型数0x12345678表示为78 56 34 12如IBM power PC结构下,short型数0x1234表示为12 34, int型数0x12345678表示为12 34 56 78

由于这个原因不同体系结构的机器之间无法通信,所以要转换成一种约定的数序,也就是网络字节顺序,其实就是如同powerpc那样的顺序 。在PC开发中有ntohl和htonl函数可以用来进行网络字节和主机字节的转换。

在Linux和Windows网络编程时需要用到htons和htonl函数,用来将主机字节顺序转换为网络字节顺序。

在Intel机器下,执行以下程序

int main()
{
    printf("%d \n",htons(16));
    return 0;
}

得到的结果是4096,初一看感觉很怪。

解释如下,数字16的16进制表示为0x0010,数字4096的16进制表示为0x1000。 由于Intel机器是小尾端,存储数字16时实际顺序为1000,存储4096时实际顺序为0010。因此在发送网络包时为了报文中数据为0010,需要 经过htons进行字节转换。如果用IBM等大尾端机器,则没有这种字节顺序转换,但为了程序的可移植性,也最好用这个函数。

另外用注意,数字所占位数小于或等于一个字节(8 bits)时,不要用htons转换。这是因为对于主机来说,大小尾端的最小单位为字节(byte)。

2 inet_pton、inet_ntop

  • 函数原型:
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  • 使用方法:
inet_pton(AF_INET,argv[1],&serv_addr.sin_addr);//IP:点分十进制-->网络端格式
  • 函数功能

    inet_pton函数:

    将点分十进制串转换成网络字节序二进制值,此函数对IPv4地址和IPv6地址都能处理。

    af : AF_INET或AF_INET6
    src: 一个指向点分十进制串的指针
    dst: 一个指向转换后的网络字节序的二进制值的指针

    inet_ntop函数

    和inet_pton函数正好相反,inet_ntop函数是将网络字节序二进制值转换成点分十进制串。

    af : AF_INET或AF_INET6
    src : 一个指向点分十进制串的指针
    dst : 一个指向转换后的网络字节序的二进制值的指针
    size: 目标的大小,以免函数溢出其调用者的缓冲区

3 socket

  • 函数原型:
int socket(int domain, int type, int protocol);

domain : 用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。在文件sys/socket.h中定义
type : type用于设置套接字通信的类型
protocol : protocol用于制定某个协议的特定类型,即type类型中的某个类型
return : 如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。

  • 使用方法:
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建套接字
  • 函数功能

为了执行网络I/O, 一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型

  • domain的值

    名称 含义 名称 含义
    PF_UNIX,PF_LOCAL 本地通信 PF_X25 ITU-T X25 / ISO-8208协议
    AF_INET,PF_INET IPv4 Internet协议(常用 PF_AX25 Amateur radio AX.25
    PF_INET6 IPv6 Internet协议 PF_ATMPVC 原始ATM PVC访问
    PF_IPX IPX-Novell协议 PF_APPLETALK Appletalk
    PF_NETLINK 内核用户界面设备 PF_PACKET 底层包访问

    AF前缀表示地址族, PF前缀表示协议族。历史上曾有这样的想法:单个协议族可以支持多个地址族, PF值用来创建套接字,而AF值用于套接字地址结构。但实际上,支持多个地址族的协议族从来就米实现过,而且头文件<sys/socket.h>中为一给定协议定义的PF值总是与此协议的AF值相等。尽管这种相等关系并不一定永远成立,但若有人试图给已有的协议改变这种约定,则许多现存代码都将崩溃.一般都使用AF开头的名称

  • type的值

    名称 含义
    SOCK_STREAM Tcp连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输
    SOCK_DGRAM 支持UDP连接(无连接状态的消息)
    SOCK_SEQPACKET 序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出
    SOCK_RAW RAW类型,提供原始网络协议访问
    SOCK_RDM 提供可靠的数据报文,不过可能数据会有乱序
    SOCK_PACKET 这是一个专用类型,不能呢过在通用程序中使用
  • protocol的值

    名称 含义
    IPPROTO_CP TCP传输协议
    IPPROTO_UDP UDP传输协议
    IPPROTO_STCP SCTP传输协议
  • socket函数中domain和type参数的组合

4 connect

  • 函数原型:
 int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)

sockfd : socket 文件描述符
addr : IP 地址加端口号结构体
addrlen : sizeof(addr)长度
return : 没有错误发生,返回0;否则返回-1

  • 使用方法:
ret = connect(sockfd,(struct sockaddr *)&serv_addr,(socklen_t)sizeof(serv_addr));//注意类型转换
  • 函数功能

connect函数的功能是完成一个有连接协议的连接过程,对于TCP来说就是那个三路握手过程.

为了理解connect函数,我们需要对connect函数的功能进行介绍。connect函数的功能可以用一句话来概括,就是完成面向连接的协议的连接过程,它是主要连接的。面向连接的协议,在建立连接的时候总会有一方先发送数据,那么谁调用了connect谁就是先发送数据的一方。如此理解connect三个参数是容易了,我必需指定数据发送的地址,同时也必需指定数据从哪里发送,这正好是connect的前两个参数,而第三个参数是为第二个参数服务的。

5 bind

  • 函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

​ sockfd : socket 文件描述符
​ addr : IP 地址加端口号结构体
​ addrlen : sizeof(addr)长度
​ 返回值 : 成功返回0,失败返回-1

  • 使用方法:
ret = bind(servfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr);
  • 函数功能

服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可
以向服务器发起连接,因此服务器需要调用bind 绑定一个固定的网络地址和端口号。

客户端没有显式调用bind()函数,操作系统自动分配端口、IP。

bind函数的作用是将参数sockfd 和addr 绑定在一起,使sockfd 这个用于网络通讯的文件描述符监听addr 所描述
的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr 参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen 指定结构体的长度。

6 listen

  • 函数原型:
int listen(int sockfd, int backlog);

​ sockfd : socket 文件描述符
​ backlog : 套接字队列的最大连接数

  • 使用方法:
ret = listen(lfd, 128);//设定最大同时连接数,default:128 
  • 函数功能

listen函数仅由服务端调用,调用listen函数导致套接字从CLOSED状态转为LISTEN状态

listen函数在一般在bind函数之后,accept函数之前调用

同时,listen函数设置了最大同时连接得数量,默认为128

7 accept

  • 函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

​ sockfd : socket 文件描述符
​ addr : IP 地址加端口号结构体
​ addrlen : sizeof(addr)长度

  • 使用方法:
clifd = accept(servfd, (struct sockaddr *)&cli_addr, &cli_addr_len);
  • 函数功能

accept函数由TCP服务端调用, 用于从已完成连接队列队头返回下一个己完成连接,如果已完成连接队列为空,那么进程被投入睡眠(假定程接字为默认的阻塞方式)

一般来说,实现时accept()为阻塞函数,当监听socket调用accept()时,它先到自己的receive_buf中查看是否有连接数据包;若有,把数据拷贝出来,删掉接收到的数据包,创建新的socket与客户发来的地址建立连接;若没有,就阻塞等待;

为了在套接字中有到来的连接时得到通知,可以使用select()或poll()。

当尝试建立新连接时,系统发送一个可读事件,然后调用accept()为该连接获取套接字。

另一种方法是,当套接字中有连接到来时设定套接字发送SIGIO信号。

8 recv

  • 函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

​ sockfd : 发送方的socket 文件描述符
​ buf : 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据
​ len : 可接收数据的最大长度
​ flags : 通常设置为0,其它见下方说明

  • 使用方法:
recv(clifd,recv_buf,20,0);
  • 函数功能

recv()用来接收远端主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间, 参数len 为可接收数据的最大长度

同步Socket的recv函数的执行流程:当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,

  • 返回值说明

如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;

如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕;

当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数;

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

  • 参数Flags值说明

参数 flags 一般设0. 其他数值定义如下:
1 MSG_OOB 接收以out-of-band 送出的数据.
2 MSG_PEEK 返回来的数据并不会在系统内删除, 如果再调用recv()会返回相同的数据内容.
3 MSG_WAITALL 强迫接收到len 大小的数据后才能返回, 除非有错误或信号产生.
4 MSG_NOSIGNAL 此操作不愿被SIGPIPE 信号中断返回值成功则返回接收到的字符数, 失败返回-1,错误原因存于errno 中.

9 send

  • 函数原型:
ssize_t send(int sockfd, const void *buf, size_t len, int flags)

sockfd : 发送方的socket 文件描述符
buf : 要发送的数据buf
len : 发送数据的长度
flags : 通常设置为0,其它见下方说明

  • 使用方法:
send(clifd, send_buf, 255, 0);
  • 函数功能

同步Socket的send函数的执行流程:当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度(因为待发送数据是要copy到套接字s的发送缓冲区的,注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里):

1.如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;

2.如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么 send就比较s的发送缓冲区的剩余空间和len:

(i)如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完;

(ii)如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里。

3.如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

  • 注意

send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)

注意事项

1 send函数和write函数的区别是什么?

发送消息时,可以用send()也可以用write(),区别是:send()函数带有Flag参数,若Flag=0,则两个函数效果相同。

2 服务端将sin_addr设置为INADDR_ANY"的含义是什么?

INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址,这样设置可以在所有的IP 地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址

3 服务端和客户端均在本机运行,如何确定客户端发起连接的服务器地址?

(1)可使用环回地址:127.*

(2)可使用本机内网IP:输入ifconfig/ipconfig命令查看

image-20200426103203575
posted @ 2020-04-22 12:08  cnwanglu  阅读(821)  评论(0编辑  收藏  举报