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;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值