1.ソケットの4つのIOモデル
1.ブロッキングタイプ
最も一般的に使用/最も簡単/低効率
関数自体にはブロッキング プロパティはありませんが、ファイル記述子自体が原因で関数がブロックされます。
デフォルトでは、Linux によって作成されたソケットはブロックされます
2.ノンブロッキング
IO 操作でブロックしないようにプロセスを設定できます。ポーリングが必要です
多くの CPU リソースを占有する
3. 多重化
複数の IO を同時に操作する
指定した時間内にデータが到着したかどうかを検出するように設定できます
4. シグナルドリブン IO
ワンステップコミュニケーション
データがソケットに到着したら、シグナルを送信してユーザーに通知します
2.ブロッキングIO
1.読み取りブロッキング
ソケット受信バッファに読み取るデータがない場合、read/recv/recvfrom などの呼び出しによってブロックが発生します。
データが到着すると、カーネルはプロセスを起動し、読み取りなどの関数を介してデータにアクセスします
プロセスのブロック中に事故が発生すると、プロセスは永久にブロックされます。
2.書き込み禁止
書き込みがブロックされる可能性は比較的低く、通常、書き込みバッファーが書き込み対象のデータを書き込めない場合に発生します。
データが書き込めない場合はブロッキング待ちに入る
送信バッファに十分なスペースがあると、カーネルは対応するプロセスを起動して書き込みます
ただし、UDP プロトコルでは送信バッファーがいっぱいになることはなく、書き込み操作の実行時に UDP ソケットがブロックされることはありません。
3. ノンブロッキング IO
すぐに完了できない IO 操作がある場合、システムはプロセスをスリープ状態にして待機させます。
ソケットをノンブロッキング モードに設定すると、システムはプロセスをスリープさせずに待機させず、エラーを直接返します。
プログラムがノンブロッキング モードでソケットを使用する場合、ループを使用して、読み取るデータがあるかどうか、ファイルの説明を継続的にチェックする必要があります。
アプリケーションのノンストップループ判定はCPUリソースを大量に消費するため、一般的には使用をお勧めしません
4. ノンブロッキングの実装方法
最初にソケット記述子を作成すると、システム カーネルはデフォルトでブロック IO になり、関数を使用してソケットを非ブロック状態に設定できます。
1.fcntl(ファイルディスクリプタ操作)
fcntl ( 文件描述词操作 )
头文件:
#include <unistd.h>
#include <fcntl.h>
定义函数 :
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock * lock);
参数分析:
fd --> 需要设置的文件描述符
cmd --> 设置的功能
F_DUPFD 用来查找大于或等于参数 arg 的最小且仍未使用的文件描述词, 并且复制参数 fd 的
F_GETFD 取得 close-on-exec 旗标. 若此旗标的 FD_CLOEXEC 位为 0, 代表在调用 exec(
F_SETFD 设置 close-on-exec 旗标. 该旗标以参数 arg 的 FD_CLOEXEC 位决定.
F_GETFL 取得文件描述词状态旗标, 此旗标为 open()的参数 flags.
F_SETFL 设置文件描述词状态旗标, 参数 arg 为新旗标, 但只允许 O_APPEND、O_NONBLOCK
F_GETLK 取得文件锁定的状态.
F_SETLK 设置文件锁定的状态. 此时 flcok 结构的 l_type 值必须是 F_RDLCK、F_WRLCK 或
F_UNLCK. 如果无法建立锁定, 则返回-1, 错误代码为 EACCES 或 EAGAIN.
F_SETLKW 同 F_SETLK 作用相同, 但是无法建立锁定时, 此调用会一直等到锁定动作成功为止
戻り値: 成功すると 0 を返し、失敗すると -1 を返します
2. 構造の定義
struct flcok
{
short int l_type; //锁定的状态
short int l_whence; //决定 l_start 位置
off_t l_start; //锁定区域的开头位置
off_t l_len; //锁定区域的大小
pid_t l_pid; //锁定动作的进程
};
l_type 有三种状态:
F_RDLCK 建立一个供读取用的锁定
F_WRLCK 建立一个供写入用的锁定
F_UNLCK 删除之前建立的锁定
l_whence 也有三种方式:
SEEK_SET 以文件开头为锁定的起始位置.
SEEK_CUR 以目前文件读写位置为锁定的起始位置
SEEK_END 以文件结尾为锁定的起始位置.
3. 操作例
int socket_fd = socket(...........); // 创建一个套接字描述符
int state = fcntl(socket_fd , F_GETFL , 0) ; // 获得当前描述符的旗标
state |= O_NONBLOCK ; // 在原基础上增加非阻塞属性
fcntl(scoket_fd , F_SETFL , state ); // 把配置的好的旗标重新设置回描述符中
5.サーバーをノンブロッキング形式で書く
ノンブロッキング IO を使用して、すべてのクライアント (最大 20 クライアント) からの接続を受信できるサーバーを実装し、メッセージの受信後にメッセージの内容を出力します。
ノンブロッキング コード
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <fcntl.h>
// 设置一个结构体
typedef struct clinet
{
int fd[20] ; // 用来保存每一个在线的用户的描述符
int num ; // 记录当前在线人数
}Clinet ;
int add_cli(Clinet * P_cli ,int connect_fd)
{
if ( P_cli->num >= 20 )
{
return -1 ;
}
// 把连接的套接字先设置为非阻塞状态
int state = fcntl(connect_fd , F_GETFL); // 获取 描述符connect_fd 的属性
state |= O_NONBLOCK ; // 设置属性信息, 添加上非阻塞状态
fcntl(connect_fd , F_SETFL ,state ); // 把配置好的属性,设置回到 connect_fd 描述符中
(P_cli->fd)[P_cli->num] = connect_fd ; // 把新连接上来的客户端的描述符保存下来
P_cli->num ++ ; // 数组下标自加
return 0 ;
}
int main(int argc, char const *argv[])
{
// 创建一个套接字 (购买一台手机)
int sock_fd = socket( AF_INET , SOCK_STREAM , 0 ); // 使用IPV4协议簇, 流式套接字(TCP)
if (-1 == sock_fd)
{
perror("socket rror");
return -1 ;
}
// 配置自己的IP和端口号等信息
// struct sockaddr_in // IPV4地址结构体
// {
// u_short sin_family;// 地址族
// u_short sin_port;// 端口
// struct in_addr sin_addr;// IPV4 地址
// char sin_zero[8];
// };
int addrlen = sizeof(struct sockaddr_in);
struct sockaddr_in my_addr = {
.sin_addr.s_addr = htonl(INADDR_ANY) , // 设置服务器的地址, INADDR_ANY 指本机中任何一个地址
.sin_family = AF_INET , // 使用 IPV4 协议簇
.sin_port = htons(65000) // 设置服务器的端口号为 65000
};
// 绑定地址信息 (IP + 端口号 + 协议簇)
int ret_val = bind( sock_fd, (struct sockaddr *)&my_addr, addrlen);
if (ret_val == -1 )
{
perror("bind error");
return -1 ;
}else{
printf(" bind succeed !!\n") ;
}
// 设置监听 (打开铃声)
if(listen( sock_fd, 5 )) // 设置sock_fd 为监听套接字, 同时可以接收连接请求数为 5 + 4(默认值)
{
perror("listen error");
return -1 ;
}
// 设置监听套接字为非阻塞属性
int state ;
state = fcntl(sock_fd , F_GETFL); // 获取 描述符sock_fd 的属性
state |= O_NONBLOCK ; // 设置属性信息, 添加上非阻塞状态
fcntl(sock_fd , F_SETFL ,state ); // 把配置好的属性,设置回到 sock_fd 描述符中
int connect_fd = -1 ;
Clinet * P_cli = calloc(1, sizeof(Clinet));
char * msg = calloc(1,128);
while(1)
{
// 等待连接
struct sockaddr_in from_addr; // 由于前面已经 把 监听套接字sock_fd 设置为非阻塞状态
connect_fd = accept( sock_fd , (struct sockaddr *)&from_addr, &addrlen);
//由于是非阻塞状态,所以一开始为-1是正常的,-1后要重新来
if ( connect_fd > 0) // 如果描述符大于0 则表示有客户端连接
{
printf("新客户端:%d 连接成功!!\n" , connect_fd);
add_cli(P_cli , connect_fd);
}
// 轮询是否有数据到达
for (int i = 0; i < P_cli->num ; i++)
{
// printf("LINE:%d\n" , __LINE__);
bzero(msg , 128) ;
// recv 的返回值与 read 返回值一致, 成功则返回实际读取的字节数
ret_val = recv((P_cli->fd)[i] , msg , 128 , 0 );
if (ret_val > 0)
{
printf("from %d , msg : %s" , P_cli->fd[i] , msg );
}
}
}
return 0 ;
}