1つのアプリケーションでLinuxの複数のポートをバインドしてリッスンすることは可能ですか?
3 に答える
聞きたいポートごとに、次のことを行います。
- で別のソケットを作成します
socket
。 - で適切なポートにバインドし
bind
ます。 - ソケットを呼び出し
listen
て、リッスンキューが設定されるようにします。
その時点で、プログラムは複数のソケットでリッスンしています。これらのソケットで接続を受け入れるには、クライアントが接続しているソケットを知る必要があります。select
たまたま、これを正確に実行するコードがあるので、複数のソケットで接続を待機し、接続のファイル記述子を返す完全なテスト例を次に示します。リモートアドレスは追加のパラメーターで返されます(バッファーは、acceptと同様に、呼び出し元によって提供される必要があります)。
(これはLinuxシステムでsocket_type
のtypedefであり、です。このコードはWindowsにも移植されているため、これらはあります。)int
INVALID_SOCKET
-1
socket_type
network_accept_any(socket_type fds[], unsigned int count,
struct sockaddr *addr, socklen_t *addrlen)
{
fd_set readfds;
socket_type maxfd, fd;
unsigned int i;
int status;
FD_ZERO(&readfds);
maxfd = -1;
for (i = 0; i < count; i++) {
FD_SET(fds[i], &readfds);
if (fds[i] > maxfd)
maxfd = fds[i];
}
status = select(maxfd + 1, &readfds, NULL, NULL, NULL);
if (status < 0)
return INVALID_SOCKET;
fd = INVALID_SOCKET;
for (i = 0; i < count; i++)
if (FD_ISSET(fds[i], &readfds)) {
fd = fds[i];
break;
}
if (fd == INVALID_SOCKET)
return INVALID_SOCKET;
else
return accept(fd, addr, addrlen);
}
このコードは、クライアントが接続したポートを呼び出し元に通知しませんがint *
、着信接続を確認したファイル記述子を取得するパラメーターを簡単に追加できます。
bind()
単一のソケットにのみ、listen()
そしてaccept()
-バインド用のソケットはサーバー用であり、からのfdaccept()
はクライアント用です。後者で選択を行い、入力で保留中のデータを持つクライアントソケットを探します。
このような状況では、 libeventに興味があるかもしれません。それはおそらくあなたのための仕事をselect()
します、おそらくのようなはるかに良いインターフェースを使用しますepoll()
。
の大きな欠点は、ソケット数を変数の最大ビット数(約100から256)に制限select()
するマクロを使用することです。2つまたは3つの接続を持つ小さなサーバーがある場合は、問題ありません。はるかに大きなサーバーで作業する場合は、簡単にオーバーフローする可能性があります。FD_...
fd_set
fd_set
また、select()
またはpoll()
を使用すると、サーバー内のスレッドを回避できます(つまりpoll()
、ソケットを使用して、、、、またはそれらを使用できるかどうかを知ることがaccept()
できread()
ますwrite()
)。
しかし、本当にUnixライクなようにしたい場合は、fork()
を呼び出す前に-ingを検討する必要がありますaccept()
。この場合、select()
orは絶対に必要ではありませんpoll()
(多くのIP /ポートをリッスンしていて、すべての子が着信接続に応答できるようにしたい場合を除きますが、それらには欠点があります...カーネルが別の要求を送信する可能性がありますあなたはすでにリクエストを処理していaccept()
ますが、カーネルは、呼び出し自体がない場合はビジーであることを認識します。accept()
まあ、それは正確には機能しませんが、ユーザーとしては、それがあなたのために機能する方法です。 )。
を使用してfork()
、メインプロセスでソケットを準備してhandle_request()
から、子プロセスを呼び出してaccept()
関数を呼び出します。そうすれば、任意の数のポートと、それぞれをリッスンする1つ以上の子を持つことができます。これは、Linuxで着信接続に非常に迅速に応答するための最良の方法です(つまり、ユーザーとして、子プロセスがクライアントを待機している限り、これは瞬時に行われます)。
void init_server(int port)
{
int server_socket = socket();
bind(server_socket, ...port...);
listen(server_socket);
for(int c = 0; c < 10; ++c)
{
pid_t child_pid = fork();
if(child_pid == 0)
{
// here we are in a child
handle_request(server_socket);
}
}
// WARNING: this loop cannot be here, since it is blocking...
// you will want to wait and see which child died and
// create a new child for the same `server_socket`...
// but this loop should get you started
for(;;)
{
// wait on children death (you'll need to do things with SIGCHLD too)
// and create a new children as they die...
wait(...);
pid_t child_pid = fork();
if(child_pid == 0)
{
handle_request(server_socket);
}
}
}
void handle_request(int server_socket)
{
// here child blocks until a connection arrives on 'server_socket'
int client_socket = accept(server_socket, ...);
...handle the request...
exit(0);
}
int create_servers()
{
init_server(80); // create a connection on port 80
init_server(443); // create a connection on port 443
}
ここでは、handle_request()
関数が1つのリクエストを処理するものとして示されていることに注意してください。単一の要求を処理する利点は、Unixの方法で処理できることです。必要に応じてリソースを割り当て、要求に応答したら、exit(0)
。は、必要な、などをexit(0)
呼び出します。close()
free()
対照的に、連続して複数のリクエストを処理する場合は、accept()
呼び出しにループバックする前に、リソースの割り当てが解除されていることを確認する必要があります。また、sbrk()
お子様のメモリフットプリントを削減するために関数が呼び出されることはほとんどありません。これは、時々少し成長する傾向があることを意味します。これが、Apache2などのサーバーが、新しい子を開始する前に、子ごとに特定の数の要求に応答するように設定されている理由です(デフォルトでは、最近は100〜1,000です)。