1. 同時サーバーモデル
【1】ループサーバー
1>一度に処理できるクライアントのリクエストは 1 つだけであり、次のクライアントはクライアントが終了した後にのみ処理できます。
2>欠点: ループサーバーによって処理されるクライアントは時間のかかる操作を行うことができません。
//*****モデル******
sfd = ソケット();
練る();
聞く();
while(1)
{ newfd = accept(); while(1) { recv(); 送信(); 閉じる (newfd); 閉じる(sfd);
【2】コンカレントサーバー
1>複数のクライアントリクエストを同時に処理できる
2>親プロセス/メインスレッドは特に接続を担当し、クライアントと対話するための子プロセス/ブランチスレッドを作成します。
~マルチスレッド:
//*****モデル******
sfd = ソケット();
練る();
聞く();
while(1)
{ newfd = accept();pthread_create();
pthread_detach(tid);
}
閉じる(sfd);
void * callBack(void * arg)
{
受信();
送信();
閉じる(newfd);pthread_exit(NULL);
}
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/wait.h>
#include<signal.h>
#include<stdlib.h>
#include<pthread.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.0.79" //本机IP
#define PORT 6666 // 1024-49151
void* deal_cli_msg(void *arg);
//需要传递给线程处理函数的参数
struct cli_msg
{
int newfd;
struct sockaddr_in cin;
};
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
printf("sfd=%d\n",sfd);
//允许端口快速重用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充地址信息结构体
//真是的地址信息结构体根据地址族制定 AF_INET: man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET;//必须填 AF_INET
sin.sin_port = htons(PORT);//端口号, 1024-49151
sin.sin_addr.s_addr = inet_addr(IP);//本机IP,ifconfig
//将IP和端口号绑定到套接字上
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess __%d__\n",__LINE__);
//将套接字设置为被动监听状态,监听是否有客户端连接成功
if(listen(sfd,128)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
struct sockaddr_in cin; // 存储连接成功的客户端地址信息
socklen_t addrlen = sizeof(cin);
pthread_t tid;
int newfd=-1;
struct cli_msg info;
while(1)
{
//阻塞函数,从已完成连接的队列头中获取一个客户端信息
//该文件描述符才是与客户端通信的文件描述符
newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
info.newfd=newfd;
info.cin=cin;
//能运行到当前位置
if(pthread_create(&tid,NULL,deal_cli_msg,&info)!=0)
{
fprintf(stderr,"line:%d pthread_createfailed\n",__LINE__);
return -1;
}
pthread_detach(tid); //分离线程
}
close(sfd);
return 0;
}
void* deal_cli_msg(void *arg)
{
int newfd=((struct cli_msg*)arg)->newfd;
struct sockaddr_in cin=((struct cli_msg*)arg)->cin;
char buf[128]="";
ssize_t res=0;
while(1)
{
//接收
res=recv(newfd,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
break;
}
else if(0==res)
{
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
break;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//发送
strcat(buf,"*_*");
if(send(newfd,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
break;
}
printf("send sucess\n");
}
close(newfd);
pthread_exit(NULL);
}
2.IOモデル
【1】IOのブロッキング
ソケット ファイル記述子を作成すると、デフォルトでブロック IO モード (読み取り、書き込み、recv、send、recvfrom、sendto) になります。
【2】ノンブロッキングIO
1>プロセスが IO 機能でブロックするのを防ぎますが、有効なデータを取得したい場合は、ポーリングする必要があります
2>プログラムがノンブロッキング IO モード ソケットを使用する場合、ループを使用してファイル記述子に読み取り可能なデータがあるかどうかを常に判断する必要があります。これはポーリングと呼ばれます。
3>アプリケーションは IO イベントが発生するかどうかを監視するためにカーネルをポーリングし続けるため、CPU 消費量が増加します。
1) fcntl関数
【3】シグナルドリブンIO
1>非同期通信方式
2>シグナル駆動型 IO とは、特定のファイル記述子で IO イベントが発生すると、カーネルが関連するプロセスに通知することをカーネルに事前に通知することを意味します: SIGIO
3> TCP の場合、信号が頻繁に生成され、どのファイル記述子が送信されたかを区別できないため、信号駆動型 IO は TCP には役に立ちません。
[4] IO多重化(強調)
1>プロセス内で複数の入出力ストリームを同時に処理する必要がある場合は、単一のプロセスと単一のスレッドを使用して、複数の入出力リクエストを同時に処理します。
2>マルチプロセスとマルチスレッドが使用できない場合は、IO多重化の使用を選択できます
3> 新しいプロセスやスレッドを作成する必要がないため、システム リソースのオーバーヘッドとコンテキスト スイッチの数が削減されます。
4> 複数の IO 操作を同時に実行できるようにします。プロセスが 1 つ以上の IO イベントを実行していることをカーネルが検出すると、プロセスに通知します。
1)選択
モデル:
コード:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/time.h>
#include<sys/select.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.0.211" //本机IP
#define PORT 6666 // 1024-49151
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess __%d__\n",__LINE__);
//允许端口快速使用
int reuse=-1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
printf("允许端口快速重用成功\n");
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port=htons(PORT);//端口号
sin.sin_addr.s_addr=inet_addr(IP);//本机号
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess__%d__\n",__LINE__);
//将套接字设置为被动监听状态,监听是否有客户端连接成功
if(listen(sfd,128)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
//设置一个读集合
fd_set readfds,tmpfds;
//由于readfds中需要防止要检测的文件描述符,所以不能让他是随机值
//所以需要将readfds清空
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
//将需要的文件描述符添加到集合中
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int s_res=0;
char buf[128]="";
struct sockaddr_in cin; // 存储连接成功的客户端地址信息
socklen_t addrlen = sizeof(cin);
struct sockaddr_in saveCin[1024-4];//另存客户端地址信息,0,1,2,sfd不可能有对应的客户端
int newfd =-1;
ssize_t res=0;
int maxfd=sfd;//最大文件描述符
while(1)
{
tmpfds=readfds;
s_res=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
if(s_res<0)
{
ERR_MSG("select");
return -1;
}
else if(0==res)
{
printf("time out\n");
return -1;
}
//能运行到当前位置,则代表有文件描述符准备就绪
//走触发事件的文件描述符对应的处理函数
//当前集合中有文件描述符准备就绪了
//当准备就绪,集合会只保留0
//当sfd准备就绪,集合就会只保留sfd
//当0和sfd都准备就绪,集合中保留0和sfd
for(int i=0;i<=maxfd;i++)
{
if(!FD_ISSET(i,&tmpfds))//如果不在集合中,则直接往后继续变量
{
continue;
}
//能运行到当前位置,则说明i代表的文件描述符在tmpfds中
//要判断i所代表的文件描述符需要走什么对应的函数
if(0==i)
{
printf("触发键盘输入事件>>");
int sndfd=-1;
res=scanf("%d %s",&sndfd,buf);
while(getchar()!=10);
if(res!=2)
{
fprintf(stderr,"请输入正确格式:int string\n");
continue;
}
//能运行到当前位置,则代表输入的格式整数
if(sndfd<=sfd||sndfd>=1024||!FD_ISSET(sndfd,&readfds))
{
fprintf(stderr,"sndfd=%d文件描述符\n",sndfd);
continue;
}
if(send(sndfd,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
}
else if(sfd==i)
{
printf("触发客户端连接事件>>");
fflush(stdout);
int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
saveCin[newfd-4]=cin;
//将newfd添加到读集合中
FD_SET(newfd,&readfds);
//更新maxfd
maxfd=maxfd>newfd?maxfd:newfd;
}
else
{
bzero(buf,sizeof(buf));
//接收
res=recv(i,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
return -1;
}
else if(0==res)
{
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//关闭文件描述符
close(i);
//从集合中剔除该文件描述符
FD_CLR(i,&readfds);
//更新maxfd
//从目前最大的文件描述符中往小的判断
int j=0;
for(int j=maxfd;j>=0;j--)
{
if(FD_ISSET(j,&readfds))
{
maxfd=j;
break;
}
}
if(j<0)
maxfd=-1;
continue;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
strcat(buf,"*_*");
if(send(i,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
printf("send sucess\n");
}
}
}
close(sfd);
return 0;
}
2)投票
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/time.h>
#include<sys/select.h>
#include<poll.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"liine %d",__LINE__);\
perror(msg);\
}while(0)
#define IP "192.168.0.211" //本机IP
#define PORT 6666 // 1024-49151
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind sucess __%d__\n",__LINE__);
//将套接字设置为被动监听状态,监听是否有客户端连接成功
if(listen(sfd,128)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success __%d__\n",__LINE__);
//设置一个读集合
fd_set readfds,tmpfds;
//由于readfds中需要防止要检测的文件描述符,所以不能让他是随机值
//所以需要将readfds清空
FD_ZERO(&readfds);
FD_ZERO(&tmpfds);
//将需要的文件描述符添加到集合中
FD_SET(0,&readfds);
FD_SET(sfd,&readfds);
int s_res=0;
char buf[128]="";
struct sockaddr_in cin; // 存储连接成功的客户端地址信息
socklen_t addrlen = sizeof(cin);
int newfd =-1;
ssize_t res=-1;
int maxfd=sfd;
while(1)
{
tmpfds=readfds;
s_res=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
if(s_res<0)
{
ERR_MSG("select");
return -1;
}
if(0==res)
{
printf("time out\n");
return -1;
}
//能运行到当前位置,则代表有文件描述符准备就绪
//走触发事件的文件描述符对应的处理函数
for(int i=0;i<=maxfd;i++)
{
if(!FD_ISSET(i,&tmpfds))//如果不在集合中,则直接往后继续变量
{
continue;
}
//能运行到当前位置,则说明i代表的文件描述符在tmpfds中
//要判断i所代表的文件描述符需要走什么对应的函数
if(0==i)
{
printf("触发键盘输入事件>>");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
printf("%s\n",buf);
}
else if(sfd==1)
{
printf("触发客户端连接事件>>");
fflush(stdout);
int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if(newfd<0)
{
ERR_MSG("accept");
return -1;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//将newfd添加到读集合中
FD_SET(newfd,&readfds);
//更新maxfd
maxfd=maxfd>newfd?maxfd:newfd;
}
else
{
bzero(buf,sizeof(buf));
//接收
res=recv(i,buf,sizeof(buf),0);
if(res<0)
{
ERR_MSG("recv");
return -1;
}
else if(0==res)
{
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
//关闭文件描述符
close(i);
//从集合中剔除该文件描述符
FD_CLR(i,&readfds);
//更新maxfd
//从目前最大的文件描述符中往小的判断
int j=0;
for(int j=maxfd;j>=0;j--)
{
if(FD_ISSET(j,&readfds))
{
maxfd=j;
break;
}
}
if(j<0)
maxfd=-1;
continue;
}
printf("[%s:%d] newfd=%d 连接成功 __%d__\n",\
inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd,__LINE__);
strcat(buf,"*_*");
if(send(i,buf,sizeof(buf),0)<0)
{
ERR_MSG("send");
return -1;
}
printf("send sucess\n");
}
}
}
return 0;
}
3. 関数ライブラリ
【1】図書館の概念
1> ライブラリはバイナリ実行可能ファイルです。バイナリ実行可能プログラムと比較して、ライブラリは単独では実行できません。
a. ライブラリに保存されるのはすべての関数関数ですが、保存できるのは関数関数のみです
b. main 関数をライブラリに保存することはできません。
c. ライブラリは、取得できる関数コードが記述されたものです。
2>使用するにはライブラリをメモリにロードする必要があります
3>各オペレーティング システムには独自のライブラリがあり、互換性がありません
図書館の分類:
静的ライブラリ、動的ライブラリ
【2】静的ライブラリ
1) 静的ライブラリの原則: 静的ライブラリによってカプセル化された関数は、プログラムがリンク ライブラリステップにコンパイルされるときに、実行可能プログラムに継承されます。
利点: 1>プログラムの実行中は静的ライブラリとは関係がないため、移植が容易です。
2> 静的ライブラリの動作効率が速くなります
デメリット: 1>プログラムの更新と導入が面倒
2> サイズが大きいため、保存時にディスク領域が無駄になり、ロードおよび実行時にメモリ領域が無駄になります。
2) 静的ライブラリ作成命令
ステップ:
1>ファイルに分割し、関数関数とメイン関数を分離します func.c main.c
2>関数関数のヘッダー ファイルを作成します: func.h
3> main.c と func.c を一緒にコンパイルし、ファイルの分割が成功したかどうかをテストします。
4>上記の手順を使用して、func.c を静的ライブラリにカプセル化します。
5>注: libxxx.a、xxx はライブラリの名前です。
3) 静的ライブラリの使用
【3】ダイナミックライブラリ(共有ライブラリ)
1) ダイナミック ライブラリの原理: ダイナミック ライブラリは、プログラムが実行されるまでライブラリ関数のリンク ステップを延期します。プログラムがライブラリ関数を実行すると、ダイナミック ライブラリ関数が見つかります。
1>ダイナミック ライブラリ関数がメモリに存在しない場合、ダイナミック ライブラリ関数は実行のためにメモリにロードされます。
2>ダイナミック ライブラリ関数がメモリ内に存在する場合、その関数は直接呼び出され、2 番目のコピーはロードされません。
3>したがって、メモリ内にはダイナミック ライブラリ関数のコピーが 1 つだけ存在します。
アドバンテージ:
1> 小さく、保存時にディスクスペースを節約し、実行時にメモリスペースを節約します
2>プログラムの更新と展開がより便利になり、実行可能プログラムを再コンパイルする必要がなくなります。
欠点:
1>プログラムの実行中にダイナミック ライブラリが見つからないとプログラムがクラッシュするため、ダイナミック ライブラリを使用してバイナリ プログラムを生成すると移植性が低くなります。
2>稼働効率が低い
2) ダイナミックライブラリ作成命令
ステップ:
1>ファイルに分割し、関数関数とメイン関数を分離します func.c main.c
2>関数関数のヘッダー ファイルを作成します: func.h
3> main.c と func.c を一緒にコンパイルし、ファイルの分割が成功したかどうかをテストします。
4>上記の手順を使用して、func.c を静的ライブラリにカプセル化します。
5>注: libxxx.so、xxx はライブラリの名前です
3) 動的ライブラリの使用
4 ) 動的ライブラリの環境変数設定
1> ダイナミック ライブラリを /lib/ または /usr/lib ディレクトリに移動します。
a.sudo mv ./libmyfunc.so /usr/lib/
b. sudo mv libmyfunc.so/lib/
2>環境変数を設定します: LD_LIBRARY_PATH
3>環境変数Liu Angの設定ファイルを変更する
a.cd /ec/ld.so.conf.d/
b. sudo touch my.conf を実行して、.conf で終わるファイルを作成します
c. sudo vim my.conf
d. ダイナミック ライブラリが配置されているフォルダーの絶対パスを入力します。1 行に 1 つのダイナミック ライブラリの絶対パスのみを入力できることに注意してください。
e. 保存して終了した後、sudo ldconfig を実行して環境変数を更新します。