目次
ソケットプログラミングで使用される重要な構造:struct sockaddr
SOCKETプログラミング
- 従来のプロセス間通信は、カーネルが提供するIPCメカニズムを使用して実行されますが、ローカルマシン通信に限定できます。マシン間で通信する場合は、ネットワーク通信を使用する必要があります。(基本的にカーネルによって-カーネルは通信を実現するためのソケット疑似ファイルメカニズムを提供します-実際にはファイル記述子を使用します)、これにはカーネルによってユーザーに提供されるソケットAPI関数ライブラリの使用が必要です
- ソケット疑似ファイルについて言及しているので、ファイル記述子に関連する関数readwriteを使用できます。
- ソケットを使用すると、ソケットペアが作成されます
- 次の図に示すように、1つのファイル記述子が2つのバッファを操作します。これは、1つのカーネルバッファを操作する2つのファイル記述子であるパイプとは異なります。
ネットワークバイトオーダー
- ビッグエンディアンとリトルエンディアンの概念
- ビッグエンディアン:下位アドレスは上位データを格納し、上位アドレスは下位データを格納します
- リトルエンディアン:下位アドレスは下位データを格納し、上位アドレスは上位データを格納します
- ビッグエンディアンとリトルエンディアンの機会を使用します。
- ビッグエンディアンとリトルエンディアンは、int shortなど、長さが2つ以上のデータタイプしかないため、1バイトに制限はなく、ネットワーク内のビッグエンディアンとリトルエンディアンのIPとポートを考慮する必要があることがよくあります。
- ネットワーク伝送にはビッグエンディアンが使用されます。マシンがリトルエンディアンを使用する場合は、ビッグエンディアン変換が必要です。
- 次の4つの関数は、大小のエンディアン変換を実行するための関数です。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
- 関数名のhはホスト、nはネットワーク、sは短い、lは長いことを意味します
- 上記のいくつかの関数は、関数内で変換する必要がない場合は変換されません。
IPアドレス変換機能
- p->は、ドット付き10進表記の文字列形式を表します
- to-> to
- n->はネットワークを意味します
int inet_pton(int af, const char *src, void *dst);
- 機能の説明:文字列形式のドット付き10進IPをビッグエンディアンモード(形状4バイト)のネットワークIPに変換します
- パラメータの説明:
- 作成者:AF_INET
- src:文字列形式のドット付き10進IPアドレス
- dst:変換された変数のアドレスを格納します
- 例:inet_pton(AF_INET、 "127.0.0.1"、&serv.sin_addr.s_addr);
- 手動で計算することもできます。192.168.232.145のように、最初に4つの正の数を16進数に変換します。
- 192 ---> 0xC0 168 ---> 0xA8 232 ---> 0xE8 145 ---> 0x91
- 最後に、ビッグエンドのバイト順序で格納されます:0x91E8A8C0、これは4バイトの整数値です。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- 機能の説明:ネットワークIPを文字列形式のドット付き10進IPに変換します
- パラメータの説明:
- 作成者:AF_INET
- src:ネットワークの整形されたIPアドレス
- dst:変換されたIPアドレス、通常は文字列配列
- サイズ:dstの長さ
- 戻り値:
- 成功-dstへのポインタを返します
- 失敗-NULLを返し、errnoを設定します
- 例:IPアドレスは010aa8c0で、点線の10進形式に変換されます。
- 01 ----> 1 0a ----> 10 a8 ----> 168 c0 ----> 192
- スレーブネットワークのIPアドレスはハイエンドモードであるため、点線の10進数に変換する必要があります:192.168.10.1
ソケットプログラミングで使用される重要な構造:struct sockaddr
- struct sockaddr構造の説明:
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
- struct sockaddr_in構造:
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ }; //网络字节序IP--大端模式
- man 7ipから関連する手順を表示できます
ソケットプログラミングの主なAPI機能の紹介
int socket(int domain, int type, int protocol);
- 機能の説明:ソケットを作成します
- パラメータの説明:
- ドメイン:プロトコルバージョン
- AF_INET IPV4
- AF_INET6 IPV6
- AF_UNIXAF_LOCALローカルソケットの使用
- タイプ:プロトコルタイプ
- SOCK_STREAMストリーミング、使用されるデフォルトのプロトコルはTCPプロトコルです
- SOCK_DGRAMレポート形式、UDPプロトコルがデフォルトで使用されます
- プロトコル:
- 通常、0を入力します。これは、対応するタイプのデフォルトプロトコルを使用することを意味します。
- 戻り値:
- 成功:0より大きいファイル記述子を返します
- 失敗:-1を返し、errnoを設定します
- ソケット関数が呼び出されると、ファイル記述子が返され、カーネルはファイル記述子に対応する読み取りバッファーと書き込みバッファーを提供します。同時に、要求接続キューと接続キューの2つのキューがあります。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 機能の説明:ソケットファイル記述子をIPおよびPORTにバインドします
- パラメータの説明:
- ソケット:ソケット関数を呼び出すことによって返されるファイル記述子
- addr:ローカルサーバーのIPアドレスとPORT、
struct sockaddr_in serv; serv.sin_family = AF_INET; serv.sin_port = htons(8888); //serv.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY: 表示使用本机任意有效的可用IP inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);
- addrlen: addr変数のメモリサイズ
- 戻り値:
- 成功:0を返します
- 失敗:-1を返し、errnoを設定します
int listen(int sockfd, int backlog);
- 機能の説明:ソケットをメインダイナミックからダイナミックに変更します
- パラメータの説明:
- sockfd:ソケット関数を呼び出すことによって返されるファイル記述子
- バックログ:同時リクエストの最大数(接続はまだ確立されていません)
- 戻り値:
- 成功:0を返します
- 失敗:-1を返し、errnoを設定します
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 機能の説明:接続を取得します。現在接続がない場合は、待機をブロックします。
- 機能パラメータ:
- sockfd:ソケット関数を呼び出すことによって返されるファイル記述子
- addr:発信パラメータ、クライアントのアドレス情報を保存します
- addrlen:着信および発信パラメータ、addr変数が占めるメモリスペース
- 戻り値:
- 成功:クライアントと通信するための新しいファイル記述子を返します
- 失敗:-1を返し、errno値を設定します。
- accept関数はブロッキング関数であり、新しい接続要求がない場合は常にブロックされます。
- 接続されたキューから新しい接続を取得し、クライアントとの通信に使用される新しいファイル記述子を取得します(カーネルは、要求キュー内の接続されたキューへの接続を取得する責任があります)。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 機能の説明:サーバーに接続します
- 機能パラメータ:
- sockfd:ソケット関数を呼び出すことによって返されるファイル記述子
- addr:サーバーアドレス情報
- addrlen: addr変数のメモリサイズ
- 戻り値:
- 成功:0を返します
- 失敗:-1を返し、errno値を設定します
- 次に、書き込みおよび読み取り機能を使用して、読み取りおよび書き込み操作を実行できます。
- 読み取り/書き込み機能の使用に加えて、recvおよびsend機能も使用できます。
- データの読み取りと送信:
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- recvとsendの2つの関数フラグに対応して、0を直接入力するだけです。
- 注:書き込みバッファーがいっぱいの場合、書き込みもブロックされます。読み取り操作中に、読み取りバッファーにデータがない場合、ブロックが発生します。
サーバーおよびクライアントプログラムを作成するためのSocketAPI関数の手順
サーバー開発プロセス
- ソケットを作成し、ファイル記述子を返しますlfd --- socket()-ファイル記述子はクライアント接続を監視するために使用されます
- lfdとIPポートをバインド---- bind()
- lfdをアクティブモニタリングからパッシブモニタリングに変更---- listen()
- 新しい接続を受け入れ、ファイル記述子を取得しますcfd ---- accept()---ファイル記述子はクライアントとの通信に使用されます
- while(1)
- {{
- データの受信---読み取りまたは受信
- データの送信---書き込みまたは送信
- }
- ファイル記述子を閉じます-close(lfd)close(cfd);
クライアント開発プロセス
- ソケットを作成し、ファイル記述子を返しますcfd --- socket()-ファイル記述子はサーバーとの通信に使用されます
- サーバーに接続します--- connect()
- while(1)
- {{
- //データを送信---書き込みまたは送信
- //データを受信---読み取りまたは受信
- }
- close(cfd)
サーバープログラムの例
//服务端程序 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <ctype.h> int main() { //创建socket //int socket(int domain, int type, int protocol); int lfd = socket(AF_INET, SOCK_STREAM, 0); if(lfd<0) { perror("socket error"); return -1; } //int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //绑定 struct sockaddr_in serv; bzero(&serv, sizeof(serv)); serv.sin_family = AF_INET; serv.sin_port = htons(8888); serv.sin_addr.s_addr = htonl(INADDR_ANY); //表示使用本地任意可用IP int ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv)); if(ret<0) { perror("bind error"); return -1; } //监听 //int listen(int sockfd, int backlog); listen(lfd, 128); //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); struct sockaddr_in client; socklen_t len = sizeof(client); int cfd = accept(lfd, (struct sockaddr *)&client, &len); //len是一个输入输出参数 //const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); //获取client端的IP和端口 char sIP[16]; memset(sIP, 0x00, sizeof(sIP)); printf("client-->IP:[%s],PORT:[%d]\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, sIP, sizeof(sIP)), ntohs(client.sin_port)); printf("lfd==[%d], cfd==[%d]\n", lfd, cfd); int i = 0; int n = 0; char buf[1024]; while(1) { //读数据 memset(buf, 0x00, sizeof(buf)); n = read(cfd, buf, sizeof(buf)); if(n<=0) { printf("read error or client close, n==[%d]\n", n); break; } printf("n==[%d], buf==[%s]\n", n, buf); for(i=0; i<n; i++) { buf[i] = toupper(buf[i]); } //发送数据 write(cfd, buf, n); } //关闭监听文件描述符和通信文件描述符 close(lfd); close(cfd); return 0; }
- テスト(テストツールncを使用):
nc ip 端口号
クライアントプログラムの例
//客户端代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> int main() { //创建socket---用于和服务端进行通信 int cfd = socket(AF_INET, SOCK_STREAM, 0); if(cfd<0) { perror("socket error"); return -1; } //连接服务端 //int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); struct sockaddr_in serv; serv.sin_family = AF_INET; serv.sin_port = htons(8888); inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr); printf("[%x]\n", serv.sin_addr.s_addr); int ret = connect(cfd, (struct sockaddr *)&serv, sizeof(serv)); if(ret<0) { perror("connect error"); return -1; } int n = 0; char buf[256]; while(1) { //读标准输入数据 memset(buf, 0x00, sizeof(buf)); n = read(STDIN_FILENO, buf, sizeof(buf)); //发送数据 write(cfd, buf, n); //读服务端发来的数据 memset(buf, 0x00, sizeof(buf)); n = read(cfd, buf, sizeof(buf)); if(n<=0) { printf("read error or server closed, n==[%d]\n", n); break; } printf("n==[%d], buf==[%s]\n", n, buf); } //关闭套接字cfd close(cfd); return 0; }
- テスト(netstatを使用):
- [注]:dark horse linux C ++チュートリアルを参照してください