2023.6.28 ネットワークプログラミングの学習を本格的に開始しました。各章とセクションのメモは、レビュー用にブログに記録されます。
第1章
1.1 ネットワークプログラミングとソケットについて
ネットワーク プログラミングはソケット プログラミングとも呼ばれます。いわゆるネットワーク プログラミングとは、ネットワークに接続された 2 台のコンピュータが相互にデータを交換できるようにするプログラムを作成することです。なぜソケットプログラミングと呼ばれるのでしょうか? 私たちは普段、コンセントにプラグを差し込んで電力網から電力を得るのと同じように、遠隔地のコンピュータとデータ通信をするためにはインターネットに接続する必要があり、プログラミングにおける「ソケット」はそのツールです。ネットワークに接続します。
サーバー側で作成されるソケットは、サーバーサイドソケットまたはリスニングソケットとも呼ばれます。接続を要求するためのソケット作成プロセスは、次の 4 つのステップに分かれています。
- ソケット関数を呼び出してソケットを作成します
- バインド関数を呼び出してIPアドレスとポート番号を割り当てます。
- listen関数を呼び出してリクエスト受付状態に遷移します。
- accept 関数を呼び出して接続要求を受け入れます。
接続を要求するクライアントソケットの作成プロセスは次のとおりです。
- ソケット関数を呼び出してソケットを作成します
- connect 関数を呼び出してサーバーに接続リクエストを送信します。
ソケットを作成した後は、すぐにはサーバーかクライアントとして区別されないことに注意してください。次にbind、listenなどの関数を呼び出すとサーバー側ソケットとなり、connect関数を呼び出すとクライアントソケットと呼ばれます。
次に、Linux 環境で上記の 2 つの例 ( hello_server.cファイルとhello_client.cファイル) をコンパイルして実行します。
クライアント プログラムとサーバー プログラムを別々にコンパイルします。
gcc hello_server.c -o hserver
gcc hello_client.c -o hclient
このコマンドの-oは実行ファイル名を指定するためのオプションパラメータであるため、コンパイル後に実行ファイルhserver、hclientが生成されます。
走る:
./hserver 9190
./hclient 127.0.0.1 9190
実行中は、まずポート 9190 でサービスを開始し、次にサーバーはクライアントの応答を待ちます。クライアントがローカル IP アドレス 127.0.0.1 でポート 9190 をリッスンすると、クライアントはサーバーから応答を受け取ります。 「Hello World!」を出力します。
ps: 実行中に入力された 127.0.0.1 は、ローカル コンピューターの IP アドレスです。この接続方法は、サーバーとクライアントの両方が同じコンピューター上で実行されている場合に使用されます。ただし、サーバーとクライアントが異なるコンピューターで実行されている場合は、サーバーが配置されているコンピューターの IP アドレスを使用する必要があります。
1.2 Linux ベースのファイル操作
低レベルのアクセスとファイル記述子
Linux では、ソケットもファイルの一種であるため、ネットワーク データ送信中にファイル I/O 関連の機能を使用できます。Windows はソケットとファイルを区別する必要があるため、Windows で特別なデータ転送関連関数を呼び出す必要があります。
ファイルまたはソケットが生成されるたびに、オペレーティング システムは、それらに割り当てられた整数 (つまり、ファイル記述子) を返します。この整数は、プログラマとオペレーティング システム間の優れた通信チャネルになります。実際、ファイル記述子は、システムによって作成されたファイルまたはソケットに都合よく割り当てられる番号。ファイル記述子はファイル ハンドルとも呼ばれます。「ハンドル」は Windows の用語であり、Linux プラットフォームでは「記述子」が使用されます。
ファイルを開く
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path, int flag);
/*
成功时返回文件描述符,失败时返回-1
path : 文件名的字符串地址
flag : 文件打开模式信息
*/
ファイルを閉じる
#include <unistd.h>
int close(int fd);
/*
成功时返回 0 ,失败时返回 -1
fd : 需要关闭的文件或套接字的文件描述符
*/
ファイル記述子引数を渡してこの関数を呼び出すと、応答ファイルは閉じられます (終了されます)。もう 1 つ注意すべき点は、この関数はファイルだけでなくソケットも閉じることができるということです。「Linuxオペレーティングシステムはファイルとソケットを区別しない」という特徴を改めて証明しました。
データをファイルに書き込む
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
/*
成功时返回写入的字节数 ,失败时返回 -1
fd : 显示数据传输对象的文件描述符
buf : 保存要传输数据的缓冲值地址
nbytes : 要传输数据的字节数
*/
この関数の定義では、size_t は typedef で宣言された unsigned int 型です。ssize_t の場合、ssize_t の前の余分な s は signed を表します。つまり、ssize_t は typedef を通じて宣言された signed int 型です。
新しいファイルを作成してデータを保存します。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
void error_handling(char *message);
int main()
{
int fd;
char buf[] = "Let's go!\n";
// O_CREAT | O_WRONLY | O_TRUNC 是文件打开模式,将创建新文件,并且只能写。如存在 data.txt 文件,则清空文件中的全部数据。
fd = open("data.txt", O_CREAT | O_WRONLY | O_TRUNC);
if (fd == -1)
error_handling("open() error!");
printf("file descriptor: %d \n", fd);
// 向对应 fd 中保存的文件描述符的文件传输 buf 中保存的数据。
if (write(fd, buf, sizeof(buf)) == -1)
error_handling("write() error!");
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
実行後、data.txt
ファイルが生成されます。Let's go!
ファイルからデータを読み取る
前の関数にwrite()
対応し、read()函数
データの入力 (受信) に使用されます。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
/*
成功时返回接收的字节数(但遇到文件结尾则返回 0),失败时返回 -1
fd : 显示数据接收对象的文件描述符
buf : 要保存接收的数据的缓冲地址值。
nbytes : 要接收数据的最大字节数
*/
次のコードは、read() 関数を通じて data.txt に保存されたデータを読み取ります。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
void error_handling(char *message);
int main()
{
int fd;
char buf[BUF_SIZE];
fd = open("data.txt", O_RDONLY);
if (fd == -1)
error_handling("open() error!");
printf("file descriptor: %d \n", fd);
if (read(fd, buf, sizeof(buf)) == -1)
error_handling("read() error!");
printf("file data: %s", buf);
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
実験結果:
low.open.c はファイルを作成してファイル データを保存し、実行後にファイル記述子 3 を返します。cat コマンドを使用して data.txt のファイル内容を出力し、データが実際にファイルに転送されたことを確認します。
low.read.c プログラムは、read 関数を通じて data.txt に保存されたデータを読み取ります。実行後、ファイル記述子とファイルの内容を出力します。
fd_seri.c プログラムは、ファイルとソケットの両方を作成します。
fd1 = ソケット(PF_INET, SOCK_STREAM, 0);
fd2 = open("test.dat", O_CREAT | O_WRONLY | O_TRUNC);
fd3 = ソケット(PF_INET, SOCK_DGRAM, 0);
次に、それぞれのファイル記述子を出力して、3、4、および 5 を取得します。0、1、および 2 は I/O ラベルに割り当てられた記述子であるため、記述子には 3 から始まる昇順で番号が付けられます。以下に示すように:
1.5 演習
1. ネットワーク プログラミングにおけるソケットの役割は何ですか? なぜソケットと呼ばれるのでしょうか?
ソケットは、ネットワーク上で通信するために使用されるプログラミング インターフェイスです。ソケットを使用すると、さまざまなコンピュータ上のプロセスがネットワーク経由でデータを転送できるようになります。これは、コンピュータが接続を確立し、データを送受信できるようにするメカニズムを提供します。ソケットを使用すると、アプリケーションはネットワーク経由で通信し、クライアントとサーバー間でデータを交換できるようになります。
電話通信のソケットに似ており、ネットワーク通信のエンドポイント接続ポイントとして機能するため、ソケットと呼ばれます。
2. サーバー側でソケットを作成した後、listen 関数、accept 関数が順番に呼び出されます。両者の機能を比較して説明してください。
listen 関数は、ソケットをパッシブ リスニング モードに設定し、クライアント接続要求を受け入れるために使用されます。
accept 関数は、クライアントの接続要求を受け入れ、クライアントとの通信を処理するための新しいソケットを作成するために使用されます。
3. Linux では、ソケット データに対する I/O を実行するときにファイル I/O 関連関数を直接使用できますが、Windows では使用できません。
これは、Linux を含む Unix 系システムでは、すべてがファイルであるという考えが広く採用されており、ソケットを含むさまざまなリソースがファイル記述子の形式に抽象化され、データの読み書きインターフェイスが統一されているためです。
ただし、Windows オペレーティング システムでは、ソケットはファイル記述子とはみなされないため、ファイル I/O 関連の関数をデータの読み取りおよび書き込みに直接使用することはできません。
4. ソケットの作成後、通常、ソケットにアドレスが割り当てられますが、なぜですか? アドレスの割り当てを完了するにはどの関数を呼び出す必要がありますか?
ネットワーク プログラミングでは、主に他のコンピュータがそのソケットを見つけて接続を確立できるように、ソケットを作成した後にアドレスを割り当てる必要があります。アドレスを割り当てることにより、ソケットの IP アドレスとポート番号を指定し、それによってネットワーク上のソケットの場所を一意に識別します。
アドレスの割り当てを完了するには、bind 関数を呼び出す必要があります。バインド関数は、ソケットを特定の IP アドレスとポート番号にバインドするために使用されます。