TCP与UDP传输数据的区别
TCP是基于字节流的传输服务,无边界。而UDP是基于消息的传输服务,传送数据报有边界。
TCP产生粘包问题的原因
粘包问题主要是因为传输数据没有被接收方完全接收。可能是因为MSS的大小与MTU、或者套接口发送缓冲区的长度。
粘包问题的解决方案
缺点
增加网络的负担,因为无论你想要发送的是多大,使用的空间都是指定的定长字节。可以通过自己定义包的结构来优化
readn,writen
readn方法
struct packet{
int len;
char buf[1024];
}
ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
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;//这里传输的是整个函数已经接收了多少个。
} else {
bufp += nread;//更新
nleft -= nread;
}
}
return count;
}
writen方法
可以读取指定长度的数据。然后使用特殊的报文结构,添加前四个字节的包头来表示包体的长度,用于解决粘包问题。
struct packet{
int len;
char buf[1024];
}
ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
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 (nread == 0){
continue;//如果等于零,对于write操作等于无事发生
} else {
bufp += nwritten;//更新
nleft -= nwritten;
}
}
return count;
}
回射客户/服务器
服务器
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
struct packet{
int len;
char buf[1024];
}
ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
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;//这里传输的是整个函数已经接收了多少个。
} else {
bufp += nread;//更新
nleft -= nread;
}
}
return count;
}
ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
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 (nread == 0){
continue;//如果等于零,对于write操作等于无事发生
} else {
bufp += nwritten;//更新
nleft -= nwritten;
}
}
return count;
}
void do_service(int conn){
struct packet recvbuf;
while(1){
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = readn(conn, &recvbuf.len, 4);//这里使用readn,先接收四个字节的包头
if{ret == -1 }{
ERR_EXIT("read");
} else if(ret < 4){
printf("client_close\n");
break;
}
n = ntohl(recvbuf.len);//此处的是数据包的长度,后面接收包体。
ret = readn(conn, recvbuf.buf, n);
if{ret == -1 }{
ERR_EXIT("read");
} else if(ret < n){
printf("client_close\n");
close(conn);
break;
}
fputs(recvbuf.buf, stdout);
writen(conn, &recvbuf, 4+n);//这里使用writen,如果客户端不使用同样的接口会导致无法接收
}
}
}
int main(){
int listenfd;
if(listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0
{
ERR_EXIT("socket");//报错误的宏
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));//全部填充0
servaddr.sin_family = AF_INET;
servaddr.sin_prot = htons(5188);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//参数表示本机的任意地址,以下两种也是正确的写法
//servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_aton("127.0.0.1", &servaddr.sin_addr);
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){//一旦绑定完成,这个套接字就会被认为是被动套接字,否则是主动套接字。
ERR_EXIT("bind");
}
if(listen(listenfd, SOMAXCONN) < 0)//后面的宏,代表队列的最大值
{
ERR_EXIT("listen");
}
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){//这里返回值是文件描述符,在没有客户端连接时accept会阻塞
ERR_EXIT("accept");
}
pid = fork();//创建一个子进程
if(pid == -1){
ERR_EXIT("fork");
}
if(pid == 0){
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);//记得推出子进程。
} else {
close(conn);
}
}
return 0;
}
客户端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
struct packet{
int len;
char buf[1024];
}
ssize_t readn(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
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;//这里传输的是整个函数已经接收了多少个。
} else {
bufp += nread;//更新
nleft -= nread;
}
}
return count;
}
ssize_t writen(int fd, void *buf, size_t count){//size_t为无符号整数,ssize_t为有符号的整数。
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 (nread == 0){
continue;//如果等于零,对于write操作等于无事发生
} else {
bufp += nwritten;//更新
nleft -= nwritten;
}
}
return count;
}
int main(){
int sock;
if(sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) < 0)//其实前两个已经表示为是tcp协议了,第三个参数可以填0
{
ERR_EXIT("socket");//报错误的宏
}
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));//全部填充0
servaddr.sin_family = AF_INET;
servaddr.sin_prot = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(sock, (struct sockaddr*)servaddr, sizeof(servaddr)) < 0){
ERR_EXIT("connect");
}
//此时使用自定义的包结构
struct packet sendbuf;
struct packet recvbuf;
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
int n;
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};
while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin)!= NULL){//此处使用结构体里面的成员
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n);//此处多此一举是为了统一字节序转换为网络字节序,此处是包体的长度
//writen(sock, sendbuf, strlen(sendbuf));//改成writen,这里改成sizeof定长包就能与服务器通信了
writen(sock, sendbuf, 4+n);//包头有四个字节
int ret = readn(sock, &recvbuf.len, 4);//这里使用readn,先接收四个字节的包头
if{ret == -1 }{
ERR_EXIT("read");
} else if(ret < 4){
printf("client_close\n");
break;
}
n = ntohl(recvbuf.len);//此处的是数据包的长度,后面接收包体。
ret = readn(sock, recvbuf.buf, n);
if{ret == -1 }{
ERR_EXIT("read");
} else if(ret < n){
printf("client_close\n");
break;
}
fputs(recvbuf.buf, stdout);
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
}
close(sock);
return 0;
}