Linux下实现简单Echo中继服务器

Linux下编写一个Echo中继服务器,echo客户端通过它获取Echo服务器的响应。中继服务器能同时作为多个echo服务器的中继,并且具有一个简单的负载均衡算法。

1. 服务器与客户端描述与设计
支持多个服务器进行Echo服务,服务器需要设定输入端口参数,服务器和客户端可以直接使用多进程版本的设计即可。

2. 中继服务器描述与设计
为了简化,假定所有的服务器都在相同的ip地址上,而使用不同的端口,中继服务器只需要一个ip和多个端口参数的输入。
当客户端连接时,中继服务器Accept之后,开启新的进程A,进程A向使用负载均衡算法找出的Echo服务器申请连接,连接成功之后,进程A创建工作线程P。进程A的主线程接收从客户端发来的信息并发给Echo服务器,工作线程P接收从Echo服务器发来的信息并发给客户端。
这样设计是为了当客户端退出时,能够关闭socket连接,正常退出程序。
对于中继服务器还需要考虑负载均衡的,现使用最简单的算法,即在中继服务器维护一个“下次连接服务器号”,每次有客户端连接,就使用这个号码找到应该连接的服务器,然后这个号加1。

3. 端口规定
中继服务器在9000端口监听客户端的连接;
服务器在给定的端口监听中继服务器的连接。

完整代码如下:
1. 中继器

// recpeater.c
#include "unp.h"

#define BUFFER_SIZE BUFFSIZE
#define SERVPORT 9000
#define BACKLOG 20

#define MAX_SERVER 10

char buffer_c[BUFFER_SIZE];
char buffer_s[BUFFER_SIZE];
int serAmount;
int port[MAX_SERVER];
int portCurrent;
char *serIp;

typedef struct m_socket {
	int s_server;
	int s_client;
} m_socket, * pm_socket;

void str_send_2_server(int s_server, int s_client,
		char* buf) {
	size_t n;

	while (1) {
		n = Read(s_client, buf,
			BUFFER_SIZE);
		if (n <= 0)
			return;	/* EOF */

		Write(s_server, buf, n);
	}
}

void* str_echo_2_client(void *arg) {
	size_t n;
	int s_client = ((pm_socket)arg)->s_client;
	int s_server = ((pm_socket)arg)->s_server;

	while (1)	{
		n = Read(s_server, buffer_c,
			BUFFER_SIZE);

		if (n <= 0)
			return;	/* EOF */

		Write(s_client, buffer_c, n);
	}
}

void str_echo_via_repeater(int s_client) {
	int     s_server, ret;
	struct sockaddr_in servaddr;

	s_server = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(port[portCurrent]);
	Inet_pton(AF_INET, serIp, &servaddr.sin_addr);

	Connect(s_server, (struct sockaddr *) &servaddr,
		sizeof(servaddr));

	pthread_t tid;
	m_socket socket;
	socket.s_client = s_client;
	socket.s_server = s_server;

	Pthread_create(&tid, NULL, str_echo_2_client,
		(void *) &socket);

	str_send_2_server(s_server, s_client, buffer_s);

	Shutdown(s_server, 2);
	Shutdown(s_client, 2);

	Close(s_server);
	Close(s_client);
}

void sig_chld(int signo) {
	pid_t    pid;
	int      stat;

	while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);
	return;
}

int main(int argc, char *argv[]) {
	int     listenfd, connfd;
	size_t	clilen;
	struct sockaddr_in cliaddr, servaddr;
	int i;

	if (argc < 3 || argc > MAX_SERVER + 2) {
		printf("usage: repeater IP port1 [port2 port3 ... port10]\n");
		return 1;
	}

	serIp = argv[1];

	serAmount = argc - 2;
	for (i=0; i<serAmount; i++)
		port[i] = atoi(argv[i+2]);

	portCurrent = 0;

	signal(SIGCHLD, sig_chld);

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
	servaddr.sin_port = htons(SERVPORT);
	Bind(listenfd, (struct sockaddr *) &servaddr,
		sizeof(servaddr));

	Listen(listenfd, BACKLOG);

	while (1) {
		clilen = sizeof(cliaddr);
		connfd = Accept(listenfd,
			(struct sockaddr *) &cliaddr,
			&clilen);
		if (connfd < 0 && connfd == EINTR)
			continue;
		if (Fork() == 0) {
			Close(listenfd);
			str_echo_via_repeater(connfd);
			return 0;
		}

		// 实现负载均衡算法
		portCurrent = (++portCurrent % serAmount);

		Close(connfd);
	}
	return 0;
}

2. 服务器

// ser.c
#include "unp.h"

#define BUFFER_SIZE BUFFSIZE
//#define SERVPORT 9993
#define BACKLOG 20

char buffer[BUFFER_SIZE];

void str_echo2(int sockfd, char* buf) {
	ssize_t n;

	while (1) {
		n = Read(sockfd, buf, BUFFER_SIZE);

		if (n > 0)
			Write(sockfd, buf, n);
		else
			return;
	}
}

void sig_chld(int signo) {
	pid_t    pid;
	int      stat;

	while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);
	return;
}

int main(int argc, char *argv[]) {
	int     listenfd, connfd;
	size_t	clilen;
	struct sockaddr_in cliaddr, servaddr;
	int SERVPORT;

	if (argc != 2) {
		printf("usage: ser PORT\n");
		return 1;
	}

	SERVPORT = atoi(argv[1]);

	signal(SIGCHLD, sig_chld);

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
	servaddr.sin_port = htons(SERVPORT);
	Bind(listenfd, (struct sockaddr *) &servaddr,
		sizeof(servaddr));

	Listen(listenfd, BACKLOG);

	while (1) {
		clilen = sizeof(cliaddr);
		connfd = Accept(listenfd,
			(struct sockaddr *) &cliaddr,
			&clilen);
		if (connfd < 0 && connfd == EINTR)
			continue;
		if (Fork() ==0) {
			Close(listenfd);
			str_echo2(connfd, buffer);

			Close(connfd);

			return 0;
		}
		Close(connfd);
	}
	return 0;
}

3. 客户端

// cln.c
#include "unp.h"

#define BUFFER_SIZE BUFFSIZE
#define SERVPORT 9000
#define BACKLOG 20

char sbuffer[BUFFER_SIZE];
char rbuffer[BUFFER_SIZE];

void str_cli1(int infd, int outfd, int sockfd) {
	size_t n;

	while (1) {
		n = Read(infd, sbuffer, BUFFER_SIZE);
		if (n <= 0)
			return;	/* EOF */

		Write(sockfd, sbuffer, n);
		n = Read(sockfd, rbuffer, BUFFER_SIZE);
		if (n <= 0)
			return; /* FIN */
		Write(outfd, rbuffer, n);
	}
}

int main(int argc, char *argv[]) {
	int     sockfd, ret;
	struct sockaddr_in servaddr;

	if (argc != 2) {
		printf("usage: cln IP\n");
		return 1;
	}

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERVPORT);
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	Connect(sockfd, (struct sockaddr *) &servaddr,
		sizeof(servaddr));
	str_cli1(fileno(stdin), fileno(stdout),
		sockfd);     /* do it all */
	Close(sockfd);

	return 0;
}

运行结果如下:
1. 单Echo服务器
下图是单Echo服务在端口9009监听,并且中继服务器连接上,单客户端连接上,发送消息,并且断开的结果。
echo-rep-1

下图是单Echo服务在端口9009监听,并且中继服务器连接上,两个客户端连接上,发送消息,并且断开的结果。
echo-rep-2

2. 多Echo服务器
下组图是两个Echo服务在端口9009、9010监听,并且中继服务器连接上,三个客户端连接上,发送消息,并且断开的结果。
echo-rep-3

下图为三个客户端连接上中继服务器之后,然后退出后,Echo服务器和中继服务器的情况。可见,第1、3个客户端连接的数据是送往9009端口的Echo服务器,中间那个客户端连接的数据是送往9010端口的Echo服务器。
echo-rep-4

代码中unp.h的使用需要对Linux环境进行配置,代码中部分来源于NieXiaowen老师的课件,这是我在互联网程序设计这门课交的大作业,希望CV代码的朋友一定要注意这个问题,并且能够理解我的补充这么一句话的意思。

本文《Linux下实现简单Echo中继服务器》来自 www.juwends.com ,欢迎转载或CV操作,但请注明出处,谢谢!