目次
ネットワークプログラミングは、仕事でも面接でも、Linuxで非常に重要な知識です。この記事では、ソケットネットワーク通信について例を挙げて説明します。始めましょう。
1.基本的な考え方
ソケットは通常、ソケットと呼ばれ、ネットワーク内の異なるホスト上のアプリケーションプロセス間の双方向通信のエンドポイントを抽象化したものです。ソケットは、上位層アプリケーションの通信インターフェイスを提供します。次の図を参照してください。

上の図からわかるように、ソケットは本質的に抽象化レイヤーであり、次のさまざまなプロトコルをカプセル化し、上位レイヤーアプリケーションの呼び出しを容易にします。
2.原則
サーバ:
1.ソケット関数を使用してソケット記述子を作成します。
2.アドレスを作成し、bindを使用してアドレスをソケット記述子にバインドします(このソケットはリスニングソケットと呼ばれます)。
3.次に、listenを使用してソケット記述子をリスニングモードに設定し、接続の待機キューの長さを設定します。
4.接続がある場合は、acceptを使用して接続を取得し、指定されたクライアントとの接続を確立し、クライアントと通信するための新しいソケットを作成します。このソケットは接続ソケットと呼ばれます。
5.読み取り/書き込み機能を使用して相互に通信します。
6.すべての通信が完了したら、close機能を使用してソケットを閉じます。
クライアント:
1.ソケットを介してソケットを作成します。
2.接続を介して指定されたサーバーに接続します。
3.接続が成功したら、読み取り/書き込みを介して通信します。
4.通信が終了したら、クローズ機能でソケットを閉じます。
異なるアプリケーション間の通信プロセスは次のとおりです。

まず、上の図で使用されている関数を紹介します。
2.1ソケット
int socket(int domain, int type, int protocol);
この関数はint型のソケットを返します。これは一意であり、open関数によって生成されたファイル記述子と比較できます。
2.2バインド
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
この関数は、ソケットによって作成されたソケットにアドレスをバインドします。
2.3聞く
int listen(int sockfd, int backlog);
この関数はサーバーで使用され、サーバーがクライアント(他のプロセス)からの要求を受信できるようにします。
2.4受け入れる
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
この関数は、完了した接続のキューから接続を取得すると同時に、接続ソケットと呼ばれる新しい(接続された)ソケットを返します。ソケットによって作成されたソケットはリスニングソケットです。ソケットは1つだけです。接続されたソケットの各TCP接続が1つを生成します。
2.5接続
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
クライアントは接続機能を介してサーバーに接続します。TCPプロトコルを使用する場合、これは3方向のハンドシェイクプロセスです。
2.6読み取り/受信
ssize_t read(int fd,void *buf,size_t nbyte);
int recv(int sockfd,void *buf,int len,int flags);
この関数は、fd / sockfdからコンテンツを読み取る役割を果たします。
2.7書き込み/送信
ssize_t write(int fd, const void*buf,size_t nbytes);
int send(int sockfd,void *buf,int len,int flags);
書き込み/送信bufのデータをファイル記述子fd / sockfdに書き込みます。
2.8閉じる
int close(int sockfd);
ソケットを閉じます。
上記の関数は、成功した場合は0、失敗した場合は-1を返し、エラー情報はerrnoに格納されます。
3つの例
サーバーコード:
#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define PORT 3322 // 服务器端口
#define BACKLOG 1
#define MAXRECVLEN 1024
int main(int argc, char *argv[])
{
char buf[MAXRECVLEN];
int listenfd, connectfd; // listendfd 监听套接字 connectfd 已连接套接字
struct sockaddr_in server; // 服务器地址信息
struct sockaddr_in client; // 客户端地址信息
socklen_t addrlen;
// 创建套接字描述符
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket() error. Failed to initiate a socket");
return 0;
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
// 将地址 server 绑定到套接字 listenfd 上
if(bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
perror("Bind() error.");
return 0;
}
// 将套接字设置为监听模式,队列长度为 1
if(listen(listenfd, BACKLOG) == -1) {
perror("listen() error. \n");
return 0;
}
addrlen = sizeof(client);
while(1) {
// 取出一个客户端请求进行连接
if((connectfd = accept(listenfd,(struct sockaddr *)&client, &addrlen)) == -1) {
printf("accept() error. \n");
break;
}
printf("The client's ip : %s, port %d\n",inet_ntoa(client.sin_addr),htons(client.sin_port));
// 与客户端通信
char str[] = "Hello Client, I'm Server!\n";
while(1) {
int iret = recv(connectfd, buf, MAXRECVLEN, 0);
if(iret > 0) {
printf("%s\n",buf);
} else {
close(connectfd);
break;
}
send(connectfd, str, sizeof(str), 0);
}
}
// 关闭 socket
close(listenfd);
return 0;
}
クライアントコード:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define PORT 3322 //服务器端口
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int sockfd, num;
char buf[MAXDATASIZE];
struct hostent *host;
struct sockaddr_in server;
// 参数检查
if (argc != 2) {
printf("Usage: %s <IP Address>\n",argv[0]);
return 0;
}
if((host = gethostbyname(argv[1])) == NULL) {
printf("gethostbyname() error\n");
return 0;
}
// 创建套接字
if((sockfd = socket(AF_INET,SOCK_STREAM, 0))==-1) {
printf("socket() error\n");
return 0;
}
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)host->h_addr);
// 与服务器建立连接
if(connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
printf("connect() error\n");
return 0;
}
// 与服务器进行通信
char str[] = "Hello Server, I'm Client!\n";
while(true) {
if((num = send(sockfd,str,sizeof(str),0)) == -1) {
printf("send() error\n");
break;
}
if((num = recv(sockfd,buf,MAXDATASIZE,0)) == -1) {
printf("recv() error\n");
break;
}
buf[num-1]='\0';
printf("%s\n", buf);
sleep(3);
}
// 关闭套接字
close(sockfd);
return 0;
}
Makefile:
all: client server
client: client.c
gcc -o client client.c
server: server.c
gcc -o server server.c
clean:
rm -rf client server
コンパイルして実行し、ホストと通信した結果:

サーバーとクライアントの両方がローカルにある場合、ポートをバインドする代わりに共有ソケットファイルを使用して通信できます。
異なるホスト間の通信:


4、まとめ
上記は、ネットワーク通信がソケットを介して行われるかどうかを説明するアプリケーションの観点からのものであり、より深い観点から説明する時間があります。
5、参照
[1] https://www.cnblogs.com/langren1992/p/5101380.html
[2] https://blog.csdn.net/tanzongbiao/article/details/82344074
[3] https://blog.csdn.net/wjy741223284/article/details/98513237