プロトコル ファミリには多数のプロトコルがありますが、この本では、ネットワーク プログラミングに最も直接的な影響を与える IP プロトコルと TCP プロトコルのみを取り上げます。
簡略化すると、同じ 7 つの層が 4 つの異なる層で通信し、各層の変更が容易になります。
アプリケーション層アプリケーションロジックの処理を担当します
プレゼンテーション層データの形式と暗号化を定義します
セッション層これは、複数の双方向メッセージの制御と管理を含む、セッションの開始、制御、終了方法を定義します。これにより、連続メッセージの一部だけが完了したときにアプリケーションに通知できるため、プレゼンテーション層で認識されるデータは連続的になります。
トランスポート層2 つのホスト上のアプリケーションにエンドツーエンドの通信を提供します。ネットワーク層で使用されるネクスト ホップとは異なり、転送プロセスは下位層に任せられます。この層: TCP プロトコルと UDP プロトコル TCP プロトコル (伝送制御プロトコル Transmission Control Protocol)
可靠的, 面向连接, 基于流的服务アプリケーション層に提供します超时重传と数据确认により、データが正常に配信されることを保証します。不可靠的, 无连接的, 基于数据报的服务をアプリケーション層に提供します数据确认と超时重传の問題は自分で対処する必要があります。有自己的长度アドレス情報を指定する必要があります。ネットワーク層データ パケットのルーティングと転送を実現します。データ パケットが宛先アドレスに到達できない場合は、次のホップ (ホップバイホップ)下一跳、最も近いIP プロトコル (インターネット プロトコル)とICMP プロトコル (インターネット コントロール メッセージ プロトコル)を選択します。後者のプロトコル IP プロトコルの補足であり、ネットワーク接続の検出に使用されます。 1. エラー メッセージ。ステータスに応答するために使用されます。 2. クエリ メッセージ (ping プログラムは、このメッセージを使用して情報が配信されたかどうかを判断します)。
データリンク層ネットワーク カード インターフェイスを実装するネットワーク ドライバーは、メーカーによる下位層の変更を容易にし、指定されたインターフェイスを上位層に提供するだけで済みます。ARP (アドレス解決プロトコル)があります。ネットワーク層はマシンのアドレス指定に IP アドレスを使用しますが、データリンク層は物理アドレス (通常は MAC アドレス) を使用するため、それらの間の変換には ARP プロトコルARP スプーフィングが含まれる可能性があります。現時点では勉強していません。
カプセル化上位層のプロトコルはカプセル化によって実装され、TCP によってカプセル化されたデータが下位層のプロトコルに送信されますTCP报文段
UDPでカプセル化されたデータはUDP数据报になる
IPでカプセル化された後、 IP数据报最後にデータリンク層でカプセル化されて帧
イーサネットの最大データ フレームは 1518 バイトで、フレームの最後に 14 個のヘッダーと 4 個のチェックサムを除きます。 MTU: フレームの最大送信単位は通常 1500 バイトです。 MSS: TCP パケットの最大データ ロードは 1460 バイトです。 = 1500 バイト - 20Ip ヘッダー。-20TCP ヘッダーには追加の 40 バイトのオプション部分があります。
ARP ARP プロトコルは、任意のネットワーク層アドレスから任意の物理アドレスへの変換を実現できます。
IP プロトコルは、TCP/IP プロトコル スイートのコア プロトコルであり、ソケット ネットワーク プログラミングの基礎の 1 つであり、上位層プロトコルにステートレス、コネクションレス、および信頼性の低いサービスを提供します。
IP データグラムの最大長は 65535 (2^16 - 1) バイトですが、MTU 制限があります
IP データグラムの長さが MTU を超えると、送信者または中継ルーターで断片化が発生するか、最終ターゲット マシンでのみ複数回断片化される可能性があります。カーネル内の ip モジュールによって再アセンブルされる
ルーティングメカニズム
ターゲット IP アドレスが指定された後、ルーティング テーブルのどの項目が照合されますか? 3 つのステップがあります。
TCP の読み取りと書き込みはすべてバッファーに対するものであるため、読み取り数と書き込み数の間には固定された対応関係はありません。
UDP にはバッファがありません。データは時間内に受信する必要があります。そうしないとパケットが失われます。受信バッファが小さすぎると、データグラムが切り捨てられます。
ISN - 初期シーケンス番号値 32 ビット シーケンス番号 後続の TCP メッセージ セグメントのシーケンス番号値 seq = ISN + バイト ストリーム全体におけるメッセージ セグメントの最初のバイトのオフセット 32 ビット確認番号受信した TCP メッセージ + 1 。この 32 ビットの確認番号は、最後の応答になるたびに送信されます。
ACK フラグ: 確認番号が有効かどうかを示します。ACK フラグを含むメッセージ セグメントは确认报文段と呼ばれます。 PSH フラグ: 後続のデータ用のスペースを確保するために、TCP 受信バッファからデータを読み取るように受信アプリケーションに指示します。要件 相手は接続を再確立し、复位报文段を保持します。 SYN フラグ: フラグは接続の確立を要求し、同步报文段を保持します。 FIN フラグ: ローカル接続が閉じられることを相手に通知します。 、そして...结束报文段を運びます
16 ビット ウィンドウ サイズ: ウィンドウは受信通知ウィンドウを指し、ローカル TCP 受信バッファーが保持できるデータのバイト数を相手に伝えます。これは、可靠传输的重要保障受信側はCRCアルゴリズム検証を実行し、破損していないかをチェックし、 TCP头部と数据部分同時にチェックします。
TCP 接続の確立と終了
# 三次握手
# 客户端发送请求连接 ISN= seq + 0 = 3683340920
# mss 最大数据载量1460
IP 192 . 168 . 80 . 1 . 7467 > ubuntu. 8000 :
Flags [S], seq 3683340920 , win 64240 ,
options [mss 1460 , nop ,wscale 8 , nop , nop ,sackOK], length 0
# 同意客户端连接
# ack = 客户端发送 seq + 1
# 同时发送服务端的seq
IP ubuntu. 8000 > 192 . 168 . 80 . 1 . 7467 :
Flags [S.], seq 938535101 , ack 3683340921 , win 64240 ,
options [mss 1460 , nop , nop ,sackOK, nop ,wscale 7 ], length 0
# 虽然这个报文段没有字节 但由于是同步报文段 需要占用一个序号值
# 这里是tcpdump的处理 ack显示相对值 即 3683340921 - 3683340920 = 1
IP 192 . 168 . 80 . 1 . 7467 > ubuntu. 8000 :
Flags [.], ack 938535102 , win 4106 , length 0
# 包含FIN标志 说明要求结束连接 也需要占用一个序号值
IP 192 . 168 . 80 . 1 . 7467 > ubuntu. 8000 :
Flags [F.], seq 1 , ack 1 , win 4106 , length 0
# 服务端确认关闭连接
IP ubuntu. 8000 > 192 . 168 . 80 . 1 . 7467 :
Flags [.], ack 2 , win 502 , length 0
# 服务端发送关闭连接
IP ubuntu. 8000 > 192 . 168 . 80 . 1 . 7467 :
Flags [F.], seq 1 , ack 2 , win 4105 , length 0
# 客户端确认
IP 192 . 168 . 80 . 1 . 7467 > ubuntu. 8000 :
Flags [.], ack 2 , win 503 , length 0 基本的なソケット API はsys/socket.hヘッダー ファイルにあります。ソケットの最初の意味は、TCP 通信を表す唯一のネットワーク情報ですnetdb.hヘッダー ファイルにあります。
バイト オーダーは大端字节序と小端字节序ほとんどの PC はリトル エンディアン バイト オーダー (上位ビットが上位アドレスに存在する) を使用するため、リトル エンディアン バイト オーダーはホスト バイト オーダーとも呼ばれます。
マシンごとのバイトオーダーの違いによる混乱を防ぐため、送信はビッグエンディアンバイトオーダー(ネットワークバイトオーダー)に統一することが定められており、ホスト側が自らの状況に応じて判断することになります。受信したデータのバイトオーダーを変換する
基本的な接続
// 主机序和网络字节序转换
# include < netinet/in.h >
unsigned long int htonl ( unsigned long int hostlong); // host to network long
unsigned short int htons ( unsigned short int hostlong); // host to network short
unsigned long int htonl ( unsigned long int netlong);
unsigned short int htons ( unsigned short int netlong);
// IP地址转换函数
# include < arpa/inet.h >
// 将点分十进制字符串的IPv4地址, 转换为网络字节序整数表示的IPv4地址. 失败返回INADDR_NONE
in_addr_t inet_addr ( const char * strptr);
// 功能相同不过转换结果存在 inp指向的结构体中. 成功返回1 反之返回0
int inet_aton ( const char * cp, struct in_addr * inp);
// 函数返回一个静态变量地址值, 所以多次调用会导致覆盖
char * inet_ntoa ( struct in_addr in);
// src为 点分十进制字符串的IPv4地址 或 十六进制字符串表示的IPv6地址 存入dst的内存中 af指定地址族
// 可以为 AF_INET AF_INET6 成功返回1 失败返回-1
int inet_pton ( int af, const char * src, void * dst);
// 协议名, 需要转换的ip, 存储地址, 长度(有两个常量 INET_ADDRSTRLEN, INET6_ADDRSTRLEN)
const char * inet_ntop ( int af, const void * src, char * dst, socklen_t cnt);
// 创建 命名 监听 socket
# include < sys/types.h >
# include < sys/socket.h >
// domain指定使用那个协议族 PF_INET PF_INET6
// type指定服务类型 SOCK_STREAM (TCP协议) SOCK_DGRAM(UDP协议)
// protocol设置为默认的0
// 成功返回socket文件描述符(linux一切皆文件), 失败返回-1
int socket ( int domain, int type, int protocol);
// socket为socket文件描述符
// my_addr 为地址信息
// addrlen为socket地址长度
// 成功返回0 失败返回 -1
int bind ( int socket, const struct sockaddr * my_addr, socklen_t addrlen);
// backlog表示队列最大的长度
int listen ( int socket, int backlog);
// 接受连接 失败返回-1 成功时返回socket
int accept ( int sockfd, struct sockaddr * addr, socklen_t * addrlen)クライアント
// 发起连接
#include <sys/types.h>
#include <sys/socket.h>
// 第三个参数为 地址指定的长度
// 成功返回0 失败返回-1
int connect ( int sockfd , const struct sockaddr * serv_addr , socklen_t addrlen );
// 关闭连接
#include <unistd.h>
// 参数为保存的socket
// 并非立即关闭, 将socket的引用计数-1, 当fd的引用计数为0, 才能关闭(需要查阅)
int close ( int fd );
// 立即关闭
#include <sys/socket.h>
// 第二个参数为可选值
// SHUT_RD 关闭读, socket的接收缓冲区的数据全部丢弃
// SHUT_WR 关闭写 socket的发送缓冲区全部在关闭前发送出去
// SHUT_RDWR 同时关闭读和写
// 成功返回0 失败为-1 设置errno
int shutdown ( int sockfd , int howto )基本的なTCP
#include <sys/socket.h>
#include <sys/types.h>
// 读取sockfd的数据
// buf 指定读缓冲区的位置
// len 指定读缓冲区的大小
// flags 参数较多
// 成功的时候返回读取到的长度, 可能小于预期长度, 需要多次读取. 读取到0 通信对方已经关闭连接, 错误返回-1
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 );| オプション名 | 意味 | 送信可能 | 受信可能 |
|---|---|---|---|
| MSG_CONFIRM | リンク層プロトコルに、応答を受信するまでリスニングを続けるように指示します (SOCK_DGRAM および SOCK_RAW タイプのソケットにのみ使用できます)。 | Y | N |
| MSG_DONTROUTE | ルーティング テーブルをチェックせずに、データはローカル LAN ホストに直接送信されます (つまり、送信者はターゲット ホストがローカル ネットワーク内にあることを認識しています)。 | Y | N |
| MSG_DONTWAIT | ノンブロッキング | Y | Y |
| MSG_MORE | 送信するデータがまだあることをカーネルに通知し、データがバッファに書き込まれるまで待ってから、短いメッセージを減らして送信効率を向上させます。 | Y | N |
| MSG_WAITALL | 読み取り操作は、指定されたバイトが読み取られるまで待機してから戻ります。 | N | Y |
| MSG_PEEK | 内部キャッシュデータを確認してください。データには影響しません。 | N | Y |
| MSG_OOB | 緊急データの送受信 | Y | Y |
| MSG_NOSIGNAL | 読み取りクローズされたパイプまたはソケット接続にデータを書き込んでも、SIGPIPE シグナルはトリガーされません。 | Y | N |
基本的な UDP
#include <sys/types.h>
#include <sys/socket.h>
// 由于UDP不保存状态, 每次发送数据都需要 加入目标地址.
// 不过recvfrom和sendto 也可以用于 面向STREAM的连接, 这样可以省略发送和接收端的socket地址
ssize_t recvfrom ( int sockfd , void * buf , size_t len , int flags , struct sockaddr * src_addr , socklen_t * addrlen );
ssize_t sendto ( int sockfd , const void * buf , size_t len , ing flags , const struct sockaddr * dest_addr , socklen_t addrlen );一般的な読み取りおよび書き込み機能
#inclued <sys/socket.h>
ssize_t recvmsg ( int sockfd , struct msghdr * msg , int flags );
ssize_t sendmsg ( int sockfd , struct msghdr * msg , int flags );
struct msghdr
{
/* socket address --- 指向socket地址结构变量, 对于TCP连接需要设置为NULL*/
void * msg_name ;
socklen_t msg_namelen ;
/* 分散的内存块 --- 对于 recvmsg来说数据被读取后将存放在这里的块内存中, 内存的位置和长度由
* msg_iov指向的数组指定, 称为分散读(scatter read) ---对于sendmsg而言, msg_iovlen块的分散内存中
* 的数据将一并发送称为集中写(gather write);
*/
struct iovec * msg_iov ;
int msg_iovlen ; /* 分散内存块的数量*/
void * msg_control ; /* 指向辅助数据的起始位置*/
socklen_t msg_controllen ; /* 辅助数据的大小*/
int msg_flags ; /* 复制函数的flags参数, 并在调用过程中更新*/
};
struct iovec
{
void * iov_base /* 内存起始地址*/
size_t iov_len /* 这块内存长度*/
}その他のAPI
#include <sys/socket.h>
// 用于判断 sockfd是否处于带外标记, 即下一个被读取到的数据是否是带外数据,
// 是的话返回1, 不是返回0
// 这样就可以选择带MSG_OOB标志的recv调用来接收带外数据.
int sockatmark ( int sockfd );
// getsockname 获取sockfd对应的本端socket地址, 存入address指定的内存中, 长度存入address_len中 成功返回0失败返回-1
// getpeername 获取远端的信息, 同上
int getsockname ( int sockfd , struct sockaddr * address , socklen_t * address_len );
int getpeername ( int sockfd , struct sockaddr * address , socklen_t * address_len );
/* 以下函数头文件均相同*/
// sockfd 目标socket, level执行操作协议(IPv4, IPv6, TCP) option_name 参数指定了选项的名字. 后面值和长度
// 成功时返回0 失败返回-1
int getsockopt ( int sockfd , int level , int option_name , void * option_value ,
socklen_t restrict option_len );
int setsockopt ( int sockfd , int level , int option_name , void * option_value ,
socklen_t restrict option_len );| SO_REUSEADDR | ローカルアドレスを再利用する | この属性を使用してソックスを設定すると、bind() 後にソックスが TIME_WAIT 状態になった場合でも、それにバインドされているソケット アドレスをすぐに再利用して新しいソックスをバインドできます。 |
|---|---|---|
| SO_RCVBUF | TCP受信バッファサイズ | 最小値は 256 バイトです。設定後、システムは設定した値を自動的に 2 倍にし、余分な 2 倍の値が輻輳に対処するための空きバッファーとして使用されます。 |
| SO_SNDBUF | TCP送信バッファサイズ | 最小値は 2048 バイトです |
| SO_RCVLOWAT | ローウォーターマークを受信しました | デフォルトは 1 バイトです。TCP 受信バッファ内の読み取り可能なデータの総数が下限値を超えると、IO 多重化システム コールは、対応するソケットからデータを読み取ることができることをアプリケーションに通知します。 |
| SO_SNDLOWAT | ハイウォーターマークが送信されました | デフォルトは 1 バイトです。TCP 送信バッファの空き容量が最低水準点より大きい場合、データを書き込むことができます。 |
| とてもリンガー |
struct linger
{
int l_onoff /* 开启非0, 关闭为0*/
int l_linger ; /* 滞留时间*/
/*
* 当onoff为0的时候此项不起作用, close调用默认行为关闭socket
* 当onoff不为0 且linger为0, close将立即返回, TCP将丢弃发送缓冲区的残留数据, 同时发送一个复位报文段
* 当onoff不为0 且linger大于0 . 当socket阻塞的时候close将会等待TCP模块发送完残留数据并得到确认后关
* 闭, 如果是处于非阻塞则立即关闭
*/
};ネットワーク情報API
#include <netdb.h>
// 通过主机名查找ip
struct hostent * gethostbyname ( const char * name );
// 通过ip获取主机完整信息
// type为IP地址类型 AF_INET和AF_INET6
struct hostent * gethostbyaddr ( const void * addr , size_t len , int type );
struct hostent
{
char * h_name ; /* Official name of host. */
char * * h_aliases ; /* Alias list. */
int h_addrtype ; /* Host address type. */
int h_length ; /* Length of address. */
char * * h_addr_list ; /* List of addresses from name server. */
}
int main ( int argc , char * argv [])
{
if ( argc != 2 )
{
printf ( "非法输入n" );
exit ( 0 );
}
char * name = argv [ 1 ];
struct hostent * hostptr {};
hostptr = gethostbyname ( name );
if ( hostptr == nullptr )
{
printf ( "输入存在错误 或无法获取n" );
exit ( 0 );
}
printf ( "Official name of hostptr: %sn" , hostptr -> h_name );
char * * pptr ;
char inet_addr [ INET_ADDRSTRLEN ];
printf ( "Alias list:n" );
for ( pptr = hostptr -> h_aliases ; * pptr != nullptr ; ++ pptr )
{
printf ( "t%sn" , * pptr );
}
switch ( hostptr -> h_addrtype )
{
case AF_INET :
{
printf ( "List of addresses from name server:n" );
for ( pptr = hostptr -> h_addr_list ; * pptr != nullptr ; ++ pptr )
{
printf ( "t%sn" ,
inet_ntop ( hostptr -> h_addrtype , * pptr , inet_addr , sizeof ( inet_addr )));
}
break ;
}
default :
{
printf ( "unknow address typen" );
exit ( 0 );
}
}
return 0 ;
}
/*
./run baidu.com
Official name of hostptr: baidu.com
Alias list:
List of addresses from name server:
39.156.69.79
220.181.38.148
*/次の 2 つの関数は、/etc/services ファイルを読み取ってサービス情報を取得します。以下の内容は Wikipedia からのものです。
Service ファイルは、最新のオペレーティング システムの etc ディレクトリにある設定ファイルで、ネットワーク サービス名に対応するポート番号とプロトコルを記録します。
#include <netdb.h>
// 根据名称获取某个服务的完整信息
struct servent getservbyname ( const char * name , const char * proto );
// 根据端口号获取服务信息
struct servent getservbyport ( int port , const char * proto );
struct servent
{
char * s_name ; /* 服务名称*/
char * * s_aliases ; /* 服务的别名列表*/
int s_port ; /* 端口号*/
char * s_proto ; /* 服务类型, 通常为TCP或UDP*/
} #include <netdb.h>
// 内部使用的gethostbyname 和 getserverbyname
// hostname 用于接收主机名, 也可以用来接收字符串表示的IP地址(点分十进制, 十六进制字符串)
// service 用于接收服务名, 字符串表示的十进制端口号
// hints参数 对getaddrinfo的输出进行更准确的控制, 可以设置为NULL, 允许反馈各种有用的结果
// result 指向一个链表, 用于存储getaddrinfo的反馈结果
int getaddrinfo ( const char * hostname , const char * service , const struct addrinfo * hints , struct addrinfo * * result )
struct addrinfo
{
int ai_flags ;
int ai_family ;
int ai_socktype ; /* 服务类型, SOCK_STREAM或者SOCK_DGRAM*/
int ai_protocol ;
socklen_t ai_addrlen ;
char * ai_canonname ; /* 主机的别名*/
struct sockaddr * ai_addr ; /* 指向socket地址*/
struct addrinfo * ai_next ; /* 指向下一个结构体*/
}
// 需要手动的释放堆内存
void freeaddrinfo ( struct addrinfo * res ); #include <netdb.h>
// host 存储返回的主机名
// serv存储返回的服务名
int getnameinfo ( const struct sockaddr * sockaddr , socklen_t addrlen , char * host , socklen_t hostlen , char * serv
socklen_t servlen , int flags );テスト使用
telnet ip port #来连接服务器的此端口
netstat -nt | grep port #来查看此端口的监听Linux が提供する高度な IO 機能は、特定の条件下では当然より強力になります。そうでない場合、特定の条件によってファイル記述子の使用頻度が制限されることになります。ファイル記述子は負ではない整数です。各プロセスのカーネルによって維持される、プロセスによって開かれたファイルのレコード テーブルを指すインデックス値です。 STDOUT_FILENO (値 1) - 値 1 のファイル記述子は、STDOUT_FILENO をオフにした後、dup を使用して使用可能な最小値 (現在は 1) を返します。このようにして、出力は、 が指すファイルにリダイレクトされます。 dup を呼び出すパラメータ。
パイプ関数この関数を使用して、プロセス間の通信を実装するためのパイプを作成できます。
// 函数定义
// 参数文件描述符数组 fd[0] 读出 fd[1]写入 单向管道
// 成功返回0, 并将一对打开的文件描述符填入其参数指向的数组
// 失败返回-1 errno
#include <unistd.h>
int pipe ( int fd [ 2 ]); // 双向管道
// 第一个参数为 协议PF_UNIX(书上是AF_UNIX)感觉这里指明协议使用PF更好一些
#include <sys/types.h>
#include <sys/socket.h>
int socketpair ( int domain , int type , int protocol , int fd [ 2 ]);以下の内容を学習し、プロセス間通信を理解したら、戻って例を追加します。
int main ()
{
int fds [ 2 ];
socketpair ( PF_UNIX , SOCK_STREAM , 0 , fds );
int pid = fork ();
if ( pid == 0 )
{
close ( fds [ 0 ]);
char a [] = "123" ;
send ( fds [ 1 ], a , strlen ( a ), 0 );
}
else if ( pid > 0 )
{
close ( fds [ 1 ]);
char b [ 20 ] {};
recv ( fds [ 0 ], b , 20 , 0 );
printf ( "%s" , b );
}
}dup 関数と dup2 関数既存のファイル記述子をコピーする
#include <unistd.h>
// 返回的文件描述符总是取系统当前可用的最小整数值
int dup ( int oldfd );
// 可以用newfd来制定新的文件描述符, 如果newfd已经被打开则先关闭
// 如果newfd==oldfd 则不关闭newfd直接返回
int dup2 ( int oldfd , int newfd ); dup 関数は新しいファイル記述子を作成します。新しいファイル記述子と元の file_descriptor は両方とも同じターゲットを指します。この例では、 STDOUT_FILENOがオフになっているため、最小の dup はSTDOUT_FILENOになります。出力はファイル内の this に送られます
int main ()
{
int filefd = open ( "/home/lsmg/1.txt" , O_WRONLY );
close ( STDOUT_FILENO );
dup ( filefd );
printf ( "123n" );
exit ( 0 );
}読み取り/書き込み
#include <sys/uio.h>
// count 为 vector的长度, 即为有多少块内存
// 成功时返回写入读取的长度 失败返回-1
ssize_t readv ( int fd , const struct iovec * vector , int count );
ssize_t writev ( int fd , const struct iovec * vector , int count );
struct iovec {
void * iov_base /* 内存起始地址*/
size_t iov_len /* 这块内存长度*/
}戻って使用例を追加します。この例では、hexdump を使用してファイル0000000 86a0 0001を表示します。186a0 が 100000 である186a0がわかります。
// 2020年1月7日16:52:11
int main ()
{
int file = open ( "/home/lsmg/1.txt" , O_WRONLY );
int temp = 100000 ;
iovec temp_iovec {};
temp_iovec . iov_base = & temp ;
temp_iovec . iov_len = sizeof ( temp );
writev ( file , & temp_iovec , 1 );
}送信ファイル関数
#include <sys/sendfile.h>
// offset为指定输入流从哪里开始读, 如果为NULL 则从开头读取
ssize_t sendfile ( int out_fd , int in_fd , off_t * offset , size_t count );
O_RDONLY只读模式
O_WRONLY只写模式
O_RDWR读写模式
int open ( file_name , flag );stat 構造は fstat を使用して生成できます。これは単にファイルの ID カードです。
#include <sys/stat.h>
struct stat
{
dev_t st_dev ; /* ID of device containing file -文件所在设备的ID*/
ino_t st_ino ; /* inode number -inode节点号*/
mode_t st_mode ; /* protection -保护模式?*/
nlink_t st_nlink ; /* number of hard links -链向此文件的连接数(硬连接)*/
uid_t st_uid ; /* user ID of owner -user id*/
gid_t st_gid ; /* group ID of owner - group id*/
dev_t st_rdev ; /* device ID (if special file) -设备号,针对设备文件*/
off_t st_size ; /* total size, in bytes -文件大小,字节为单位*/
blksize_t st_blksize ; /* blocksize for filesystem I/O -系统块的大小*/
blkcnt_t st_blocks ; /* number of blocks allocated -文件所占块数*/
time_t st_atime ; /* time of last access -最近存取时间*/
time_t st_mtime ; /* time of last modification -最近修改时间*/
time_t st_ctime ; /* time of last status change - */
};IDカード生成機能
// 第一个参数需要调用open生成文件描述符
// 下面其他两个为文件全路径
int fstat ( int filedes , struct stat * buf );
// 当路径指向为符号链接的时候, lstat为符号链接的信息. stat为符号链接指向文件信息
int stat ( const char * path , struct stat * buf );
int lstat ( const char * path , struct stat * buf );
/*
* ln -s source dist 建立软连接, 类似快捷方式, 也叫符号链接
* ln source dist 建立硬链接, 同一个文件使用多个不同的别名, 指向同一个文件数据块, 只要硬链接不被完全
* 删除就可以正常访问
* 文件数据块 - 文件的真正数据是一个文件数据块, 打开的`文件`指向这个数据块, 就是说
* `文件`本身就类似快捷方式, 指向文件存在的区域.
*/mmap および munmap 関数
mmapプロセス通信によって共有されるメモリを作成し (そこにファイルをマッピングできます)、 munmapこのメモリを解放します。
#include <sys/mman.h>
// start 内存起始位置, 如果为NULL则系统分配一个地址 length为长度
// port参数 PROT_READ(可读) PROT_WRITE(可写) PROT_EXEC(可执行), PROT_NONE(不可访问)
// flag参数 内存被修改后的行为
// - MAP_SHARED 进程间共享内存, 对内存的修改反映到映射文件中
// - MAP_PRIVATE 为调用进程私有, 对该内存段的修改不会反映到文件中
// - MAP_ANONUMOUS 不是从文件映射而来, 内容被初始化为0, 最后两个参数被忽略
// 成功返回区域指针, 失败返回 -1
void * mmap ( void * start , size_t length , int port , int flags , int fd , off_t offset );
// 成功返回0 失败返回-1
int munmap ( void * start , size_t length );スプライス機能2 つのファイル名記述子間でデータを移動するために使用されます。0 コピー操作
#include <fcntl.h>
// fd_in 为文件描述符, 如果为管道文件描述符则 off_in必须为NULL, 否则为读取开始偏移位置
// len为指定移动的数据长度, flags参数控制数据如何移动.
// - SPLICE_F_NONBLOCK 非阻塞splice操作, 但会受文件描述符自身的阻塞
// - SPLICE_F_MORE 给内核一个提示, 后续的splice调用将读取更多的数据???????
ssize_t splice ( int fd_in , loff_t * off_in , int fd_out , loff_t * off_out , size_t len , unsigned int flags );
// 使用splice函数 实现echo服务器
int main ( int argc , char * argv [])
{
if ( argc <= 2 )
{
printf ( "the parmerters is wrongn" );
exit ( errno );
}
char * ip = argv [ 1 ];
int port = atoi ( argv [ 2 ]);
printf ( "the port is %d the ip is %sn" , port , ip );
int sockfd = socket ( PF_INET , SOCK_STREAM , 0 );
assert ( sockfd >= 0 );
struct sockaddr_in address {};
address . sin_family = AF_INET ;
address . sin_port = htons ( port );
inet_pton ( AF_INET , ip , & address . sin_addr );
int ret = bind ( sockfd , ( sockaddr * ) & address , sizeof ( address ));
assert ( ret != -1 );
ret = listen ( sockfd , 5 );
int clientfd {};
sockaddr_in client_address {};
socklen_t client_addrlen = sizeof ( client_address );
clientfd = accept ( sockfd , ( sockaddr * ) & client_address , & client_addrlen );
if ( clientfd < 0 )
{
printf ( "accept errorn" );
}
else
{
printf ( "a new connection from %s:%d successn" , inet_ntoa ( client_address . sin_addr ), ntohs ( client_address . sin_port ));
int fds [ 2 ];
pipe ( fds );
ret = splice ( clientfd , nullptr , fds [ 1 ], nullptr , 32768 , SPLICE_F_MORE );
assert ( ret != -1 );
ret = splice ( fds [ 0 ], nullptr , clientfd , nullptr , 32768 , SPLICE_F_MORE );
assert ( ret != -1 );
close ( clientfd );
}
close ( sockfd );
exit ( 0 );
}select 関数select 関数は、2 番目のパラメーター リストが読み取り可能になったときに戻るか、指定された時間が戻るまで待機します。
返された後、2 番目のパラメータ fdset が指すコレクションは読み取り可能な fd リストに変更されます。これには、返されるたびに fdset コレクションを更新する必要があります。
戻った後、この関数の戻り値は読み取り可能な fd の数です。この関数は fdset コレクションを走査し、FD_ISSET を使用して fdset[i] がその中にあるかどうかを判断し、fd が listenfd であるかどうかを判断します。そうでない場合は、他の人によって受け入れられたことを意味し、読み取るデータがあるか、接続が切断されているかを判断します。
#include <fcntl.h>
// maxfdp 最大数 FD_SETSIZE
// struct fd_set 一个集合,可以存储多个文件描述符
// - FD_ZERO(&fd_set) 清空 -FD_SET(fd, &fd_set) 放入fd FD_CLR(fd, &fd_set)从其中清除fd
// - FD_ISSET(fd, &fd_set) 判断是否在其中
// readfds 需要监视的文件描述符读变化, 其中的文件描述符可读的时候返回
// writefds 需要监视的文件描述符写变化, 其中的文件描述符可写的时候返回
// errorfds 错误
// timeout 传入NULL为阻塞, 设置为0秒0微秒则变为非阻塞函数
// 返回值 负值为错误 等待超时说明文件无变化返回0 有变化返回正值
int select ( int maxfdp , fd_set * readfds , fd_set * writefds , fd_set * errorfds , struct timeval * timeout );
#define exit_if ( r , ...)
{
if (r)
{
printf(__VA_ARGS__);
printf("errno no: %d, error msg is %s", errno, strerror(errno));
exit(1);
}
}
int main ( int argc , char * argv [])
{
int keyboard_fd = open ( "/dev/tty" , O_RDONLY | O_NONBLOCK );
exit_if ( keyboard_fd < 0 , "open keyboard fd errorn" );
fd_set readfd ;
char recv_buffer = 0 ;
while (true)
{
FD_ZERO ( & readfd );
FD_SET ( 0 , & readfd );
timeval timeout { 5 , 0 };
int ret = select ( keyboard_fd + 1 , & readfd , nullptr , nullptr , & timeout );
exit_if ( ret == -1 , "select errorn" );
if ( ret > 0 )
{
if ( FD_ISSET ( keyboard_fd , & readfd ))
{
recv_buffer = 0 ;
read ( keyboard_fd , & recv_buffer , 1 );
if ( 'n' == recv_buffer )
{
continue ;
}
if ( 'q' == recv_buffer )
{
break ;
}
printf ( "the input is %cn" , recv_buffer );
}
}
if ( ret == 0 )
{
printf ( "timeoutn" );
}
}
}sudo service rsyslog restart // 启动守护进程 #include <syslog.h>
// priority参数是所谓的设施值(记录日志信息来源, 默认为LOG_USER)与日志级别的按位或
// - 0 LOG_EMERG /* 系统不可用*/
// - 1 LOG_ALERT /* 报警需要立即采取行动*/
// - 2 LOG_CRIT /* 非常严重的情况*/
// - 3 LOG_ERR /* 错误*/
// - 4 LOG_WARNING /* 警告*/
// - 5 LOG_NOTICE /* 通知*/
// - 6 LOG_INFO /* 信息*/
// -7 LOG_DEBUG /* 调试*/
void syslog ( int priority , const char * message , .....);
// ident 位于日志的时间后 通常为名字
// logopt 对后续 syslog调用的行为进行配置
// - 0x01 LOG_PID /* 在日志信息中包含程序PID*/
// - 0x02 LOG_CONS /* 如果信息不能记录到日志文件, 则打印到终端*/
// - 0x04 LOG_ODELAY /* 延迟打开日志功能直到第一次调用syslog*/
// - 0x08 LOG_NDELAY /* 不延迟打开日志功能*/
// facility参数可以修改syslog函数中的默认设施值
void openlog ( const char * ident , int logopt , int facility );
// maskpri 一共八位 0000-0000
// 如果将最后一个0置为1 表示 记录0级别的日志
// 如果将最后两个0都置为1 表示记录0和1级别的日志
// 可以通过LOG_MASK() 宏设定 比如LOG_MASK(LOG_CRIT) 表示将倒数第三个0置为1, 表示只记录LOG_CRIT
// 如果直接设置setlogmask(3); 3的二进制最后两个数均为1 则记录 0和1级别的日志
int setlogmask ( int maskpri );
// 关闭日志功能
void closelog ();UID - 実際のユーザー ID EUID - 実効ユーザー ID - リソース アクセスを容易にする GID - 実グループ ID EGID - 実効グループ ID
#include <sys/types.h>
#include <unistd.h>
uid_t getuid ();
uid_t geteuid ();
gid_t getgid ();
gid_t getegid ();
int setuid ( uid_t uid );
int seteuid ( uid_t euid );
int setgid ( gid_t gid );
int setegid ( gid_t gid ); setuidとsetgidユーザーの uid と gid は両方とも 0 です。
PGID - プロセス グループ ID (Linux 上の各プロセスはプロセス グループに属します)
#include <unistd.h> pid_t getpgid(pid_t pid); 成功した場合は pid が属する pgid を返します。失敗した場合は -1 を返します。 int setpgid(pid_t pid, pid_t pgid);
セッション一部の関連プロセス グループはセッション スキップを形成します
ps以下のプロセス関係を確認する
リソース制限ディレクトリを少し変更しますわずかに
サーバーモデル-CSモデル
アドバンテージ
模式図
作成したデモではフォーク機能を使用していません。将来的には改善される予定です。
サーバーフレームワークのIOモデル
私は Javaweb を半年勉強したので、このモデルはおそらく理解できます。
ソケットは作成時にデフォルトでブロックされますが、非ブロック呼び出しはすぐに戻りますが、 SOCK_NONBLOCKが発生していない可能性があります (recv が情報を受信しなかった場合)。イベントが発生した場合、またはエラーが発生した場合は、返回-1 errnoイベントが発生しなかった場合、accept、send、recv errno はEAGAIN(再来一次)またはEWOULDBLOCK(期望阻塞)に設定されます。 EINPROGRESS(正在处理中)に設定されます
パフォーマンスを向上させるには、イベントがすでに発生しているときにノンブロッキング IO を呼び出す必要があります。
一般的に使用される IO 多重化機能select poll epoll_waitについては、第 9 章で説明します。信号については第 10 章で説明します。
2 つの効率的なイベント処理モードと同時実行モード
プログラムは、コンピューティング集約型 (CPU を大量に使用し、IO リソースをほとんど使用しない) と IO 集約型 (その逆) に分けられます。前者は同時プログラミングを使用すると効率が低下しますが、後者は両方のマルチプロセスを使用して効率が向上します。そしてマルチスレッドの方法。
同時実行モード - IO ユニットと複数の論理ユニットの間でタスクを調整する方法。サーバーには 2 つの主要な同時実行モードがあります。
準同期/準非同期モードIO モデルにおける非同期と同期の違いは、カーネルがアプリケーションにどのような種類の IO イベントを通知するか (準備完了イベントまたは完了イベント)、および誰が IO の読み書きを完了するか (アプリケーションまたはカーネル) です。
ここで (同時実行モード) の同期とは、完全にコード シーケンスの順序で実行することを指します。同期的に実行されるスレッドは同期スレッドと呼ばれます。非同期はシステム イベント (割り込み、シグナル) によって駆動される必要があります。非同期的に実行されるスレッドは、非同期スレッドと呼ばれます
サーバー (優れたリアルタイム パフォーマンスが必要で、同時に複数の顧客リクエストを処理できます) - 通常、同期スレッドと非同期スレッド (つまり、半同期/半非同期モード) を使用して実装されます。 同期スレッド - 顧客ロジックを処理し、オブジェクトを処理します。リクエストキューを非同期的にスレッド - 顧客リクエストを受信した後、IOイベントを処理し、それらをリクエストオブジェクトにカプセル化し、リクエストキューに挿入します。
セミシンク/セミアシンクパターンのバリエーションがあります半同步/半反应堆模式
非同期スレッド - メインスレッド - すべてのソケット上のイベントの監視を担当します
リーダー/フォロワーモデルわずかに
効率的なプログラミング方法 - 有限状態マシン
// 状态独立的有限状态机
STATE_MACHINE ( Package _pack ) {
PackageType _type = _pack . GetType ();
switch ( _type ) {
case type_A :
xxxx ;
break ;
case type_B :
xxxx ;
break ;
}
}
// 带状态转移的有限状态机
STATE_MACHINE () {
State cur_State = type_A ;
while ( cur_State != type_C ) {
Package _pack = getNewPackage ();
switch ( cur_State ) {
case type_A :
process_package_state_A ( _pack );
cur_State = type_B ;
break ;
case type_B :
xxxx ;
cur_State = type_C ;
break ;
}
}
}5,000 ワードのコードを一文字ずつコピーするのに 1 時間かかりました @2019 年 9 月 8 日 22:08:46@
プール- 時間とスペースを交換するプロセス プールとスレッド プール
データ レプリケーション- 高性能サーバーは不必要なレプリケーションを避けるように努めるべきです
コンテキストのスイッチとロック锁の範囲を減らします。あまり多くのワーカー プロセスを作成せず、専用のビジネス ロジック スレッドを使用してください。
I/O 多重化により、プログラムは複数のファイル記述子を同時に監視できます。
一般的に使用されるメソッドselect 、 poll 、 epoll
# include < sys/select.h >
// nfds - 被监听的文件描述符总数
// 后面三个分别指向 可读, 可写, 异常等事件对应的文件描述符集合
// timeval select超时时间 如果传递0 则为非阻塞, 设置为NULL则为阻塞
// 成功返回就绪(可读, 可写, 异常)文件描述符的总数, 没有则返回0 失败返回-1
int select ( int nfds, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval * timeout);
//操作fd_set的宏
FD_ZERO ( fd_set * fdset);
FD_SET ( int fd, fd_set * fdset);
FD_CLR ( int fd, fd_set * fdset);
FD_ISSET ( int fd, fd_set * fdset);
// 设置 timeval 超时时间
struct timeval
{
long tv_sec; // 秒
long tv_usec; // 微秒
}選択する
ファイル記述子の準備完了状態
世論調査
# include < poll.h >
// fds 结构体类型数组 指定我们感兴趣的文件描述符上发生的可读可写和异常事件
// nfds 遍历结合大小 左闭右开
// timeout 单位为毫秒 -1 为阻塞 0 为立即返回
int poll ( struct pollfd * fds, nfds_t nfds, int timeout);
struct pollfd
{
int fd;
short events; //注册的事件, 告知poll监听fd上的哪些事件
short revents; // 实际发生的事件
}# define exit_if (r, ...)
{
if (r)
{
printf (__VA_ARGS__);
printf ( " errno no: %d, error msg is %s " , errno, strerror (errno));
exit ( 1 );
}
}
struct client_info
{
char *ip_;
int port_;
};
int main ( int argc, char * argv[])
{
int port = 8001 ;
char ip[] = " 127.0.0.1 " ;
struct sockaddr_in address;
address. sin_port = htons (port);
address. sin_family = AF_INET;
address. sin_addr . s_addr = htons (INADDR_ANY);
int listenfd = socket (PF_INET, SOCK_STREAM, 0 );
exit_if (listenfd < 0 , " socket error n " );
int ret = bind (listenfd, ( struct sockaddr *)&address, sizeof (address));
exit_if (ret == - 1 , " bind error n " );
ret = listen (listenfd, 5 );
exit_if (ret == - 1 , " listen error n " );
constexpr int MAX_CLIENTS = 1024 ;
struct pollfd polls[MAX_CLIENTS] = {};
struct client_info clientsinfo[MAX_CLIENTS] = {};
polls[ 3 ]. fd = listenfd;
polls[ 3 ]. events = POLLIN | POLLRDHUP;
while ( true )
{
ret = poll (polls, MAX_CLIENTS + 1 , - 1 );
exit_if (ret == - 1 , " poll error n " );
for ( int i = 3 ; i <= MAX_CLIENTS; ++i)
{
int fd = polls[i]. fd ;
if (polls[i]. revents & POLLRDHUP)
{
polls[i]. events = 0 ;
printf ( " close fd-%d from %s:%d n " , fd, clientsinfo[fd]. ip_ , clientsinfo[fd]. port_ );
}
if (polls[i]. revents & POLLIN)
{
if (fd == listenfd)
{
struct sockaddr_in client_address;
socklen_t client_addresslen = sizeof (client_address);
int clientfd = accept (listenfd, ( struct sockaddr *)&client_address,
&client_addresslen);
struct client_info *clientinfo = &clientsinfo[clientfd];
clientinfo-> ip_ = inet_ntoa (client_address. sin_addr );
clientinfo-> port_ = ntohs (client_address. sin_port );
exit_if (clientfd < 0 , " accpet error, from %s:%d n " , clientinfo-> ip_ ,
clientinfo-> port_ );
printf ( " accept from %s:%d n " , clientinfo-> ip_ , clientinfo-> port_ );
polls[clientfd]. fd = clientfd;
polls[clientfd]. events = POLLIN | POLLRDHUP;
}
else
{
char buffer[ 1024 ];
memset (buffer, '