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