08包尾加上\n編程實踐-readline與recv的結合使用

\n作爲協議的邊界
	ssize_t recv(int s, void *buf, size_t len, int flags);
	與read相比,只能用於套接字文件描述符;
	多了一個flags
       MSG_OOB
   This  flag requests receipt of out-of-band data that would not be received in the normal data stream.  Some protocols place expedited data at thehead of the normal data queue, and thus this flag cannot be used with such protocols.
帶外數據 緊急指針
       MSG_PEEK
This flag causes the receive operation to return data from the beginning of the receive queue without removing that data from the queue.  Thus, asubsequent receive call will return the same data.
		可以讀數據,不從緩存區中讀走,利用此特點可以方便的實現按行讀取數據。 
	一個一個字符的讀,方法不好;多次調用系統調用read方法

\n編程實踐

9client_readline.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

/*
	包尾加上\n編程實踐
*/

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


/*
	使用說明:
		//1一次全部讀走 //2次讀完數據 //出錯分析 //對方已關閉
	   
	思想:
		tcpip是流協議,不能保證1次讀操作,能全部把報文讀走,所以要循環
		讀指定長度的數據。
	按照count大小讀數據,若讀取的長度ssize_t<count 說明讀到了一個結束符,
	對方已關閉
	
	函數功能:
		從一個文件描述符中讀取count個字符到buf中
	參數:
		@buf:接受數據內存首地址
		@count:接受數據長度
	返回值:
		@ssize_t:返回讀的長度 若ssize_t<count 讀失敗失敗
*/
ssize_t readn(int fd, void *buf, size_t count)
{
	size_t nleft = count;	//剩下需要讀取的數據個數
	ssize_t nread;			//成功讀取的字節數
	char * bufp = (char*)buf;//將參數接過來
	while (nleft > 0) 
	{
		//如果errno被設置爲EINTR爲被信號中斷,如果是被信號中斷繼續,
		//不是信號中斷則退出。
		 if ((nread = read(fd, bufp, nleft)) < 0) 
		 {
		 	//異常情況處理
		 	if (errno == EINTR) //讀數據過程中被信號中斷了
		 		continue;		//再次啓動read
		 		//nread = 0;	//等價於continue
		 	return -1;	
		 }else if (nread == 0)	//到達文件末尾EOF,數據讀完(讀文件、讀管道、socket末尾、對端關閉)
		 	break;
		 bufp += nread;			//將字符串指針向後移動已經成功讀取個數的大小。
		 nleft -=nread;			//需要讀取的個數=需要讀取的個數-已經成功讀取的個數
	}
	return (count - nleft);//返回已經讀取的數據個數
}

/*
	思想:tcpip是流協議,不能1次把指定長度數據,全部寫完 
	按照count大小寫數據
	若讀取的長度ssize_t<count 說明讀到了一個結束符,對方已關閉。
	
	
	函數功能:
		向文件描述符中寫入count個字符
	函數參數:
		@buf:待寫數據首地址
		@count:待寫長度
	返回值:
		@ssize_t:返回寫的長度 -1失敗
*/
ssize_t writen(int fd, 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 ((nwritten < 0) && (errno == EINTR)) //讀數據過程中被信號中斷了
		 		continue;			//再次啓動write
		 		//nwritten = 0;		//等價continue
		 	else
		 		return -1;
		 }
		 
		 bufp += nwritten;	//移動緩衝區指針
		 nleft -=nwritten;	//記錄剩下未讀取的數據
	}
	return count;//返回已經讀取的數據個數
}


//讀數據,但不把數據緩衝區清空
//@ssize_t返回值:返回緩衝區數據的長度 -1失敗
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
	while (1) 
	{
		 //MSG_PEEK 讀取隊列中指定大小的數據,但不取出
		 int ret = recv(sockfd, buf, len, MSG_PEEK);
		 //如果被信號中斷,則繼續
		 if (ret == -1 && errno == EINTR) 
		 	continue;
		 return ret;
	}
}

/*
	maxline 一行最大數
	先提前peek一下緩衝區,如果有數據從緩衝區讀數據,
	1、緩衝區數據中帶\n
	2 、緩存區中不帶\n
	讀取數據包直到\n
	
	功能:按行讀取文件,只要遇到\n就,讀走數據,返回,
	@buf 接收數據內存首地址
	@maxline 接收數據內存最大值
*/
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
	int ret;
	int nread;			//成功預讀取的數據個數
	char *bufp = buf;	//讀取數據存放的數組,在外分配內存
	int nleft = maxline;//封包最大值
	
	while (1) 
	{
		 //看一看緩衝區有沒有數據,並不移除內核緩衝區數據
		 //讀數據,但不把數據緩衝區清空,成功:ret是報文的長度
		 ret = recv_peek(sockfd, bufp, nleft);
		 if (ret < 0) //失敗
		 	return ret;
		 else if (ret == 0)//對方已關閉
		 	return ret;
		nread = ret;
		int i;
		
		//讀數據,但不把數據緩衝區清空,避免了一個字節一個字節的讀數據
        //先利用recv的MSG_PEEK功能,預讀數據,然後查找\n
        //根據\n的位置,根據指定長度,再真正的讀數據
		for (i = 0; i < nread; i++)
		{
			if (bufp[i] == '\n') //若緩衝區有\n
			{
				ret = readn(sockfd, bufp, i+1);//將數據從緩存區讀走
				if (ret != i + 1) 
					exit(EXIT_FAILURE);
				return ret;//有\n就返回,並返回讀走的數據
			}
		} 	

		//若數據長度 nread > 緩衝區最大長度maxline 退出
		if (nread > nleft) 
			exit(EXIT_FAILURE);
			
		//若沒有\n,說明消息還沒有結束,不是完整的一條消息,就把這些數據也讀到buf緩衝區中。
		//依此循環,直到遇到\n,把整個一行數據,全部讀完,放入buf中
		//bufp記錄了每次需追加的位置
		nleft -= nread;
		ret = readn(sockfd, bufp, nread);
		if (ret != nread) 
			exit(EXIT_FAILURE);
		bufp += nread; //bufp每次跳到追加的末尾
	}	 	
	return -1; 	 	
}	

void test()
{
	int sockfd = 0;
	const char *serverip = "192.168.66.128";
	
	//創建socket
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		ERR_EXIT("socket()");
	
	//定義socket結構體 man 7 ip
	struct sockaddr_in srvsddr;
	srvsddr.sin_family = AF_INET;
	srvsddr.sin_port = htons(8001);//轉化爲網絡字節序
	//第一種
	#if 0
	srvsddr.sin_addr.s_addr = inet_addr(serverip);
	#endif
	//第二種
	#if 0
	//srvsddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是0.0.0.0 不存在網絡字節序
	//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //綁定本機的任意一個地址
	#endif
	//第三種
	//建議使用這種
	#if 1
	int ret;
	ret = inet_pton(AF_INET, serverip, &srvsddr.sin_addr);
	if (ret == 0)
	{
		ERR_EXIT("inet_pton()");
	}	
	#endif
	//進程-》內核
	if (connect(sockfd, (struct sockaddr*)&srvsddr, sizeof(srvsddr)) < 0)
		ERR_EXIT("connect");

	struct sockaddr_in localaddr;
	socklen_t addrlen = sizeof(localaddr);
	//內核-》進程
	//獲取本地的地址 注意是已連接以後的套接字
	if ((getsockname(sockfd, (struct sockaddr *)&localaddr, &addrlen)) < 0)
		ERR_EXIT("getsockname()");
	printf("本機的ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) 
	{
		//寫數據(本身帶有\n), 所以不需要再單獨加\n,因爲從stdin輸入完需要按下enter鍵
		writen(sockfd, sendbuf, strlen(sendbuf));
		//按照行讀數據
		int ret = readline(sockfd, recvbuf, sizeof(recvbuf));
		if (ret == -1) 
			ERR_EXIT("readline()");
		else if (ret == 0)
		{
			printf("server close\n");
			break;
		}
		fputs(recvbuf, stdout);
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));
	}
	close(sockfd);
	return ;
}

int main()
{
	test();
	return 0;
}

10server_readline.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>

/*
	包尾加上\n編程實踐
*/

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


/*
	使用說明:
		//1一次全部讀走 //2次讀完數據 //出錯分析 //對方已關閉
	   
	思想:
		tcpip是流協議,不能保證1次讀操作,能全部把報文讀走,所以要循環
		讀指定長度的數據。
	按照count大小讀數據,若讀取的長度ssize_t<count 說明讀到了一個結束符,
	對方已關閉
	
	函數功能:
		從一個文件描述符中讀取count個字符到buf中
	參數:
		@buf:接受數據內存首地址
		@count:接受數據長度
	返回值:
		@ssize_t:返回讀的長度 若ssize_t<count 讀失敗失敗
*/
ssize_t readn(int fd, void *buf, size_t count)
{
	size_t nleft = count;	//剩下需要讀取的數據個數
	ssize_t nread;			//成功讀取的字節數
	char * bufp = (char*)buf;//將參數接過來
	while (nleft > 0) 
	{
		//如果errno被設置爲EINTR爲被信號中斷,如果是被信號中斷繼續,
		//不是信號中斷則退出。
		 if ((nread = read(fd, bufp, nleft)) < 0) 
		 {
		 	//異常情況處理
		 	if (errno == EINTR) //讀數據過程中被信號中斷了
		 		continue;	//再次啓動read
		 		//nread = 0;//等價於continue
		 	return -1;	
		 }else if (nread == 0)	//到達文件末尾EOF,數據讀完(讀文件、讀管道、socket末尾、對端關閉)
		 	break;
		 bufp += nread;	//將字符串指針向後移動已經成功讀取個數的大小。
		 nleft -=nread;	//需要讀取的個數=需要讀取的個數-已經成功讀取的個數
	}
	return (count - nleft);//返回已經讀取的數據個數
}

/*
	思想:tcpip是流協議,不能1次把指定長度數據,全部寫完 
	按照count大小寫數據
	若讀取的長度ssize_t<count 說明讀到了一個結束符,對方已關閉。
	
	
	函數功能:
		向文件描述符中寫入count個字符
	函數參數:
		@buf:待寫數據首地址
		@count:待寫長度
	返回值:
		@ssize_t:返回寫的長度 -1失敗
*/
ssize_t writen(int fd, 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 ((nwritten < 0) && (errno == EINTR)) //讀數據過程中被信號中斷了
		 		continue;			//再次啓動write
		 		//nwritten = 0;		//等價continue
		 	else
		 		return -1;
		 }
		 
		 bufp += nwritten;	//移動緩衝區指針
		 nleft -=nwritten;	//記錄剩下未讀取的數據
	}
	return count;//返回已經讀取的數據個數
}

//從指定的socket中讀取指定大小的數據但不取出,封裝後不被信號中斷
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
	while (1) 
	{
		 //MSG_PEEK 讀取隊列中指定大小的數據,但不取出
		 int ret = recv(sockfd, buf, len, MSG_PEEK);
		 //如果被信號中斷,則繼續
		 if (ret == -1 && errno == EINTR) 
		 	continue;
		 return ret;
	}
}

/*
	maxline 一行最大數
	先提前peek一下緩衝區,如果有數據從緩衝區讀數據,
	1、緩衝區數據中帶\n
	2 、緩存區中不帶\n
	讀取數據包直到\n
*/
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
	int ret;
	int nread;			//成功預讀取的數據個數
	char *bufp = buf;	//讀取數據存放的數組,在外分配內存
	int nleft = maxline;//封包最大值
	
	while (1) 
	{
		 //看一看緩衝區有沒有數據,並不移除內核緩衝區數據
		 ret = recv_peek(sockfd, bufp, nleft);
		 if (ret < 0) //失敗
		 	return ret;
		 else if (ret == 0)//對方已關閉
		 	return ret;
		nread = ret;
		int i;
		
		//逐字符讀取
		for (i = 0; i < nread; i++)
		{
			if (bufp[i] == '\n') //若緩衝區有\n
			{
				ret = readn(sockfd, bufp, i+1);//讀走數據
				if (ret != i + 1) 
					exit(EXIT_FAILURE);
				return ret;//有\n就返回,並返回讀走的數據
			}
		} 	

		//如果讀到的數大於 一行最大數 異常處理
		if (nread > nleft) 
			exit(EXIT_FAILURE);
		nleft -= nread;//若緩衝區沒有\n, 把剩餘的數據讀走
		ret = readn(sockfd, bufp, nread);
		if (ret != nread) 
			exit(EXIT_FAILURE);
		bufp += nread;//bufp指針後移後,再接着偷看緩衝區數據recv_peek,直到遇到\n
	}	 	
	return -1; 	 	
}	



void do_service(int conn)
{
	char  recvbuf[1024];
	while (1) 
	{
		memset(recvbuf, 0, sizeof(recvbuf));
		int ret = readline(conn, recvbuf, sizeof(recvbuf));
		if (ret == -1) 
			ERR_EXIT("readline()");
		if (ret ==  0)
		{
			printf("client close\n");
			break;
		}
		
		//將數據打印輸出
		fputs(recvbuf, stdout);
		writen(conn, recvbuf, strlen(recvbuf));
	}	
}

void test()
{
	int sockfd = 0;
	int conn = 0;
	const char *serverip = "192.168.66.128";
	
	//創建socket
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		ERR_EXIT("socket()");
	
	//定義socket結構體 man 7 ip
	struct sockaddr_in srvsddr;
	srvsddr.sin_family = AF_INET;
	srvsddr.sin_port = htons(8001);//轉化爲網絡字節序
	//第一種
	#if 0
	srvsddr.sin_addr.s_addr = inet_addr(serverip);
	#endif
	//第二種
	#if 0
	//srvsddr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY 就是0.0.0.0 不存在網絡字節序
	//srvaddr.sin_addr.s_addr = inet_addr(INADDR_ANY); //綁定本機的任意一個地址
	#endif
	//第三種
	//建議使用這種
	#if 1
	int ret;
	ret = inet_pton(AF_INET, serverip, &srvsddr.sin_addr);
	if (ret == 0)
	{
		ERR_EXIT("inet_pton()");
	}	
	#endif
	
	//設置端口複用
	//使用SO_REUSEADDR選項可以使得不必等待TIME_WAIT狀態消失就可以重啓服務器
	int optval = 1;
    if( setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
		ERR_EXIT("setsockopt()");
		
	if(bind(sockfd, (struct sockaddr *)&srvsddr,sizeof(srvsddr)) <0 )
		ERR_EXIT("bind()");
		
	if(listen(sockfd, SOMAXCONN) < 0)
		ERR_EXIT("listen()");
	
	struct sockaddr_in peeraddr;
	socklen_t peerlen = sizeof(peeraddr);//值-結果參數
	pid_t pid;
	
	while (1) 
	{
		if ((conn = accept(sockfd, (struct sockaddr *)&peeraddr, &peerlen)) < 0) 
			ERR_EXIT("accept()");
			
		printf("客戶端1 ip:%s port:%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
		
		
#if 1
//方法2 只要能拿到連接socket就行
	struct sockaddr_in clientaddr;
	socklen_t clientlen = sizeof(clientaddr);
	//注意是已連接以後的套接字
	if (getpeername(conn, (struct sockaddr*)&clientaddr, &clientlen) < 0)
		ERR_EXIT("getpeername");

	printf("客戶端2 ip=%s port=%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
	
#endif
		
		
				
		pid = fork(); 
		if (pid == -1) 
		{
			ERR_EXIT("fork()");
		}
		if (pid == 0) 
		{
			//子進程不需要監聽socket
			close(sockfd);
			do_service(conn);
			exit(EXIT_SUCCESS);
		}else 
		{
			close(conn);//父進程不需要連接socket
		} 
	}
	return ;
}

int main()
{
	test();
	return 0;
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章