1我们都知道TCP传输,是基于字节流传输的,所以流与流直接传输就会产生边界问题,我个人对粘包的理解就是,TCP传输无法获悉不同包与包之间的“界限”。
如果对等接受方彼此直接没有约定好传输数据大小的话,就会出现解析数据不准确问题,而且传输数据小于约定大小空间的话,也会出现浪费空间问题,为了解决这种问题,通常才有包头+包体传输,这样对等方就可以分辨出不同的包,所对应的数据。(该办法解决的是发送不定长包)
2.UDP是基于数据包协议,所以也就不存在粘包问题,虽然UDP是不可靠的传输协议,可能存在丢包,失序,错序,重复等问题,但是他的效率是比UDP高的多,我们知道TCP可靠性方面有一个超时重连机制,要让UDP实现可靠传输的话,就需要模拟一套TCP传输机制。
粘包问题解决方法代码如下:回射服务器代码,数据包格式:包头+包体
服务端代码:
/*************************************************************************
> File Name: ser.cpp
> Created Time: Sun 29 Oct 2017 09:00:58 PM CST
************************************************************************/
#include <iostream>
using namespace std;
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//处理粘包问题
//封装read函数
struct packet
{
int len; //包头
char buf[1024]; //包体,实际长度
};
ssize_t readn(int fd, void* buf, size_t count)
{
size_t nleft = count;//剩余的字节数
ssize_t nread; //已经接受的字节数
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd, bufp, nleft))<0)
{
cout << " < 0" << endl;
if(errno == EINTR) //信号打断
continue;
return -1;
}
else if(nread == 0)
{
return count-nleft;
}
bufp += nread;//偏移到屁股
nleft -= nread;
}
return count;
}
//封装write方法
ssize_t writen(int fd, const void* buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nwritten = write(fd, bufp, nleft)) < 0)
{
if(errno == EINTR) //信号中断
continue;
return -1;
}
else if(nwritten == 0)
{
continue;
//return count-nleft;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
/
void do_server(int conn)
{
struct packet recvbuf;
while(1)
{
memset(recvbuf.buf, 0, sizeof(recvbuf.buf));
int ret = readn(conn, &recvbuf.len, 4);
if(ret == -1)
{
perror("read");
exit(1);
}
else if(ret < 4)
{
cout << "clien close\n";
break;
}
int m = recvbuf.len;
cout << "n:" << m << endl;
int n = ntohl(recvbuf.len);
cout << "n:" << n << endl;
ret = readn(conn, &recvbuf.buf, n);
if(ret == -1)
{
perror("read");
exit(1);
}
if(ret < n)
{
cout << "clien close\n";
break;
}
fputs(recvbuf.buf, stdout);
writen(conn, &recvbuf, 4+n);
}
close(conn);
}
int main()
{
int listenfd;
//初始化监听套接字(默认是主动套接字,发起连接)
if((listenfd = socket(AF_INET, SOCK_STREAM, 0))<0)
{
perror("socket created failed");
exit(1);
}
//bind函数,bind一个本地地址到套接字
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8881);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*servaddr.sin_addr.s_addr = inet_addr("127.0.0.1")*/
getsockopt///
int on = 1;//开启地址重复利用
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
perror("setsockopt");
}
//
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
perror("bind a local addr to listenfd failed...");
exit(1);
}
//listen 讲套接字用于监听进入的连接,讲套接字从close状态转换成监听状态
//也就是说调用listen函数就将套接字由主动变为被动套接字(接受连接)
if(listen(listenfd, SOMAXCONN) < 0)//SOMAXCONN 默认最大队列
{
perror("listenfd to listen client connect failed..");
exit(1);
}
//accept 从已经连接的队列中返回是第一个连接,队列为空就阻塞
//成功返回一个新的套接字,也就是连接套接字,并且这个新套接字是主动套接字
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);//必须初始值
int conn;
///多进程处理多客户端连接
pid_t pid;
while(1)
{
if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
{
perror("accept client connect failed...");
exit(1);
}
cout << "Ip:" << inet_ntoa(peeraddr.sin_addr) << " port:" << ntohs(peeraddr.sin_port) << endl;
pid = fork();
if(pid < 0)
{
perror("fork");
exit(1);
}
if(pid == 0) // 子进程处理客户端连接
{
close(listenfd);
do_server(conn);
}
}
return 0;
}
回射服务器,
客户端代码如下
/*************************************************************************
> File Name: ser.cpp
> Created Time: Sun 29 Oct 2017 09:00:58 PM CST
************************************************************************/
#include <iostream>
using namespace std;
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct packet
{
int len; //包头
char buf[1024]; //包体,实际长度
};
ssize_t readn(int fd, void* buf, size_t count)
{
size_t nleft = count;
ssize_t nread;
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd, bufp, nleft))<0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nread == 0)
{
return count-nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void* buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nwritten = write(fd, bufp, nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nwritten == 0)
{
continue;
//return count-nleft;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
/
int main()
{
int sock;
//初始化监听套接字(默认是主动套接字,发起连接)
if((sock = socket(AF_INET, SOCK_STREAM, 0))<0)
{
perror("socket created failed");
exit(1);
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8881);
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//connect ,发起连接
if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect ser is failed\n");
exit(1);
}
struct packet sendbuf;
struct packet recvbuf;
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
int n;
while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL)
{
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n); //网络字节序
writen(sock, &sendbuf, 4+n);//sizeof()发送定长包
int ret = readn(sock, &recvbuf.len, 4);
if(ret == -1)
{
perror("read");
exit(1);
}
else if(ret < 4)
{
cout << "clien close\n";
break;
}
n = ntohl(recvbuf.len);
ret = readn(sock, &recvbuf.buf, n);
if(ret == -1)
{
perror("read");
exit(1);
}
if(ret < n)
{
cout << "clien close\n";
break;
}
fputs(recvbuf.buf,stdout);
memset(recvbuf.buf, 0, sizeof(recvbuf.buf));
memset(sendbuf.buf, 0 ,sizeof(sendbuf.buf));
}
close(sock);
return 0;
}