粘包/分包
发送得太快后会出现粘包。
为什么粘包?TCP是面向字节流的协议,UDP是面向报文的协议。
数据流会把小的报文粘在一起,也会把大的报文拆分。
这里采用自定义消息结构解决的粘包分包。在内容前面加上长度。
写函数
bool TcpWrite(const int sockfd,const char *buffer,const int ibuflen)
{
if (sockfd==-1) return false;
int ilen=0; // 报文长度。
// 如果ibuflen==0,就认为需要发送的是字符串,报文长度为字符串的长度。
if (ibuflen==0) ilen=strlen(buffer);
else ilen=ibuflen;
int ilenn=htonl(ilen); // 把报文长度转换为网络字节序。
char TBuffer[ilen+4]; // 发送缓冲区。
memset(TBuffer,0,sizeof(TBuffer)); // 清区发送缓冲区。
memcpy(TBuffer,&ilenn,4); // 把报文长度拷贝到缓冲区。
memcpy(TBuffer+4,buffer,ilen); // 把报文内容拷贝到缓冲区。
// 发送缓冲区中的数据。
if (Writen(sockfd,TBuffer,ilen+4) == false) return false;
return true;
}
读函数
bool TcpRead(const int sockfd,char *buffer,int *ibuflen,const int itimeout)
{
if (sockfd==-1) return false;
// 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
if (itimeout>0)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,itimeout*1000) <= 0 ) return false;
}
// 如果itimeout==-1,表示不等待,立即判断socket的缓冲区中是否有数据,如果没有,返回false。
if (itimeout==-1)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,0) <= 0 ) return false;
}
(*ibuflen) = 0; // 报文长度变量初始化为0。
// 先读取报文长度,4个字节。
if (Readn(sockfd,(char*)ibuflen,4) == false) return false;
(*ibuflen)=ntohl(*ibuflen); // 把报文长度由网络字节序转换为主机字节序。
// 再读取报文内容。
if (Readn(sockfd,buffer,(*ibuflen)) == false) return false;
return true;
}
这两个函数用到的也是被封装的读写函数。
原因是send和recv可能一次不能处理所用数据,要用while。
Readn
bool Readn(const int sockfd,char *buffer,const size_t n)
{
int nLeft=n; // 剩余需要读取的字节数。
int idx=0; // 已成功读取的字节数。
int nread; // 每次调用recv()函数读到的字节数。
while(nLeft > 0)
{
if ( (nread=recv(sockfd,buffer+idx,nLeft,0)) <= 0) return false;
idx=idx+nread;
nLeft=nLeft-nread;
}
return true;
}
Writen
bool Writen(const int sockfd,const char *buffer,const size_t n)
{
int nLeft=n; // 剩余需要写入的字节数。
int idx=0; // 已成功写入的字节数。
int nwritten; // 每次调用send()函数写入的字节数。
while(nLeft > 0 )
{
if ( (nwritten=send(sockfd,buffer+idx,nLeft,0)) <= 0) return false;
nLeft=nLeft-nwritten;
idx=idx+nwritten;
}
return true;
}
连接初始化的时候要忽略SIGPIPE
信号。(往一个已经关掉的socket里面写入数据)
多进程的网络服务端
使用fork实现。有了连接后,父进程继续回到Accept()
状态,同时关闭客户端的socket;子进程关闭listen的socket。
多进程的退出,父子进程都重新定向下自己的信号处理函数。都关闭下自己没有关闭的socket。
文件传输系统
先实现上传文件名,服务端回复;再实现文件内容的上传。
1000个字节为一次,上传内容。之后根据服务端的报文选择删除本地文件或转存。
异步通信机制实现快速传输
使用poll实现
在TcpRead里面
// 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
if (itimeout>0)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,itimeout*1000) <= 0 ) return false;
}
// 如果itimeout==-1,表示不等待,立即判断socket的缓冲区中是否有数据,如果没有,返回false。
if (itimeout==-1)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,0) <= 0 ) return false;
}