socket编程(四)

1.流协议与粘包

TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP的发送方无法保证对等方每次接收到的是一个完整的数据包。主机A向主机B发送两个数据包,主机B的接收情况可能是

socket编程(四)_第1张图片

2.粘包产生的原因

(1)应用层调用write方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区。而发送缓冲区有一个SO_SNDBUF的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送。

(2)TCP所传输的报文段有MSS的限制,如果套接字缓冲区的大小大于MSS,也会导致消息的分割发送。

(3)由于链路层最大发送单元MTU,在IP层会进行数据的分片。

socket编程(四)_第2张图片

3.粘包处理方案

(1)发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。

(2)包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。

(3)包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。

(4)使用更加复杂的应用层协议。

4.回射客户/服务器

echoserver.cpp

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 



#define ERR_EXIT(m) \
    do \
    { \
    	perror(m); \
    	exit(EXIT_FAILURE); \
    }while(0)
    

/*
 param1:fd, param2:buf, param3:count
return:读取成功字节数
ssize_t:有符号
size_t:无符号
 */

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 (0 == nread) {
			return count - nleft;
		}
		bufp += nread;
		nleft -= nread;
		
	}
	return count;
}


/*
param1:fd, param2:buf, param3:count
return:已经发送了多少
*/
ssize_t writen(int fd, void* buf, size_t count) {
	size_t nleft = count;
	ssize_t nwrite;
	char* bufp = (char*)buf;
	while(nleft > 0) {
		if((nwrite = write(fd, bufp, nleft)) < 0) {	
			if(errno == EINTR)
				continue;
			return -1;
		} else if(0 == nwrite) {
			continue;
		}
		bufp += nwrite;
		nleft -= nwrite;
	}
	return count;
}

void do_service(int conn) {
  	//char recvbuf[1024];
  	struct packet recvbuf;
	int n;
  	while(1) {
  		memset(&recvbuf, 0, sizeof(recvbuf));
  		int ret = readn(conn, &recvbuf.len, 4);
		if(-1 == ret) {
			ERR_EXIT("read");
		} else if(4 > ret) {
			printf("client close\n");
			break;
		}
		n = ntohl(recvbuf.len);
		ret = readn(conn, recvbuf.buf, n);
		if(ret < n) {  //server端知道client关闭
			printf("client close\n");
			break;
		} else if(-1 == ret) {
			ERR_EXIT("read");
		}
  		fputs(recvbuf.buf, stdout);
  		writen(conn, &recvbuf, 4+n);
  	}
}

int main () {
  
	int listenfd;
	if(( listenfd= socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  	//if((listenfd= socket(PF_INET, SOCK_STREAM, 0)) <0) 
  		ERR_EXIT("socket");
  	
  	
  	struct sockaddr_in servaddr;
  	memset(&servaddr, 0, sizeof(servaddr));
  	servaddr.sin_family = AF_INET;
  	servaddr.sin_port = 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);
  
	int on = 1;
	if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
		ERR_EXIT("setsockopt");
	
  	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)
			ERR_EXIT("accept");

		printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
		
		pid = fork();
		if(-1 == pid) {
			ERR_EXIT("fork");
		}
		if(pid == 0) {  //child 
			close(listenfd);
			do_service(conn);
			exit(EXIT_SUCCESS);
		} else {	//parent
			close(conn);
		}
  	}


	
	close(listenfd);
	close(conn);
  return 0;
}

echoclient.cpp

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 




#define ERR_EXIT(m) \
    do \
    { \
    	perror(m); \
    	exit(EXIT_FAILURE); \
    }while(0)
    


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 (0 == nread) {
			return count - nleft;
		}
		bufp += nread;
		nleft -= nread;
		
	}
	return count;
}

ssize_t writen(int fd, void* buf, size_t count) {
	size_t nleft = count;
	ssize_t nwrite;
	char* bufp = (char*)buf;
	while(nleft > 0) {
		if((nwrite = write(fd, bufp, nleft)) < 0) {	
			if(errno == EINTR)
				continue;
			return -1;
		} else if(0 == nwrite) {
			continue;
		}
		bufp += nwrite;
		nleft -= nwrite;
	}
	return count;
}

int main () {
  
	int sock;
	if(( sock= socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  		ERR_EXIT("socket");
  	
  	sock = socket(PF_INET, SOCK_STREAM, 0);
  	
  	struct sockaddr_in servaddr;
  	memset(&servaddr, 0, sizeof(servaddr));
  	servaddr.sin_family = AF_INET;
  	servaddr.sin_port = htons(5188);
  	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  	
  	if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
  		ERR_EXIT("sockect");
  	
  	
	struct packet sendbuf;
	struct packet recvbuf;
	memset(&sendbuf, 0, sizeof(sendbuf));
	memset(&recvbuf, 0, sizeof(recvbuf));
  	//char sendbuf[1024] = {0};	
  	//char recvbuf[1024] = {0};
  	int n;
  	while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL) {
		n = strlen(sendbuf.buf);
		sendbuf.len = htonl(n);  //注意主机字节序-->网络字节序 
  		writen(sock, &sendbuf, 4+n);  //定制协议
  		//writen(sock, sendbuf, sizeof(sendbuf));  //发送定长包
  		//writen(sock, sendbuf, strlen(sendbuf));
  		
			
  		int ret = readn(sock, &recvbuf.len, 4);
		if(-1 == ret) {
			ERR_EXIT("read");
		} else if(4 > ret) {
			printf("client close\n");
			break;
		}
		n = ntohl(recvbuf.len);
		ret = readn(sock, recvbuf.buf, n);
		if(ret < n) {  //server端知道client关闭
			printf("client close\n");
			break;
		} else if(-1 == ret) {
			ERR_EXIT("read");
		}
	
		//readn(sock, recvbuf, sizeof(recvbuf));
  		fputs(recvbuf.buf, stdout);
  		memset(&sendbuf, 0, sizeof(sendbuf));
  		memset(&recvbuf, 0, sizeof(recvbuf));
  	}
  	
  	close(sock);
  	return 0;
}



你可能感兴趣的:(Linux网络编程)