1、socket()函数
int socket(int domain,int type,int protocol);
socket()中domain用于指定通信域参数,选择通信时的地址族,有两个选项AF_INET和AF_UNIX,这些地址族都在sys/socket.h中定义;其中AF_INET用于网络通信,使用IPv4格式的地址;AF_UNIX用于本地进程通信;
type参数用于指定socket的类型,有三个选项SOCK_STREAM,SOCK_DGRAM,SOCK_RAW;SOCK_STREAM表示使用TCP协议,SOCK_DGRAM表示使用UDP协议。SOCK_RAW表示使用ICMP协议,提供单一的网络访问,一般用于开发人员需要自行设置数据报格式和参数时;
protocol参数一般设置为0,表示使用默认协议;
socket函数调用失败会返回-1,并设置error;
2、bind()函数
int bind(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
bind()函数用于服务器端,服务器的网络地址和端口号通常固定不变,客户端得知服务器的地址和端口号以后,可以主动向服务器请求连接。因此服务器需要调用bind()绑定地址。bingd()函数定义在sys/socket.h头文件中;
sockfd表示socket文件的文件描述符,一般为socket函数的返回值;
addr表示服务器的通信地址,本质为struct sockaddr 结构体类型指针,struct sockaddr结构体定义如下
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
结构体中的成员,sa_data[]表示进程地址;
bind函数中的第三个参数addrlen表示参数addr的长度;addr参数可以接受多种类型的结构体,而这些结构体的长度各不相同,因此需要使用addrlen参数额外指定结构体长度;可以使用下列语句,定义一个struct sockaddr_in类型的结构体
struct sockaddr_in servaddr;//结构体定义
bzero(&servaddr,sizeof(servaddr));//结构体清零
servaddr.sin_family=AF_INET;//设置地址类型为AF_INET
servaddr.si_addr.s_addr=htonl(INADDR_ANY);//设置网络地址为INADDR_ANY
servaddr.sin_port=htons(80);//设置端口为80
bind函数调用成功返回0,否则返回-1,并设置erro;
3、关于bind()函数中sockaddr_int结构体的详解
https://blog.youkuaiyun.com/will130/article/details/53326740/
4、listen()函数
int listen(int sockfd,int backlog);
listen()函数用于服务器,使已绑定的socket等待监听客户端的连接请求,并设置服务器同时可连接的数量,listen函数位于sys/socket.h头文件中;
sockfd为socket文件描述符;
backlog用于设置请求队列最大长度;
listen函数调用成功返回0,否则返回-1;
5、accept()函数
int accept(int sockfd,struct sockaddr * addr,socklen_t * addrlen);
sockfd的参数为listen()函数返回的监听套接字;
addr是一个传出参数,表示客户端的地址,该参数设置为NULL时,表示不关心客户端的地址。
addrlen为一个传入传出参数,传入时为函数调用时提供参数addr的长度,传出时为客户端地址结构体的实际长度;
accept的返回值也是一个套接字,该套接字用于与本次通信的客户端进行数据交互。
6、connect()函数
int connect(int sockfd, const struct sockaddr * addr,socklen_t addrlen);
connect()函数用于客户端,该函数的功能为向服务器发起连接请求。connect()函数存在与sys/socket.h头文件中。
connect函数的参数与bind()函数中的参数形式一致;
7、send()函数
ssize_t send(int sockfd,const void * buf,size-t len,int flags);
send()函数用于向处于连接状态的套接字中发送数据,该函数粗在于函数库sys/socket.h中;
sockfd表示要发送数据的socket文件描述符;
buf为指向要发送数据的缓冲区指针;
len表示缓冲区buf中要发送的数据的长度;
flags参数为调用的执行方式(阻塞/非阻塞),当flags设置为0时,可以使用函数write()来代替send()函数;
8、sendto()、sendmsg()函数
size_t sendto(int sockfd,const void * buf,size_t len,int flags,const struct sockaddr * dest_addr,socklen_t addrlen);
size_t sendmsg(int sockfd,const struct msghdr * msg,int flags);
sendto()函数中的前四个参数都和send()的参数相同,后面两个参数分别用于设置接收数据进程的地址和地址长度;
sendmsg()函数中的第二个参数msg为struct msghdr类型的结构体指针,该参数用于传入目标继承的地址,地址的长度等信息;
若sendto()函数和sendmsg()函数向已连接的进程中发送消息,则忽略参数dest_addr、addrlen和msg结构体中用于传递地址的成员。此时若参数dest_addr和addrlen不为NULL,则可能会返回错误EISCONN或0;
这两个函数调用成功返回0,失败返回-1,并设置errno;
函数调用成功并不代表接收端一定能接受到数据;
9、recv()、recvfrom()、recvmsg()函数详解
size_t recv(int sockfd,void * buf,size_t len,int flags);
recv()函数的参数列表与send()函数的参数列表形式相同,代表的含义也基本对应,只是参数sockfd表示用于接收数据的socket文件描述符;
此外函数read()、recvfrom()、recvmsg()函数也可用于接收信息,recvfrom、recvmsg函数与sendto、sendmsg相对;
这几个函数调用成功将返回接收到的字节数,调用失败将返回-1并设置errno;
10、close()函数
int close(int fd);
close()函数存在于函数库unistd.h函数库中;
close()函数用于释放系统分配给套接字的资源,该函数即文件操作中常用的close函数。
参数fd为需要关闭的套接字文件描述符;
调用成功返回0,否则返回-1并设置errno;
11、socket通信流程
根据进程在网络通信中使用的协议,可以将socket通信方式分为两种:面向连接、基于TCP协议的通信;另一种是面向无连接,基于UDP协议的通信。
当使用面向连接的方式进行通信时,服务器和客户机先各自创建socket文件,服务器调用bind()函数绑定服务器端口和地址。之后服务器通过接口listen()设置可连接的数量。若客户端需要与服务器进行交互,客户端会调用connect()函数向已知服务器地址端口发送连接请求并 阻塞 等待服务器应答。服务器监听到请求连接以后,会调用accept()函数试图进行连接。若服务器为到达最大连接数量,便成功建立连接,此后客户端解除阻塞,两端可正常通信。否则服务器忽略本次连接。最后当通信完成以后,双方各自调用close()函数,关闭socket文件,释放资源。
当使用面向无连接的方式进行通信的时候,服务器和客户机各自创建自己的socket文件,再由服务器调用bind()函数绑定服务器地址和端口。此后通信双方可以开始通信,需要注意的,因为服务器与客户机尚未建立连接,所以客户端每次向服务器发送数据时,都需要额外的指定服务器的地址端口。同样的,若服务器需要向客户端发送数据,服务器也需要额外指定地址端口。通信结束以后,通信双方需要调用close()函数,关闭socket文件,释放资源。
12、网络字节顺序
高字节和低字节
一. 计算机的数值应视为连续若干个二进制位的集合;
二. 所谓高、低字节就是此集合中位地址高/低的二进制位集合;
三. 例如定义一个unsigned short型变量在0x1234 5678,那么这个变量的地址就是0x1234 5678,占用0x1234 5678与0x1234 5679两字节存储空间,其中0x1234 5678是低字节、0x1234 5679是高字节。
四、 一个16进制数有两个字节组成,例如:A9。
高字节就是指16进制数的前8位(权重高的8位),如上例中的A。
低字节就是指16进制数的后8位(权重低的8位),如上例中的9。
大端模式:将数据的高字节保存在内存的低地址,将数据的低字节保存在内存的高地址,这种存放模式称为大端模式;
小端模式:将数据的高字节保存在内存的高地址,将数据的低字节保存在内存的低地址,这种存放模式称为小端模式;
磁盘文件中的多字节数据相对于文件中的偏移地址有大端、小端之分,内存中的多字节数据相对于内存地址也有大端、小端之分;同样的,网络数据流同样有大端、小端之分;
发送端主机在发送数据时,通常将发送缓冲区中的数据按内存地址从低到高顺序依次发出;接收端主机从网络上接收数据时,会将从网络上接收的数据按内存地址从低到高的顺序依次保存。因此,网络数据流的地址这样规定:先发出的数据占据低地址,后发出的数据占据高地址;
TCP/IP协议规定,网络数据流应采用大端模式存储。举例说明:假设从网络获取了一个16位的数据0x1121,即十进制的4385,那么发送端主机在发送该数据时,会先发送低地址的数据0x21,再发送高地址的数据0x11;假设接收端为此数据分配的地址为0、1,高字节的数据0x11被存放到低地址0所对应的空间;低字节的数据0x21被存放到高地址1所对应的空间。
但是如果发送端主机采用小端模式,那么16位的数据会被解释为0x2111,这显然不正确。因此发送端主机将数据填充到发送缓冲区前要先进性字节顺序转换。
linux提供了一些字节顺序转换的函数,在函数库arpa/inet.h中
unit32_t htonl(unit32_t hostlong);
unit16_t htons(unit16_t hostshort);
unit32_t ntohl(unit32_t netlong);
unit16_t ntohs(unit16_t nettshort);
h代表主机
n代表网络
l代表32位长整型
s代表16位短整型
htonl 表示host to net long
若主机采用大端模式,则这些函数不转换,将参数原样返回。只有主机采用小端模式,参数的字节顺序才会转换
13、IP地址转换函数
常见的IP地址格式类似192.168.10.1,这是一个IPv4格式的地址,但是这种格式只是为了方便用户对其操作。若要计算机能够识别,需要先将其由文本格式转换为二进制格式。
早期linux系统中常用以下函数转换IP地址
int inet_aton(const char * cp,struct in_addr * inp);
in_addr_t inet_addr(const char * cp);
char * inet_ntoa(struct in_addr_in);
但是以上函数只能处理IPv4地址。如今linux使用以下函数进行IP地址转换;不仅能转换IPv4地址,也能转换IPv6地址;这些函数存在函数库arpa/inet.h中;
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()会先将字符串src转换为af地址族中的网络地址结构,进而将转换后的网络地址结构存储到参数dst所指的缓冲区中,其中参数af的值必须是AF_INET或AF_INET6。
函数inet_ntop()会将af地址族中的网络地质结构src转换为字符串,再将获得的地址字符串存储到参数dst所指的缓冲区中。
以上两个函数所需要转换IPv4和IPv6这两种形式的地址,因此用来传递地址的参数类型为void *;
14、sockaddr数据结构
IPv4和IPv6的地址格式定义在netinet/in.h中:IPv4地址用结构体sockaddr_in表示,该结构体中包含16位端口号和32位的IP地址;IPv6地址用结构体sockaddr_in6表示,该结构体中包含16位端口号和128位的IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用结构体sock_addr_un表示。
各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现都有长度字段,如Linux就没有),后16表示地址类型。IPv4、IPv6和UNIX Domain Socket的地址类型分别为AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sickaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。也是因为如此,socket API可以接收各种类型的sockaddr结构体指针作为参数,例如bind()、accept()等函数,这些函数应该设计成void* 类型以便接收各种类型的指针。单数sock API的实现遭遇ANSI C标准化,那时候还没有void* 类型,因此这些函数都用struct sockaddr * 类型表示,但是在传递参数之前要进行强制类型转换,如:
struct sockaddr_in servaddr;
bind(listen_fd,(struct sockaddr *)&servaddr,sizeof(servaddr));