C++ POSIX API超详细分析
目录
- 1.网络通信
- 2.posix API
- 3.POSIX网络API
- 4.函数内部过程解析
- 4.1 socket套接字创建
- 4.2 bind 绑定端口
- 4.3 网络字节序和主机字节序
- 4.4 listen监听fd
- 4.5 connect发起连接请求
- 4.6 accept()接收请求建立连接
- 4.7 消息的发送和接收
- 4.8 粘包问题
- 4.9 close
1.网络通信
1.消息传递(管道、FIFO、消息队列)
2.同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
3.共享内存(匿名的和具名的)
使用TCP/IP协议 通过socket完成
2.posix API
目的:实现不同系统上的源代码的可移植性。
举例:linux和windows都要实现基本的posix标准,linux把fork函数封装成posix_fork(随便说的),windows把creatprocess函数也封装成posix_fork,都声明在unistd.h里。这样,程序员编写普通应用时候,只用包含unistd.h,调用
3.POSIX网络API
4.函数内部过程解析
4.1 socket套接字创建
int socket(int domain, int type, int protocol); //参数分别是地址族、 套接字类型和协议 //AF_INET SOCK_STREAM 可默认为0 //IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等
4.2 bind 绑定端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:即socket描述字
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 监听时,监听所有的地址 server_addr.sin_port = htons(port);
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET 协议族*/ in_port_t sin_port; /* port in network byte order 端口号*/ struct in_addr sin_addr; /* internet address IP地址*/ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。但是我认为客户端也可以绑定。
4.3 网络字节序和主机字节序
小端:小字节放前面,大字节放后面 大端:大字节放前面,小字节放后面 转换端口 uint16_t htons(uint16_t hostshort); //主机字节序->网络字节序 uint16_t ntohs(uint16_t netshort); //网络字节序->主机字节序 转IP htonl(uint32_t hostlong);//主机字节序->网络字节序 ntohl(uint32_t netlong);//网络字节序->主机字节序
4.4 listen监听fd
int listen(fd,size) // 无错返回0,错误返回-1
服务端调用listen()后,开始监听网络上发送给socket的连接请求。
也就是说,开始接收请求了。
4.5 connect发起连接请求
int connect(int sockfd, const struct sockaddr *serv_addr, int socklen_t addrlen);
4.6 accept()接收请求建立连接
accept()函数只做两件事,将连接请求从全连接队列中取出,给该连接分配一个fd并返回。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
4.7 消息的发送和接收
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count); // 目的fd 消息 消息长度 ssize_t write(int fd, const void *buf, size_t count); #include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
4.8 粘包问题
2个数据包同时被提出,但是由于数据是在一起的,没有办法分离
解决方法:
- 在包头添加一个数据包长度的字段,标明长度来确定数据包
- 在包结束后添加分割符,这里注意分割符要选择不经常使用的
注意: 1优于2,因为,添加分割符,需要遍历整个消息来找到分隔符,这样大大影响效率,但是2可以结合1使用。
4.9 close
#include <unistd.h> int close(int fd);
close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。
过程分析
1.正常情况下一方调用close情况如下图:
2.当双方同时调用close,如下图:
当同时发送close时,两边同时发送fin 和ack 这时候调用time_wait等待消息发送完毕。
Fin_wait_1作用?
等待对方回复,超时自动重发fin。
Fin_wait_2作用?
等待对方业务逻辑处理后,发送fin包。这里有可能出现死等待的情况服务器如果出现大量的Fin_wait_2可能需要考虑是不是没有close,或者close之前做了耗时操作。time_wait 作用?
防止最后一个ACK没有顺利到达对方,超时重新发送ack。time_wait时常一般是120s可以修改。
服务器掉线重启出现端口被占用怎么办?
其实主要是由于还处于time_wait状态,端口并没有真正释放。这时候可以设置SO_REUSEADDR属性,保证掉线能马上重连。
到此这篇关于C++ POSIXAPI超详细分析的文章就介绍到这了,更多相关C++ POSIXAPI内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!