TCP网络通信
TCP通信是面向连接、可靠传输、面向字节流的
服务端流程
- 创建套接字:内核中创建socket结构体
- 绑定地址信息:通过socket描述源端地址信息
- 开始监听:告诉OS开始接收连接请求,由于TCP是面向连接的,通信前要建立连接。服务端要**接受客户端的连接请求【监听套接字】**会为客户端创建新的socket【通信套接字】,新的套接字中包含源端信息和对端信息,只用于和对应的客户端进行通信
- 服务端程序中获取新建通信套接字的操作句柄描述符,后续的通信都是通过这个操作句柄完成的
- 接收、发送数据:建立连接后直接进行
- 关闭套接字:释放资源
客户端流程
- 创建套接字
- 绑定地址信息
- 向服务端发起连接请求
- 收发数据
- 关闭套接字
TCP编程socket接口
1.创建套接字:int socket(int domain,int type,int protocol)
IPv4:AF_INET;流式套接字:SOCK_STREAM;TCP:IPPROTO_TCP
2.绑定地址信息:int bind(int sockfd,struct sockaddr *addr,socklen_t len);struct sockaddr_in;
3.服务端开始监听:int listen(int sockfd,int backlog)
SYN泛型攻击 :恶意主机不断向服务端发送连接请求,由于服务器要为连接创建socket,将会瞬间资源耗尽,服务器崩溃
监听接口:int listen(int sockfd,int backlog)
因此,服务端用connection pending queue来存放连接套接字,backlog参数决定该队列的最大节点数量,如果队列满了还有新连接请求到来就将其丢弃
4.获取新建通信套接字的操作句柄:从内核指定socket的pending queue中取出一个socket,与指定的客户端通信,返回操作句柄
int accpet(int sockfd,struct sockaddr *addr,socklen_t len)//len:指定地址信息想要的长度及返回实际的长度
5.通过新获取的套接字操作句柄与指定客户端通信
接收数据:ssize_t recv(int sockfd,char* buf,int len,int flag)//成功返回实际读取的数据长度,连接断开返回0,读取失败返回-1
发送数据:ssize_t send(int sockfd,char* data,int len,int flag)//成功返回实际发送的数据长度,连接断开触发异常导致进程退出,失败返回-1
6.关闭套接字:int close(int fd)
7.客户端向服务端发送连接请求:int connect(int sockfd,int sockaddr *addr,socklen_t len)
通过上面的理解写出以下流程:
while(1)
{
int newfd=accept();//获取新连接
//通信
ssize_t ret=recv();
ssize_t ret=send();
}
运行后发现服务端客户端无法持续通信:
accept是阻塞函数,当没有获取到新连接时阻塞等待直到有新连接
recv/send也是阻塞函数,接收缓冲区没有数据(客户端没有发送数据)recv阻塞,发送缓冲区数据满了send阻塞
这两处都可能发生阻塞导致流程无法推进
解决
防止流程阻塞,就要保证一个执行流只负责一个功能:让一个执行流来获取新连接,获取成功后创建新执行流与客户端通信
实现
封装TCPsocket类向外提供简单接口
#include <cstdio>
#include <string>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BACKLOG 10
#define CHECK_RET(q) if((q)==false){return -1;}
class TcpSocket
{
public:
TcpSocket():_sockfd(-1){
}
int GetFd() {
return _sockfd;
}
void SetFd(int fd) {
_sockfd = fd;
}
//创建套接字
bool Socket() {
//地址域,套接字类型,协议类型
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_sockfd < 0) {
perror("socket error");
return false;
}
return true;
}
void Addr(struct sockaddr_in *addr, const std::string &ip, uint16_t port) {
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
inet_pton(AF_INET, ip.c_str(), &(addr->sin_addr.s_addr));
}
//绑定地址信息
bool Bind(const std::string &ip, const uint16_t port) {
//定义IPv4地址结构
struct sockaddr_in addr;
Addr(&addr, ip, port);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
if (ret < 0) {
perror("bind error");
return false;
}
return true;
}
//服务端开始监听
bool Listen(int backlog = BACKLOG) {
int ret = listen(_sockfd, backlog);
if (ret < 0) {
perror("listen error");
return false;
}
return true;
}
//客户端发起连接请求
bool Connect