C++后端开发(2.2.3)——POSIXAPI解析

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

C++后端开发(2.2.3)——POSIXAPI解析_第1张图片

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 

ssize_t read(int fd, void *buf, size_t count); // 目的fd 消息 消息长度
ssize_t write(int fd, const void *buf, size_t count);

#include 
#include 

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. 在包结束后添加分割符,这里注意分割符要选择不经常使用的
    注意: 1优于2,因为,添加分割符,需要遍历整个消息来找到分隔符,这样大大影响效率,但是2可以结合1使用。

4.9 close

#include 
int close(int fd);

close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

4.9.1 过程分析

1.正常情况下一方调用close情况如下图:
C++后端开发(2.2.3)——POSIXAPI解析_第2张图片
2.当双方同时调用close,如下图:
C++后端开发(2.2.3)——POSIXAPI解析_第3张图片
当同时发送close时,两边同时发送fin 和ack 这时候调用time_wait等待消息发送完毕。

  1. Fin_wait_1作用?
    等待对方回复,超时自动重发fin。
  2. Fin_wait_2作用?
    等待对方业务逻辑处理后,发送fin包。这里有可能出现死等待的情况服务器如果出现大量的Fin_wait_2可能需要考虑是不是没有close,或者close之前做了耗时操作。
  3. time_wait 作用?
    防止最后一个ACK没有顺利到达对方,超时重新发送ack。time_wait时常一般是120s可以修改。
    4.服务器掉线重启出现端口被占用怎么办?
    其实主要是由于还处于time_wait状态,端口并没有真正释放。这时候可以设置SO_REUSEADDR属性,保证掉线能马上重连。

你可能感兴趣的:(Linux,c++,linux,网络)