【Linux网络编程】基于TCP协议 I/O多路转接(select) 的高性能回显服务器客户端模型




服务端代码:

myselect.c

#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>


#define ARRAY_SIZE 1024


/* 使用说明 */
void usage(const char* proc)
{
	printf("%s usage : [server_ip] [ server_port]\n", proc);
}


/* 初始化一个socket连接 */
int init_sock(const char* _ip, int _port)
{
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("socket");
		exit(2);
	}

	int flg = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flg, sizeof(flg));
	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(_port);
	local.sin_addr.s_addr = inet_addr(_ip);

	if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
	{
		perror("bind");
		exit(3);
	}

	if(listen(sock , 10) < 0)
	{
		perror("listen");
		exit(4);
	}

	return sock;
}

/* 等待事件发生并处理 */
void run_select(int listen_sock)
{
	
	int array_fds[ARRAY_SIZE];  /* 保存所有关心的描述符  */
	array_fds[0] = listen_sock; /* 将listen_sock 保存*/

	/* 初始化为 array_fds */
	int idx_init = 1;
	for(idx_init = 1; idx_init < ARRAY_SIZE; ++idx_init)
		array_fds[idx_init] = -1;

	fd_set rfds;      /* 关心的读事件描述符集 */
	fd_set wfds;      /* 关心的写事件描述符集 */
	int max_fd = -1;

	struct timeval _timeout = {1, 0};

	while(1)
	{
		max_fd = -1;
		_timeout.tv_sec = 1;
		FD_ZERO(&rfds);   
		FD_ZERO(&wfds);

		/* 一. 将数组中存储的描述符,添加到关心的集合中 
		 *  并找出其中最大的描述符,
		 */
		int idx_add = 1;
		for(idx_add = 0; idx_add < ARRAY_SIZE; idx_add++)
		{
			if(array_fds[idx_add] > 0)
			{
				FD_SET(array_fds[idx_add], &rfds);
				FD_SET(array_fds[idx_add], &wfds);
				if(array_fds[idx_add] > max_fd)
					max_fd = array_fds[idx_add];

			}
		}
		/* 二. 检测select的返回情况*/
		int select_ret = select(max_fd+1, &rfds, &wfds, NULL, &_timeout);
		switch( select_ret )
		{
			case 0:
				printf("timeout\n");
				break;
			case -1:
				perror("select\n");
				break;
			default:
				{
					/* 遍历数组,检测事件发生的描述符,并响应  */
					int idx_check = 0;
					for(idx_check = 0; idx_check < ARRAY_SIZE; ++idx_check)
					{
						if(array_fds[idx_check] < 0)
							continue;

						if(idx_check == 0 && FD_ISSET(array_fds[idx_check], &rfds) )
						{ /* listensock 有数据来,表示有新的连接请求 */
							struct sockaddr_in client;
							socklen_t len = sizeof(client);

							int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
							if(new_sock > 0)
							{
								printf("新的连接请求来自: ip = %s, port = %d \n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));

								/* 在数组中找到未被占用的描述符位置,并保存新连接的描述符 */
								int idx_find = 0;
								for(idx_find = 1; idx_find < ARRAY_SIZE; ++idx_find)
								{
									if(array_fds[idx_find] < 0)
									{
										array_fds[idx_find] = new_sock;
										break;
									}
								}
								if(idx_find == ARRAY_SIZE)
									close(new_sock);
							}
						} // << end if idx_check == 0 && FD_ISSET -- rfds>> 
						else if( idx_check != 0 &&  FD_ISSET(array_fds[idx_check], &rfds) )
						{ /* 其余描述符有数据来*/ 
							
							char buf[ 1024];
							memset(buf, '\0', sizeof(buf));
							ssize_t s = read(array_fds[idx_check], buf, sizeof(buf) - 1);
							if(s > 0)
							{
								printf("client say : %s\n", buf);
								if(FD_ISSET(array_fds[idx_check], &wfds))	
									write(array_fds[idx_check], buf, sizeof(buf)-1);
							}
							else if(s == 0)
							{
								printf("client quit\n");
								close(array_fds[idx_check]);
								array_fds[idx_check] = -1;
							}
							else
							{
								perror("read fail");
								close(array_fds[idx_check]);
								array_fds[idx_check] = -1;
							}
						} // << end else if idx_check != 0 && FD_ISSET -- rfds >>
					} // << end for idx_check = 0; idx_check<ARRAY_SIZE; ++idx_check >>
				} // << end default >>
				break;
		} // << end switch select >>
	} // << end while 1 >>
}

int main(int argc, char **argv)
{
	if(3 != argc)
	{
		usage(argv[0]);
		exit(1);
	}

	int sock = init_sock(argv[1], atoi(argv[2]));

	printf("start run_select\n");
	run_select(sock);

	return 0;
}	



客户端代码:

为了练习dup 和 dup2 函数的使用,在客户端中,使用了这两个函数进行标准输出的重定向以及恢复,使用printf 函数向sockfd 中写数据,并提示用户输入。


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
	if(argc != 3)
	{
		printf("Usage [server_ip] [ server_port]\n");
		exit(1);
	}

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("socket");
		exit(2);
	}

	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = inet_addr(argv[1]);
	server.sin_port = htons(atoi(argv[2]));



	if(connect(sock,(struct sockaddr*)&server, sizeof(server))<0)
	{
		perror("connect");
		exit(3);
	}

	// 保存标准输出的描述符
	int oldfd = dup(STDOUT_FILENO);
	char buf[1024];

	while(1)
	{
		printf("please input #");
		fflush(stdout);
		memset(buf, '\0', sizeof(buf));

		/* 重定向输出到sock */
		dup2(sock, STDOUT_FILENO);
		ssize_t s = read(0, buf, sizeof(buf)-1);
		if(s > 0)
		{
			/* 处理客户端只输入一个回车而程序挂起的bug */
			if( buf[0] == '\n' )
			{
				dup2(oldfd, STDOUT_FILENO);
				continue;
			}

			if(strncmp("quit", buf, 4) == 0)
				break;

			buf[s - 1] = 0;

			/* 用printf 向sock写 */
			printf("%s", buf);
			fflush(stdout);

		    /* 恢复 标准输出 */
		    dup2(oldfd, STDOUT_FILENO);

			/* 从sock读服务端回显的数据,  */
		    ssize_t _s =  read(sock, buf, sizeof(buf) - 1);
		    if(_s >0)
		    {
		    	buf[_s] = 0;
		    	printf("server echo # %s\n", buf);
		    }
		    else if (s <= 0)
		    {
		    	continue;
		    }
		}
	}

	close(sock);
	close(oldfd);

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值