不恰当使用read会造成服务器阻塞

可正常工作的tcp服务器:

#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <time.h>

//测试方法,运行程序,并指定端口号8888
//在另一个终端上使用  
// telnet 127.0.0.1 8888进行连接
//127.0.0.1是没有连接网络时使用的本地回环ip地址


//使用浏览器测试,需要使用使用http协议进行,在浏览器中输入http://IP.8888 即可访问(本机测试)


/*声明自定义函数*/
void sig_handler(int signo);
void out_addr(struct sockaddr_in *clientaddr);
void do_service(int fd);
int sockfd;

int main(int argc, char * argv[])
{
	
	if(argc < 2)
	{
		printf("usage: %s #port\n",argv[0]);
		exit(1);
	}
	if(signal(SIGINT, sig_handler) == SIG_ERR)   //开始捕捉信号  SIGINT
	{
		perror("signal sigint error!");
		exit(1);
	}


	/*步骤1创建socket
	*AF_INET IPV4
	*SOCK_STREAM 采用tcp协议
	*0 采用上面设置的协议类型
	*/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);  //使用默认协议
	if(sockfd < 0)
	{
		perror("socket error!");
		exit(1);
	}
	/*
	*步骤2,:调用bind函数将socket和地址(包括 IP,port)进行绑定
	**/
	struct sockaddr_in serveraddr;  //声明专用地址,需要的时候再转换为通用地址
	memset(&serveraddr, 0, sizeof(serveraddr));
	//往地址填入ip、port、intnernet地址族类型
	serveraddr.sin_family = AF_INET; //IPV4
	serveraddr.sin_port = htons(atoi(argv[1]));  //填入端口 
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(sockfd, (struct sockaddr *) &serveraddr,sizeof(serveraddr)) < 0)
	{
		perror("bind error!");
		exit(1);
	}
	/* 
	*步骤3:调用listen函数启动端口监听
	*通知系统去接受来自客户端的连接请求
	*listen 的第二个参数10是请求队列长度,将接收到的客户端连接请求放到对应的队列当中
	*/
	if(listen(sockfd, 10) < 0)  //10监听的队列的上限
	{
		perror("listen error!");
		exit(1);
	}
	/*
	*步骤4:调用accept函数,从队列中获得
	*      一个客户端请求连接,并返回新的sock文件描述符fd,
	*      因为listen能够监听好多的连接请求,
	*      使用accept获得一个连接使用
	*      若是accept没有获得客户端连接,会进行阻塞,直到获得客户端连接
	*/
	struct sockaddr_in clientaddr;
	socklen_t clientaddr_len = sizeof(clientaddr);
	while(1)
	{
		//使用循环就能够在断开与一个客户端连接之后,在连接下一个客户端连接
		int fd = accept(sockfd, (struct sockaddr*) &clientaddr,&clientaddr_len);
		if(fd < 0)
		{
			perror("accept error!");
			continue;
		}
		/*
		*步骤5: 调用IO函数(read/write)和连接的客户端进行双向通信
		*
		*/

		out_addr(&clientaddr);
		do_service(fd);
		/*步骤6:关闭socket*/
		close(fd);
	}
	

	return 0;
}

void sig_handler(int signo)
{
	if(signo == SIGINT)
	{
		printf("server close\n");
		/*步骤6:关闭socket*/
		close(sockfd);
		exit(1);
	}

}

void out_addr(struct sockaddr_in *clientaddr)
{
	/*将端口网络字节序转换为主机字节序 */   
	/*port是short类型数据*/
	int port = ntohs(clientaddr->sin_port);
	char ip[16];
	memset(ip, 0, sizeof(ip));
	/*将ip地址从网络地址转换为点分十进制*/
	/*
		需要注意的地方
		#include <arpa/inet.h>

       const char *inet_ntop(int af, const void *src,
                             char *dst, socklen_t size);
		在函数中 const void *src说明第二个参数是指针类型的数据,但是当传入
		clientaddr->sin_addr.s_addr == (*clientaddr).sin_addr.s_addr就不在是一个指针类型数据
		因此需要使用&将其取址之后在传入。

	*/

	inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
	printf("client: %s(%d) connected\n",ip, port);
}

void do_service(int fd)
{
	
	//获取系统时间
	long t = time(0);
	char *s = ctime(&t);
	size_t size = strlen(s)*sizeof(char);
	/*将服务器端获取的系统时间写回到客户端*/
	if(write(fd, s, size) != size)
	{
		perror("write error!");
	}

}

能正常工作的tcp客户端:

#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <time.h>





int main(int argc, char * argv[])
{
	if(argc < 3)
	{
		printf("usage:%s ip port\n",argv[0]);
		exit(1);
	}

	/*步骤1:创建socket*/
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
	{
		perror("socket error!");
		exit(1);
	}
	/*往serveraddr中填入ip,port和地址族类型(ipv4)*/
	struct sockaddr_in serveraddr;
	memset(&serveraddr, 0, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(atoi(argv[2]));
	/*将字符串ip地址转换为网络字节序填入 serveraddr中*/
	inet_pton(AF_INET, argv[1], 
					&serveraddr.sin_addr.s_addr);

	/*
	*步骤2: 客户端调用connect函数连接到服务器端
	*/
	if(connect(sockfd, (struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0)
	{
		perror("connect error!");
		exit(1);
	}

	/*步骤3:调用IO函数,read和write和服务器进行双向通信*/
	char buffer[1024];
	memset(buffer, 0, sizeof(buffer));
	size_t size;
	if((size = read(sockfd, buffer, sizeof(buffer))) < 0)
	{
		perror("read error!");
		exit(1);
	}
	if(write(STDOUT_FILENO, buffer, size) != size)
	{
		perror("write error!");
		exit(1);
	}
	/*步骤4:关闭socket套接字*/
	close(sockfd);

	

	return 0;
}


在do_service增加read之后,但是没有在客户端增加,发送数据函数,会造成阻塞,如:

void do_service(int fd)
{
	size_t len;
	char buff[20];
	if((len = read(fd, buff, 20)) < 0)
	{
		perror("read error!");
	}
	//获取系统时间
	long t = time(0);
	char *s = ctime(&t);
	size_t size = strlen(s)*sizeof(char);
	/*将服务器端获取的系统时间写回到客户端*/
	if(write(fd, s, size) != size)
	{
		perror("write error!");
	}

}

更改之后的服务器:

#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <signal.h>
#include <time.h>

//测试方法,运行程序,并指定端口号8888
//在另一个终端上使用  
// telnet 127.0.0.1 8888进行连接
//127.0.0.1是没有连接网络时使用的本地回环ip地址


//使用浏览器测试,需要使用使用http协议进行,在浏览器中输入http://IP.8888 即可访问(本机测试)


/*声明自定义函数*/
void sig_handler(int signo);
void out_addr(struct sockaddr_in *clientaddr);
void do_service(int fd);
int sockfd;

int main(int argc, char * argv[])
{
	
	if(argc < 2)
	{
		printf("usage: %s #port\n",argv[0]);
		exit(1);
	}
	if(signal(SIGINT, sig_handler) == SIG_ERR)   //开始捕捉信号  SIGINT
	{
		perror("signal sigint error!");
		exit(1);
	}


	/*步骤1创建socket
	*AF_INET IPV4
	*SOCK_STREAM 采用tcp协议
	*0 采用上面设置的协议类型
	*/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);  //使用默认协议
	if(sockfd < 0)
	{
		perror("socket error!");
		exit(1);
	}
	/*
	*步骤2,:调用bind函数将socket和地址(包括 IP,port)进行绑定
	**/
	struct sockaddr_in serveraddr;  //声明专用地址,需要的时候再转换为通用地址
	memset(&serveraddr, 0, sizeof(serveraddr));
	//往地址填入ip、port、intnernet地址族类型
	serveraddr.sin_family = AF_INET; //IPV4
	serveraddr.sin_port = htons(atoi(argv[1]));  //填入端口 
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(sockfd, (struct sockaddr *) &serveraddr,sizeof(serveraddr)) < 0)
	{
		perror("bind error!");
		exit(1);
	}
	/* 
	*步骤3:调用listen函数启动端口监听
	*通知系统去接受来自客户端的连接请求
	*listen 的第二个参数10是请求队列长度,将接收到的客户端连接请求放到对应的队列当中
	*/
	if(listen(sockfd, 10) < 0)  //10监听的队列的上限
	{
		perror("listen error!");
		exit(1);
	}
	/*
	*步骤4:调用accept函数,从队列中获得
	*      一个客户端请求连接,并返回新的sock文件描述符fd,
	*      因为listen能够监听好多的连接请求,
	*      使用accept获得一个连接使用
	*      若是accept没有获得客户端连接,会进行阻塞,直到获得客户端连接
	*/
	struct sockaddr_in clientaddr;
	socklen_t clientaddr_len = sizeof(clientaddr);
	while(1)
	{
		//使用循环就能够在断开与一个客户端连接之后,在连接下一个客户端连接
		int fd = accept(sockfd, (struct sockaddr*) &clientaddr,&clientaddr_len);
		if(fd < 0)
		{
			perror("accept error!");
			continue;
		}
		/*
		*步骤5: 调用IO函数(read/write)和连接的客户端进行双向通信
		*
		*/

		out_addr(&clientaddr);
		do_service(fd);
		/*步骤6:关闭socket*/
		close(fd);
	}
	

	return 0;
}

void sig_handler(int signo)
{
	if(signo == SIGINT)
	{
		printf("server close\n");
		/*步骤6:关闭socket*/
		close(sockfd);
		exit(1);
	}

}

void out_addr(struct sockaddr_in *clientaddr)
{
	/*将端口网络字节序转换为主机字节序 */   
	/*port是short类型数据*/
	int port = ntohs(clientaddr->sin_port);
	char ip[16];
	memset(ip, 0, sizeof(ip));
	/*将ip地址从网络地址转换为点分十进制*/
	/*
		需要注意的地方
		#include <arpa/inet.h>

       const char *inet_ntop(int af, const void *src,
                             char *dst, socklen_t size);
		在函数中 const void *src说明第二个参数是指针类型的数据,但是当传入
		clientaddr->sin_addr.s_addr == (*clientaddr).sin_addr.s_addr就不在是一个指针类型数据
		因此需要使用&将其取址之后在传入。

	*/

	inet_ntop(AF_INET, &clientaddr->sin_addr.s_addr, ip, sizeof(ip));
	printf("client: %s(%d) connected\n",ip, port);
}

void do_service(int fd)
{
	size_t len;
	char buff[20];
	if((len = read(fd, buff, 20)) < 0)
	{
		perror("read error!");
	}
	//获取系统时间
	long t = time(0);
	char *s = ctime(&t);
	size_t size = strlen(s)*sizeof(char);
	/*将服务器端获取的系统时间写回到客户端*/
	if(write(fd, s, size) != size)
	{
		perror("write error!");
	}

}

客户端的程序保持不变,经过测试,服务器会阻塞到read函数的地方。

猜你喜欢

转载自blog.csdn.net/andrewgithub/article/details/81712996