网络通信:
底层遵循TCP/IP协议没在系统中以socket接口方式呈现
基于TCP协议的网络通信模型:
服务端 客户端
创建socket对象 创建socket对象
准备通讯地址(本机IP)+端口号 准备通讯地址(目标公网IP)+端口号
绑定socket和通信地址 ...
设置监听和排队的数量 ...
等待客户端连接 连接服务端
分配新的socket对象 ...
接收请求 发送请求
相应请求 接受响应
关闭socket 关闭socket
使用到的函数:
int socket(int domain, int type, int protocol);
功能:创建socket对象
domain:
AF_UNIX, AF_LOCAL 本地通信,进程间通信
AF_INET 基于IPv4地址通信
AF_INET6 基于IPv6地址通信
type:
SOCK_STREAM 数据流协议
protocol:
特殊的通信协议,一般不用,写0即可
返回值:成功返回socket描述符,失败返回-1
#include <netinet/in.h> [vim /usr/include/linux/in.h]
struct sockaddr_in {
__kernel_sa_family_t sin_family; //domain写什么就填什么
__be16 sin_port; //端口号(大端数据htons)
struct in_addr sin_addr; //IP地址(大端数据)
};
struct in_addr {
__be32 s_addr;
};
大小端转换函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
功能:把4字节的本地字节序转换成网络字节序
uint16_t htons(uint16_t hostshort);
功能:把2字节的本地字节序转换成网络字节序
uint32_t ntohl(uint32_t netlong);
功能:把4字节的网络字节序转换成本地字节序
uint16_t ntohs(uint16_t netshort);
功能:把4字节的网络字节序转换成本地字节序
IPv4地址转换:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
功能:把字符串格式的点分十进制的IP地址,转换成整数形式的IP地址(大端)
例如:inet_addr("192.168.0.1");
char *inet_ntoa(struct in_addr in);
功能:把整数形式的IP地址,转换成字符串格式的点分十进制IP地址
注意:send、recv是tcp协议通信时专用的数据发送和接受函数
但是也可以继续使用write、read函数
基于UDP协议的网络通信模型:
接受者 发送者
创建socket 创建socket
准备通信地址 准备通信地址
绑定 ...
接受请求 发送请求
响应请求 接收响应
关闭 关闭
使用到的函数:
int socket(int domain, int type, int protocol);
功能:创建socket对象
domain:
AF_UNIX, AF_LOCAL 本地通信,进程间通信
AF_INET 基于IPv4地址通信
AF_INET6 基于IPv6地址通信
type:
SOCK_DGRAM 数据报协议
protocol:
特殊的通信协议,一般不用,写0即可
返回值:成功返回socket描述符,失败返回-1
UDP专用的收发数据函数:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据,UDP专用
sockfd:socket描述符
buf:待发送的数据的内存首地址
len:要发送的字节数
flags:是否阻塞 写0即可(阻塞)
dest_addr:通信目标的地址
addrlen:地址结构体的字节数
返回值:成功发送的字节数,
-1 出现错误
0 通信关闭
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据,UDP专用
sockfd:socket描述符
buf:存储读取到的数据的缓冲区首地址
len:缓冲区的大小
flags:是否阻塞 写0即可(阻塞)
src_addr:用于存储发送者的地址结构体
addrlen:既是输入也是输出
1、告诉系统src_addr结构体的字节数
2、反馈实际接收到的发送者的地址结构体字节数
返回值:成功发送的字节数,
-1 出现错误
0 通信关闭
封装TCP/UDP通信代码:
1、gcc -fpic -c network.c
2、gcc -shared -fpic network.o -o libnw.so
3、sudo cp libnw.so /usr/lib
4、sudo cp network.h /usr/include/
多路复用:
使用一个进程(且只有一个主线程)同时监控若干个文件描述符的读写,这种读写模式叫做多路复用
多用于TCP客户端,用于监控若干个客户端的链接和数据的收发
优点:不需要频繁的创建进程、销毁进程,从而达到节约内存资源、时间资源,也能避免进程之间的竞争、等待
缺点:单个客户端的任务不能耗时太长,否则其他客户端就会感知到
适合并发量高,任务量短小的情景,例如:Web服务器
select:
fd_set是文件描述符的集合,是要通过以下函数进行操作
void FD_CLR(int fd, fd_set *set);
功能:从集合中删除fd文件描述符
int FD_ISSET(int fd, fd_set *set);
功能:判断集合中是否存在fd文件描述符
void FD_SET(int fd, fd_set *set);
功能:把文件描述符添加到集合中
void FD_ZERO(fd_set *set);
功能:清空集合
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功能:监控多个文件描述符的读、写、异常操作
nfds:被监控的文件描述符中最大的+1
readfds:监控读操作的文件描述符集合
writefds:监控写操作的文件描述符集合
exceptfds:监控异常操作的文件描述符集合
timeout:设置超时时间
NULL 一直阻塞,知道某个文件描述符产生变化
大约0 等待超时时间,超时后返回0
0秒0毫秒 非阻塞
返回值:监控的发生相关操作的文件描述符的个数,超时返回0,错误返回-1
注意:readfds,writefds,exceptfd 这三个参数即是输入也是输出,也就是说调用select时这三个即合理秒需要存放着被监控的文件描述符,当函数因为监控的文件发生变化而返回时,发生变化的文件描述符会存在集合中没发生变化的就删除掉了
select设计不合理的地方:
1、每次调用select时都需要重新向他传递被监控者的集合
2、调用结束后若想要知道哪些文件喵师傅发生了相关操作,需要把所有的监控的文件描述符都测试一遍
select的优点:
他是最早的多路复用的函数,几乎所有的操作系统都支持,程序的的兼容性高
pselect:[只是select的增强版,本质上没有区别,缺点没变]
int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);
功能:与select大致相同
区别:
1、超时时间的类型不同,pselect的精度更高
2、pselect的timeout,即是输入也是输出,可以返回剩余的时间,但是select只是输入,不能输出
3、pselect监听时可以通过sigmask参数设置要想要在监听时屏蔽的信号,可以保证pselect的监听不被信号干扰
poll:
struct pollfd {
int fd; //被监听的文件描述符 输入
short events; //要监听的事件 输入
short revents; //实际监听到的事件 输出
{
POLLIN 普通优先级的读事件
POLLPRI 高优先级的读事件
POLLOUT 普通优先级的写事件
POLLRDHUP 对方socket关闭
POLLERR 错误事件
POLLHUP 对方挂起事件
POLLNVAL 非法的文件描述符
}
};
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:pollfd结构体数组
nfds:数组的长度,也就是数组成员个数
timeout:超时时间
返回值:监听到发生相关操作的描述符的个数,超时返回0,错误返回-1
epoll
#include <sys/epoll.h>
int epoll_create(int size);
功能:创建epoll对象,该对象可以保存被监控的描述符
size:epoll能保存描述符的数量
返回值:创建成功的epoll对象的描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:控制epoll对象,添加、删除描述符
epfd:epoll对象的描述符,即epoll_create的返回值
op:
EPOLL_CTL_ADD 添加socket文件描述符
EPOLL_CTL_MOD 删除socket文件描述符
EPOLL_CTL_DEL 修改要监控的事件
fd:
想要操作的socket文件呢描述符
event:
想要监控的事件,可以参考poll的事件 前面加E
返回值:成功返回0,失败返回-1
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
功能:监控文件描述符,并返回产生事件的文件描述符
epfd:epoll对象的描述符,即epoll_create的返回值
events:输出型参数,用于获取产生事件的文件描述符
maxevents:可以返回的最大的事件数
timeout:设置超时时间
返回值:事件发生的描述符的数量
epoll的优点:
1、只需要添加一次描述符,不需要重复赋值
2、会把发生变化的文件描述符返回,因此不需要遍历全部都文件描述符即可
3、编程结构简洁
epoll的条件触发和边缘触发:
条件触发:当文件缓冲区中又要读取的数据时就会触发事件 [像键盘]
边缘触发:当数据发送时只触发一次 [像鼠标]
1、事件监控设置为EPOLLET
2、要循环读取缓冲区直到缓冲区读完
3、要以非阻塞的模式读取
4、返回-1表示读取完毕,才能进行下一次epoll_wait监控事件
优点:减低事件触发的次数