はじめに
本稿ではsocketシステムコールを使用して、簡単なechoサーバをC言語で作成する。
使用するシステムコール
今回のTCP通信のネットワークプログラミングには、以下のシステムコールを使用する。
システムコール | 使用用途 |
---|---|
socket() | ソケットを作成する |
bind() | アドレスに名前をつける |
listen() | 接続を待ち受ける |
accept() | 接続を受け付ける |
write() or send() | ソケットに書き込む |
read() or recv() | ソケットから読み込む |
close() | ソケットを閉じる |
socketシステムコール
socketシステムコールでは、通信のためのソケット(出入り口)を作成する。
成功した場合、ソケットのファイルディスクリプターを返す。
エラーが発生した場合は -1 を返し、errno を適切に設定する。
socketシステムコールの書式
socketシステムコールの書式は以下になる。
#include <sys/types.h> // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain
domainには、プロトコルファミリを指定する。
代表的なプロトコルファミリを以下に記す。
プロトコルファミリ | 使用目的 |
---|---|
AF_INET | IPv4 インターネットプロトコル |
AF_INET6 | IPv6 インターネットプロトコル |
AF_UNIX AF_LOCAL | ローカル通信 |
AF_PACKET | 低レベルのパケットインターフェース |
今回は、IPv4を使用するので、AF_INET を指定する。
type
typeには、通信方式を指定する。
代表的な通信方式は以下になる。
通信方式 | 用途 |
---|---|
SOCK_STREAM | TCP通信 |
SOCK_DGRAM | UDP通信 |
SOCK_RAW | 生のネットワークプロトコルへのアクセス |
今回はTCP通信を行うので、SOCK_STREAM を指定する。
protocol
プロトコルの公式番号を指定する。
プロトコルファミリとソケットタイプの組み合わせによって決まり、自明の時は 0 を指定する。
今回は、上記からIPv4のTCP通信と自明なので、0 を指定する。
bindシステムコール
bindシステムコールは、socketシステムコールで作成したソケットにアドレスを割り当てる。
成功した場合は 0 を返す。
エラーが発生した場合は -1 が返し、errno を適切に設定する。
bindシステムコールの書式
bindシステムコールの書式は以下になる。
#include <sys/types.h> // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
三番目の引数である socklen_t は実際には、int である。
sockaddr_in構造体
ソケットに割り当てるアドレスは、第二引数に指定する。
AF_INET の場合は、以下の構造体になる。
struct sockaddr_in {
sa_family_t sin_family; // アドレスファミリ: AF_INET
in_port_t sin_port; // ポート番号
struct in_addr sin_addr; // インターネットアドレス
};
struct in_addr {
uint32_t s_addr; // アドレス
};
listenシステムコール
listenシステムコールでは、ソケットを接続待ちソケットとする。
接続待ちソケットは、acceptシステムコールで接続を受け付けられるソケットである。
成功した場合は 0 を返す。
エラーが発生した場合は -1 が返し、errno を適切に設定する。
listenシステムコールの書式
listenシステムコールの書式は以下になる。
#include <sys/types.h> // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>
int listen(int sockfd, int backlog);
backlog
backlogには、sockfd についての保留中の接続のキューの最大長を指定する。
acceptシステムコール
acceptシステムコールは、接続待ちソケット宛の接続要求のキューから先頭の接続要求を取り出し、接続済ソケットを作成する。
成功した場合、接続済ソケットのファイルディスクリプターを返す。
エラーが発生した場合は -1 を返し、errno を適切に設定する。
acceptシステムコールの書式
acceptシステムコールの書式は以下になる。
#include <sys/types.h> // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
ソースコード
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int sockfd, new_sockfd;
struct sockaddr_in addr, client_addr;
socklen_t len;
char c[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1) {
perror("socket");
exit(-1);
}
addr.sin_family = AF_INET; // AF_INETを指定
addr.sin_port = htons(12345); // ポート番号を 123445 に指定
addr.sin_addr.s_addr = 0; // 自動的に自分のIPを割り当てる
if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
exit(-1);
}
if( listen(sockfd, 5) == -1) {
perror("listen");
exit(-1);
}
while(1) {
len = sizeof(client_addr);
new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &len);
if(new_sockfd == -1) {
perror("accept");
continue;
}
printf("from %s port %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
while(read(new_sockfd, c, 1) > 0) {
write(1, c, 1);
}
close(new_sockfd);
}
close(sockfd);
return 0;
}
実行画面
プログラムを実行し、telnetでプログラムが待ち受けるポートに接続する。
% telnet localhost 12345
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!!
telnetで "Hello!" と入力した時のプログラムの出力は以下になる。
% ./a.out
from 127.0.0.1 port 49195
Hello!!
参照
- https://linuxjm.osdn.jp/html/LDP_man-pages/man2/socket.2.html
- https://linuxjm.osdn.jp/html/LDP_man-pages/man7/socket.7.html
- https://linuxjm.osdn.jp/html/LDP_man-pages/man2/bind.2.html
- https://linuxjm.osdn.jp/html/LDP_man-pages/man2/listen.2.html
- https://linuxjm.osdn.jp/html/LDP_man-pages/man2/accept.2.html
0件のコメント