TCP通信编程
TCP通信编程的流程:
模拟服务端和客户端通信的过程:
接口的使用:
1.创建套接字:
int socket(int domain,int type,int protocol)
domain:地址域 AF_INET/AF_INET6
type:SOCK_STREAM(TCP流式套接字) SOCK_DGRAM(UDP数据报套接字)
protocol:IPPROTO_TCP IPPROTO_UDP
返回socket操作句柄
2.绑定地址信息:
int bind(int sockfd,(struct sockaddr*)addr,socklen_t len)
3.开始监听
listen(int sockfd,int backlog)
sockfd:将哪个socket设置为监听套接字,就传入哪个套接字的句柄
backlog:同一时间的并发连接数,决定同一时间最多接受多少个客户端的连接请求
如果有一个恶意主机同一时间向服务器无限制的发送连接请求,服务端是不是要一直为每一条请求创建套接字?如果是这样,最终只有一个结构就是资源耗尽,系统崩溃。
backlog就是解决这个问题的,前面流程里面说到,监听阶段,监听服务器收到连接请求创建套接字放入未完成连接队列中,绑定好五元组信息后放入已完成连接队列中。
backlog就是决定这个未完成连接队列的节点个数的。但只决定同一时间的节点个数,但不决定系统可以接受的最大客户端连接。
4.获取新建连接
从已完成连接队列中取出一个socket,返回这个socket的操作句柄,这样就可以通信了。
int accept(int sockfd,(struct sockaddr*)cli_addr,socklen* len);
sockfd : 监听套接字,表示要获取哪个tcp服务端套接字的新建连接(可能会有很多TCP服务端)
cli_addr:这个新建套接字的客户端地址信息
len:地址信息长度
返回值:新建套接字的操作句柄 --- 外部程序中的套接字句柄
5.收发数据
ssize_t recv(int sockfd,char* buf,int len,int flag);
默认阻塞,没有数据则等待,连接断开返回0,不在阻塞
ssize_t send(int sockfd,char* data,int len,int flag);
默认阻塞,缓冲区满了则等待,连接断开则出发SIGPIPE异常
6.关闭套接字
close(sockfd); #include<unistd.h>
7.发送连接请求
int connect(int sockfd,struct sockaddr* srv_addr, int len);
srv_addr:服务端的地址信息
connet这个接口中也会描述服务端的地址信息(五元组),所以收发数据只需要直接收发即可。
简易TCP通信流程
socket类的封装
封装一个TcpSocket类,实例化的每一个对象都是一个udp通信连接,通过这个对象的成员方法完成通信流程。
class TcpSocket{
public:
TcpSocket():_sockfd(-1);
bool Socket();
bool Bind(const std::string& ip,uint16_t port);
bool Listen(int backlog = MAX_LISTEN);
bool Accept(TcpSocket* new_sock,std::string* ip =NULL,uint16_t* port);
bool recv(std::string* buf);
bool send(const std::string& data);
bool Close();
bool Connect(const std::string& ip,uint16_t port);
private:
int _sockfd;
#pragma once
#include<iostream>
#include<string>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
class TcpSocket
{
private:
int sockfd;
public:
TcpSocket()
{
if(!Socket())
{
cout<<"socket create failed"<<endl;
}
}
~TcpSocket()
{
Close();
}
public:
bool Socket()
{
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == 0)
{
cerr<<"scoket error"<<endl;
return false;
}
return true;
}
//有可能错
void Addr(struct sockaddr_in* addr, const string& ip,const uint16_t& port)
{
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
addr->sin_addr.s_addr = inet_addr(ip.c_str());
}
bool Bind(const string& ip,const uint16_t& port)
{
struct sockaddr_in addr;
Addr(&addr,ip,port);
socklen_t len = sizeof(addr);
int ret = bind(sockfd,(struct sockaddr*)&addr,len);
if(ret < 0)
{
cerr<<"bind errror"<<endl;
return false;
}
return true;
}
bool Listen()
{
int ret = listen(sockfd,5);
if(ret == -1)
{
cerr<<"listen error"<<endl;
return false;
}
return true;
}
bool Connect(const string& ip,const uint16_t& port)
{
struct sockaddr_in addr;
Addr(&addr,ip,port);
socklen_t len = sizeof(addr);
int ret = connect(sockfd,(struct sockaddr*)&addr,len);
if(ret < 0)
{
cerr<<"connect error"<<endl;
return false;
}
return true;
}
bool Accept(TcpSocket* new_sock,string* ip = NULL,uint16_t* port = NULL)
{
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
int newfd = accept(sockfd,(struct sockaddr*)&addr,&len);
if(newfd < 0)
{
cerr<<"accept error"<<endl;
return false;
}
new_sock->sockfd = newfd;
if(ip != NULL)
{
*ip = inet_ntoa(addr.sin_addr);
}
if(port != NULL)
{
*port = ntohs(addr.sin_port);
}
return true;
}
bool Recv(string* buf)
{
char tmp[4096] = {0};
int ret = recv(sockfd,tmp,4095,0);;
if(ret <0)
{
cerr<<"recv error"<<endl;
return false;
}
*buf = tmp;
buf->assign(tmp);
return true;
}
bool Send(const string& data)
{
//UDP是数据报传输,整条交付
//而TCP是字节流传输,能传输多少就传输多少,对数据边界并没有进行区分
//如果对方的缓冲区只剩了50个字节的空间,send给对方100个字节,就只能成功发送50个字节,并返回传送大小50字节
size_t total = 0 ;
while(total < data.size())
{
int ret = send(sockfd,&data[0]+total,data.size()-total,0);
if(ret < 0)
{
cerr<<"send error"<<endl;
return false;
}
total+=ret;
}
return true;
}
bool Close()
{
close(sockfd);
return true;
}
};