|
○ はじめに
前回 に引続き、サーバ側のプログラムを作成します。今回も UDP ソケット通信用システムコールを使っています。簡単には解説していますが、詳細はソケットプログラミング関連の書籍などを参照して下さい。
○ -サーバ- サンプルプログラム
サーバのサンプルプログラム server.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。
gcc -o server server.c
としてコンパイルします。コンパイルオプションは各自の環境などに応じて修正して下さい。
実行すると、UDP ポートの 13000 番に対して送信された音声パケットを受信してスピーカに出力します。サーバを起動した上で前回作成したクライアントを実行し、クライアント PC でマイクに向かって何か話したり歌ったりしてみて下さい。サーバ PC のスピーカから同じ音が出て来ましたか? 大したプログラムではないですが、自分で作ったプログラムがうまく動いた時の嬉しさを実感できると思います。
--- server.c ---
1 #include <fcntl.h> 2 #include <stdio.h> 3 #include <string.h> 4 #include <sys/ioctl.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <unistd.h> 8 #include <netdb.h> /* gethostbyname() ... */ 9 #include <netinet/in.h> /* htons() ... */ 10 #include <arpa/inet.h> /* inet_aton() ... */ 11 #include <linux/soundcard.h> 12 13 14 static int setup_dsp( int* dsp ); 15 static int setup_udp( int* sock, unsigned short port ); 16 17 18 /* 19 * サーバの UDP ポート 20 */ 21 #define PORT 13000 22 23 24 /* 25 * 量子化ビット数 : 16 bits 26 * サンプリング周波数 : 44.1 KHz 27 * チャンネル数 : 1 28 * 29 * フラグメントサイズを 100 ms 分の領域 30 * 31 * 16 (bits) * 44100 (Hz) * 0.1 (sec) = 8820 (bytes) 32 * 33 * を越えない最大の 2 の巾乗数とする 34 * ( SNDCTL_DSP_SETFRAGMENT の仕様による ) 35 */ 36 #define FRAGSIZE 8192 37 38 39 int 40 main( int argc, char** argv ) 41 { 42 struct sockaddr_in client_addr; 43 int sock; 44 int dsp; 45 int len; 46 int addr_len; 47 48 unsigned char buf[ FRAGSIZE ]; 49 50 51 /* /dev/dsp の設定 */ 52 if ( setup_dsp( &dsp ) != 0 ) { 53 fprintf( stderr, "Setup /dev/dsp failed.\n" ); 54 return 1; 55 } 56 57 /* UDP ソケットの設定 */ 58 if ( setup_udp( &sock, PORT ) != 0 ) { 59 fprintf( stderr, "Setup UDP socket failed.\n" ); 60 close( dsp ); 61 return 1; 62 } 63 64 addr_len = sizeof( struct sockaddr_in ); 65 66 /* 67 * エラーが起こるか強制終了されるまでループします。 68 */ 69 while ( 1 ) { 70 /* 71 * UDP ソケット入力を読み込むまでブロック 72 */ 73 if ( ( len = recvfrom( sock, buf, sizeof( buf ), 0, 74 ( struct sockaddr* )&client_addr, &addr_len ) ) == -1 ) { 75 76 perror( "recvfrom()" ); 77 close( dsp ); 78 close( sock ); 79 return 1; 80 } 81 82 /* 83 * /dev/dsp に書き込む 84 */ 85 if ( write( dsp, buf, len ) == -1 ) { 86 perror( "write()" ); 87 close( dsp ); 88 close( sock ); 89 return 1; 90 } 91 } 92 93 94 /* 95 * while() ループは終わらないのでここには来ません。。。 96 */ 97 close( dsp ); 98 close( sock ); 99 100 return 0; 101 } 102 103 104 /* 105 * /dev/dsp を設定する 106 */ 107 static int 108 setup_dsp( int* dsp ) 109 { 110 int fmt = AFMT_S16_LE; 111 int freq = 44100; 112 int channel = 1; 113 114 int bit; 115 int i; 116 int fragarg; 117 118 119 if ( ( *dsp = open( "/dev/dsp", O_WRONLY ) ) == -1 ) { 120 perror( "open()" ); 121 return -1; 122 } 123 124 /* 125 * フラグメントサイズの設定 126 * 127 * 0xMMMMSSSS ( MMMM はフラグメント数、SSSS は 128 * フラグメントサイズ = 2^{SSSS} となる数 ) 129 * 130 * の形式で設定するため、まず SSSS を求める。 131 * 132 * ( 0 <= SSSS <= 65535, ただしフラグメントサイズ 133 * は最大でも全バッファサイズ / 2 までしか 134 * 設定できない ) 135 * 136 * また MMMM は特に制限を設けないため 0x7fff 137 * を設定する。 138 */ 139 for ( i = 0, bit = 1; i <= 65535; bit <<= 1, i ++ ) { 140 if ( FRAGSIZE < bit ) { 141 i = ( i == 0 ) ? 0 : i - 1; 142 break; 143 } 144 } 145 146 fragarg = ( 0x7fff << 16 ) | i; 147 148 if ( ioctl( *dsp, SNDCTL_DSP_SETFRAGMENT, &fragarg )== -1 ) { 149 perror( "ioctl( SNDCTL_DSP_SETFRAGMENT )" ); 150 close( *dsp ); 151 return -1; 152 } 153 154 if ( ioctl( *dsp, SOUND_PCM_SETFMT, &fmt ) == -1 ) { 155 perror( "ioctl( SOUND_PCM_SETFMT )" ); 156 close( *dsp ); 157 return -1; 158 } 159 160 if ( ioctl( *dsp, SOUND_PCM_WRITE_CHANNELS, &channel ) == -1 ) { 161 perror( "ioctl( SOUND_PCM_WRITE_CHANNELS )" ); 162 close( *dsp ); 163 return -1; 164 } 165 166 if ( ioctl( *dsp, SOUND_PCM_WRITE_RATE, &freq ) == -1 ) { 167 perror( "ioctl( SOUND_PCM_WRITE_RATE )" ); 168 close( *dsp ); 169 return -1; 170 } 171 172 return 0; 173 } 174 175 176 /* 177 * クライアントとの通信用に UDP ソケットを設定する 178 */ 179 static int 180 setup_udp( int* sock, unsigned short port ) 181 { 182 struct sockaddr_in server_addr; 183 184 if ( ( *sock = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 ) { 185 perror( "socket()" ); 186 return -1; 187 } 188 189 memset( &server_addr, 0, sizeof( struct sockaddr_in ) ); 190 191 server_addr.sin_family = AF_INET; 192 server_addr.sin_addr.s_addr = INADDR_ANY; 193 server_addr.sin_port = htons( port ); 194 195 if ( bind( *sock, ( struct sockaddr* )&server_addr, 196 sizeof( struct sockaddr_in ) ) == -1 ) { 197 198 perror( "bind()" ); 199 close( *sock ); 200 return -1; 201 } 202 203 return 0; 204 } 205 206 207 /* End of server.c */
○ サンプルプログラムの説明
では、サンプルプログラムについて説明していきます。
- 14 〜 15 行目
自作内部関数のプロトタイプ宣言です。setup_dsp() は /dev/dsp の設定を行います。setup_udp() は、 クライアントとの通信を行うための UDP ソケットの設定を行います。
- 18 〜 21 行目
クライアントからの音声を受信するための UDP ポート番号を定義しています。 この値はクライアントの値と一致していてかつ他のソフトウェアから使用されていなければ、 どんな値でも構いません。
- 24 〜 36 行目
前回 の client.c の FRAGSIZE と同じ値の fragment サイズを定義しています。16bits、44.1 KHz、100ms 分のバッファサイズです。
- 39 〜 101 行目 ( main() )
main() 関数では、まず /dev/dsp の設定 ( setup_dsp() ) および UDP ソケットの設定 ( setup_udp() ) を行った後に、受信した音声パケットを /dev/dsp に書き込む while() ループを実行します。
while() ループでは、まず UDP ソケットから FRAGSIZE 分のデータを recvfrom() で読み込み、 読み込んだパケットを write() で /dev/dsp に書き込んでいます。この書き込みにより、 音声がスピーカから出力されます。
#include <sys/types.h> #include <sys/socket.h> int recvfrom( int s, void* buf, size_t len, int flags, struct sockaddr* from, socklen_t* fromlen ); s : socket() システムコールで作成したソケット buf : 受信データ格納用バッファへのポインタ len : 受信するデータ長 flags : ソケット通信用フラグ。詳細はマニュアル参照。ここでは使用しないため 0 を設定 from : 送信元情報へのポインタ(送信元情報が格納される) fromlen : from の実データの大きさ
エラーが発生した場合は -1 が返され、それ以外は受信したデータ長が返されます。
- 104 〜 173 行目 ( setup_dsp() )
/dev/dsp の設定を行っています。前回 の client.c の setup_dsp() と一ヶ所を除いて全く同じ内容です。唯一、119 行目で /dev/dsp を O_WRONLY ( 書き込みのみ ) モードで開いている点が異なります。
- 176 〜 204 行目 ( setup_udp() )
クライアントとの通信に使用する、UDP ソケットの設定を行っています。
184 〜 187 行目では、socket() システムコールによって通信に使用するソケットの準備を行っています。
#include <sys/types.h> #include <sys/socket.h> int socket( int domain, int type, int protocol ); domain : IP 通信の場合は PF_INET もしくは AF_INET を指定する type : UDP の場合は SOCK_DGRAM を指定する protocol : ここでは 0 を指定して下さい
成功すると、準備されたソケットを参照するディスクリプタが返されます。 以後はこの値を使用してソケットを指定します。エラーが発生した場合は -1 が返されます。
191 〜 193 行目では、自分の情報を struct sockaddr_in 型構造体に格納しています。 後述の bind() システムコールで使用します。
191 行目でクライアントとの通信に用いるプロトコルを ( IP の場合は PF_INET もしくは AF_INET )、 192 行目でパケットの受信待ちを行う IP アドレスを設定 ( INADDR_ANY を指定することにより、 実際のアドレス値の設定を OS に依頼している ) し、193 行目でポート番号を設定しています。 ポート番号については、ネットワークバイトオーダという形式で数値を格納する必要がありますので、 unsisgned short 型の数値をネットワークバイトオーダに変換する htons() 関数を使っています。
195 〜 201 行目では bind() システムコールを使って、作成したソケットにアドレスを設定しています。 これにより、OS が該当アドレス:ポートに対して送信されたパケットの受信を開始してくれます。
#include <sys/types.h> #include <sys/socket.h> int bind( int sockfd, struct sockaddr* my_addr, socklen_t addrlen ); sockfd : 作成したソケット my_addr : ソケットに設定するアドレス情報 addrlen : my_addr のデータ長
成功すると 0 を返し、エラーが発生した場合には -1 を返します。
server.c の説明は以上です。サーバ実行後にクライアントを実行して、クライアントのマイクに入力した音声がサーバのスピーカから聞こえて来る感動をぜひ味わってみて下さい!