基础知识:
首先我们来了解Linux网络API:
①socket地址API: | socket最开始的含义:一个IP地址和端口对(ip,port)。他唯一的表示了TCP通信的一端。 |
②socket基础API: | socket主要的API包括创建socket,命名socket,监听socket,接受连接,发起连接,读写数据,获取地址信息,检测带外标记,以及读取和设置socket选项。 |
③网络信息API: | 以实现主机名和IP地址之间的转换;服务名称和端口号之间的转换。这些API都定义在netdb.h头文件中。 |
主机字节序和网络字节序
1.字节序:现代CPU的累加器一次都能装载(至少)4字节(32位机),即一个整数,那么这4个字节在内存中排列的顺序将影响它被累加器转载成的整数值。这就是字节序问题。
大端字节序 | 一个整数的高位字储存在内存的低地址处。 |
小端字节序 | 是指整数的高位字节存储在内存的高地址。 |
主机字节序 | 现代PC大多采用小端字节序,因此成为主机字节序。 |
网络字节序 | 大端字节序也称为网络字节序。 |
Linux提供了4个函数来完成主机字节序列和网络字节序列的转换以保证接收端和发送端的一致性:
#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
//他们的含义很明确,比如htonl表示为:“host to nettwork long”
//在这四个函数中,长整型函数通常用来转换IP地址,
//短整型函数用来转换端口号。
TCP函数了解:
系统调用函数1:创建socket(文件描述符)
函数头文件:
#include<sys/types.h>
#include<sys/socket.h>
函数原型:
int socket(int domain,int type,int protocol); |
函数参数解释:
①domain: | 告诉系统使用哪个底层协议族。对于TCP协议族:PF_INET(Protocol Family of Internet,用于IPv4)或PF_INET6(IPv6); |
②type: | 指定服务类型---》流服务:SOCK_STREAM 和 数据报服务: SOCK_UGRAM。 对于TCP/IP协议族,采用流服务为其运输层采用TCP协议,采用数据报服务表明其运输层采用UDP协议。 |
③protocol: | 在前两个参数构成的协议集合下,在选择一个具体的协议。(前两个参数已经决定了他的值),默认设置为0使用默认协议。 |
函数返回值:
socket系统调用成功时返回一个socket文件描述符,失败返回-1。 |
系统调用函数2:bind 命名socket
函数头文件:
#include<sys/types.h>
#include<sys/socket.h>
函数原型:
int bind(int sockfd , const struct sockaddr* my_addr,socklen_t addrlen); |
函数解释:
创建socket时,我们给它指定了地址族,但是并未指定使用该地址族中的那个具体的socket地址,将一个socket和socket地址绑定称为给socket命名。在服务器程序中,我们通常要命名socket,这样客户端才能知道该如何连接它。而客户端通常不需要命名socket,而是采用匿名的socket地址。
函数参数:
①my_addr bind | my_addr bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符。 |
②addlen | 指出该socket地址的长度。 |
函数返回值:
成功 | 返回0 |
失败 | 返回-1:失败通常有两种情况 EACCES:无权限访问该端口号 EADDRINUSE: 端口号被占用。 |
系统调用函数3:listen //监听socket
函数原型:
int listen(int sockfd,int backlog); |
函数解释:
socket被命名之后还不能马上接受客户连接,我们需要使用系统调用来创建一个监听队列以存放待处理的客户连接,启动监听,不阻塞。
函数参数:
①socket | 指定要被监听的socket。 |
②backlog | 提示内核监听队列的最大长度。监听队列的长度如果超过了backlog,服务器将不受理新的客户连接,客户端也受到ECONNREFUSED错误信息2.2版本之后,其表示完全连接状态的socket的上限。backlog参数的典型值为5。 |
系统调用函数4:accept //接受连接
函数原型:
int accept(int sockfd ,struct sockaddr * addr,socktlen_t *addrlen); |
函数参数:
①sockfd | 是执行过listen系统调用的监听socket。 |
②addr | 用来获取被接受连接的远端 socket的地址。 |
③addrlen | 2中地址长度由addrlen参数指出。 |
函数解释:
该函数只是从监听队列中取出连接,而不论连接处于何种状态(ESTABLISHED状态和CLOSE_WAIT状态),更不关心任何网络状况的变化。
函数返回值:
当accept成功的时间返回一个新的连接的sockfd。
系统调用函数5:connect //发起连接
函数原型:
int connect(int sockfd,const struct sockaddr *serv_add,socklen_t addrlen); |
函数参数:
①serv_addr | 是服务器监听的socket地址 |
②addrlen | 指定了这个地址的长度。 |
函数返回值:
成功 | 一旦连接成功,sockfd就唯一地标识了这个连接,客户端就可以读写sockfd来和服务器通信。 |
失败 | 两种情况 ECONNREFUSED目标端口不在,连接被拒绝;ETIMEDOUT,连接超时。 |
函数6:TCP数据读写
函数原型:
ssize_t recv(int sockfd,void *buf ,size_t len,int flag);//读取 ssize_t send(int sockfd,const void *buf,size_t len,int flags);//写数据 |
函数解释:
recv是读取sockfd上的数据 | |
buf和len | 指定读取缓冲区的位置和大小 |
flag | 为数据收发提供的额外控制,通常设置为0。 |
函数返回值:
成功 | 返回实际读取到的数据的长度,可能小于我们期望的len,返回0,表明对方已经关闭连接了。 |
失败 | 失败返回-1. |
函数7:close //关闭连接
函数原型:
int close(int fd); |
函数参数:
fd | fd 待关闭的socket的sockfd。close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减一,只有当fd为0时,才真正关闭连接。 |
UDP读写函数:
函数原型:
recvfrom函数: | ssize_trecvfrom(int sockfd,void* buf,size_t len, int flags,struct sockaddr* arc_addr,socklen_t* addrlen); |
sendto函数: | ssize_t sendto( int sockfd,const void* buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t addrlen); |
函数介绍:
recvfrom/sendto系统调用函数也可以用于面向连接(STREAM)的socket的数据读写,只需要把最后两个参数都设置为NULL以忽略发送端/接收端的socket的地址(因为我们已经和对方建立了链接,所以已经知道其socket地址。)
recvfrom功能: | 读取sockfd上的数据,UDP通信没有连接的概念,所以我们每次读取的数据都需要获取发送端的socket地址,即参数src_addr所指的内容。 |
sendto功能: | 往sockfd上写数据,dest_addr参数指定接收端的socket地址。 |
参数介绍:
buf,和 len分别指定读/写缓冲区的位置和大小 | |
src_addr 和 dest_addr分别表示该缓冲区的位置信息 |