Il existe de nombreux protocoles dans la famille des protocoles. Ce livre sélectionne uniquement les protocoles IP et TCP, qui ont l'impact le plus direct sur la programmation réseau.
Les sept mêmes couches constituent le modèle de référence osi. Après simplification, quatre couches différentes communiquent entre elles via des interfaces, ce qui facilite la modification de chaque couche.
Couche d'application Responsable de la gestion de la logique applicative
Couche de présentation Définit le format et le cryptage des données
couche de session Il définit comment démarrer, contrôler et terminer une session, y compris le contrôle et la gestion de plusieurs messages bidirectionnels, afin que les applications puissent être averties lorsque seule une partie d'un message continu est terminée, afin que les données vues par la couche de présentation soient continues.
couche de transport Fournit une communication de bout en bout pour les applications sur deux hôtes. Différent du saut suivant utilisé par la couche réseau, il ne se soucie que du début et de la fin, et le processus de transfert est laissé à la couche inférieure. Il existe deux protocoles principaux. cette couche : Protocole TCP et protocole UDP Protocole TCP (Transmission Control Protocol Transmission Control Protocol)
可靠的, 面向连接, 基于流的服务
à la couche application超时重传
et数据确认
.不可靠的, 无连接的, 基于数据报的服务
à la couche application数据确认
et超时重传
.有自己的长度
couche réseau Il réalise le routage et le transfert des paquets de données. Si le paquet de données ne peut pas atteindre l'adresse de destination, il下一跳
saut suivant (saut par saut) et choisira le protocole IP (Internet Protocol) et le protocole ICMP (Internet Control Message Protocol) les plus proches. ) . Ce dernier protocole Il s'agit d'un complément au protocole IP, utilisé pour détecter les connexions réseau 1. Les messages d'erreur, utilisés pour répondre à l'état 2. Les messages de requête (le programme ping utilise ce message pour déterminer si l'information a été délivrée).
couche de liaison de données Un pilote réseau qui implémente l'interface de la carte réseau. Le pilote facilite ici les modifications de la couche inférieure du fabricant et n'a besoin que de fournir l'interface spécifiée à la couche supérieure : ARP (Address Resolve Protocol, Address Résolution Protocol) . sont également RARP ( Reverse ~, Reverse Address Résolution Protocol) Étant donné que la couche réseau utilise des adresses IP pour adresser les machines, mais que la couche liaison de données utilise des adresses physiques (généralement des adresses MAC), la conversion entre elles implique une usurpation d'identité du protocole ARP, qui peut être utilisée. être lié à cela. Je n’étudie pas pour le moment.
encapsulation Le protocole de couche supérieure est envoyé au protocole de couche inférieure. Il est implémenté par encapsulation. Lors de la transmission entre couches, ses propres informations d'en-tête sont ajoutées. Les données encapsulées par TCP deviennent TCP报文段
Les données encapsulées par UDP deviennent UDP数据报
Après avoir été encapsulé par IP, il devient IP数据报
Enfin, il est encapsulé par la couche liaison de données et devient帧
La trame de données maximale d'Ethernet est de 1 518 octets, supprimant 14 en-têtes et 4 sommes de contrôle à la fin de la trame. MTU : L'unité de transmission maximale de la trame est généralement de 1 500 octets. MSS : La charge de données maximale des paquets TCP est de 1 460 octets. = 1500 octets - en-tête 20Ip -20TCP a une partie facultative supplémentaire de 40 octets.
ARP Le protocole ARP peut réaliser la conversion de n'importe quelle adresse de couche réseau en n'importe quelle adresse physique.
Le protocole IP est le protocole principal de la suite de protocoles TCP/IP et l'un des fondements de la programmation réseau socket. Le protocole IP fournit des services sans état, sans connexion et peu fiables pour les protocoles de couche supérieure.
La longueur maximale du datagramme IP est de 65 535 (2 ^ 16 - 1) octets, mais il existe une limite MTU
Lorsque la longueur d'un datagramme IP dépasse la MTU, il sera fragmenté pour la transmission. La fragmentation peut se produire au niveau de l'expéditeur ou du routeur de transit, ou il peut être fragmenté plusieurs fois uniquement sur la machine cible finale. être réassemblé par le module ip dans le noyau
mécanisme de routage
Une fois l’adresse IP cible donnée, quel élément de la table de routage sera mis en correspondance. Il y a trois étapes.
La lecture et l'écriture TCP sont toutes destinées aux tampons, il n'y a donc pas de correspondance fixe entre le nombre de lectures et d'écritures.
UDP n'a pas de tampon. Les données doivent être reçues à temps, sinon les paquets seront perdus ou si le tampon de réception est trop petit, les datagrammes seront tronqués.
ISN - Valeur du numéro de séquence initial Numéro de séquence de 32 bits La valeur du numéro de séquence dans le segment de message TCP suivant seq = ISN + Le décalage du premier octet du segment de message dans l'ensemble du flux d'octets Numéro de confirmation de 32 bits La valeur du numéro de séquence de le message TCP reçu + 1 . Ce numéro de confirmation de 32 bits est envoyé à chaque fois qu'il s'agit de la dernière réponse.
Indicateur ACK : Indique si le numéro de confirmation est valide. Le segment de message portant l'indicateur ACK est appelé确认报文段
. Indicateur PSH : Invite l'application réceptrice à lire les données du tampon de réception TCP pour faire de la place aux données suivantes. Conditions requises L'autre partie rétablit la connexion et transporte... le flag复位报文段
SYN : le flag demande d'établir une connexion et transporte...同步报文段
Flag FIN : informe l'autre partie que la connexion locale doit être fermée , et porte...结束报文段
Taille de la fenêtre de 16 bits : la fenêtre fait référence à la fenêtre de notification de réception, qui indique à l'autre partie combien d'octets de données le tampon de réception TCP local peut contenir. Somme de contrôle de 16 bits :可靠传输的重要保障
et l'extrémité réceptrice effectue la vérification de l'algorithme CRC. Vérifiez s'il est endommagé et vérifiez TCP头部
et数据部分
en même temps.
Établissement et fermeture de la connexion 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
L'API socket de base se trouve dans le fichier d'en-tête sys/socket.h
. La signification initiale de socket est une paire d'adresse IP et de port. Les seules informations réseau qui représentent la communication TCP se trouvent dans netdb.h
.
L'ordre des octets est divisé en大端字节序
et小端字节序
Étant donné que la plupart des PC utilisent l'ordre des octets small-endian (les bits de poids fort existent aux adresses élevées), l'ordre des octets small-endian est également appelé ordre des octets de l'hôte.
Afin d'éviter toute confusion causée par l'ordre des octets différent des différentes machines, il est stipulé que la transmission doit être unifiée dans l'ordre des octets big-endian (ordre des octets du réseau). De cette manière, l'hôte décidera en fonction de sa propre situation - si). pour convertir l'ordre des octets des données reçues
connexion de base
// 主机序和网络字节序转换
# 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)
client
// 发起连接
#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 de base
#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 );
nom de l'option | signification | Disponible pour l'envoi | disponible pour recevoir |
---|---|---|---|
MSG_CONFIRM | Demande au protocole de couche liaison de continuer à écouter jusqu'à ce qu'une réponse soit reçue (ne peut être utilisé que pour les sockets de type SOCK_DGRAM et SOCK_RAW). | Oui | N |
MSG_DONTROUTE | Sans vérifier la table de routage, les données sont envoyées directement à l'hôte LAN local (ce qui signifie que l'expéditeur sait que l'hôte cible se trouve dans le réseau local) | Oui | N |
MSG_DONTWAIT | non bloquant | Oui | Oui |
MSG_MORE | Informez le noyau qu'il y a plus de données à envoyer et attendez que les données soient écrites dans le tampon avant de les envoyer toutes ensemble. Réduisez les messages courts et améliorez l'efficacité de la transmission. | Oui | N |
MSG_WAITALL | L'opération de lecture attend que l'octet spécifié soit lu avant de revenir. | N | Oui |
MSG_PEEK | Jetez un œil aux données du cache interne, cela n'affectera pas les données | N | Oui |
MSG_OOB | Envoyer ou recevoir des données d'urgence | Oui | Oui |
MSG_NOSIGNAL | L'écriture de données sur un tuyau ou une connexion socket fermée en lecture ne déclenchera pas le signal SIGPIPE. | Oui | N |
UDP de base
#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 );
Fonctions générales de lecture et d'écriture
#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 /* 这块内存长度*/
}
Autres 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 | Réutiliser l'adresse locale | Une fois qu'une chaussette est définie avec cet attribut, même si la chaussette est dans l'état TIME_WAIT après avoir été bind(), l'adresse de socket qui lui est liée peut toujours être immédiatement réutilisée pour lier une nouvelle chaussette. |
---|---|---|
SO_RCVBUF | Taille du tampon de réception TCP | La valeur minimale est de 256 octets. Après le réglage, le système doublera automatiquement la valeur que vous avez définie. Le double supplémentaire sera utilisé comme tampon libre pour gérer la congestion. |
SO_SNDBUF | Taille du tampon d'envoi TCP | La valeur minimale est de 2 048 octets |
SO_RCVLOWAT | Ligne d'eau basse reçue | La valeur par défaut est de 1 octet. Lorsque le nombre total de données lisibles dans le tampon de réception TCP est supérieur à sa limite inférieure, l'appel système de multiplexage IO informera l'application que les données peuvent être lues à partir du socket correspondant. |
SO_SNDLOWAT | ligne des hautes eaux envoyée | La valeur par défaut est 1 octet. Les données peuvent être écrites lorsque l'espace libre dans le tampon d'envoi TCP est supérieur à la limite inférieure. |
SO_LINGER |
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 d'informations sur le réseau
#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
*/
Les deux fonctions suivantes obtiennent des informations sur le service en lisant le fichier /etc/services. Le contenu suivant provient de Wikipédia.
Le fichier Service est un fichier de configuration dans le répertoire etc des systèmes d'exploitation modernes. Il enregistre le numéro de port et le protocole correspondant au nom du service réseau. Son objectif est le suivant.
#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 );
Test d'utilisation
telnet ip port #来连接服务器的此端口
netstat -nt | grep port #来查看此端口的监听
Les fonctions d'E/S avancées fournies par Linux sont naturellement plus puissantes dans des conditions spécifiques. Sinon, que feraient-elles d'autre ? Des conditions spécifiques limiteraient naturellement la fréquence d'utilisation des descripteurs de fichiers. Le descripteur de fichier est un entier non négatif. Est une valeur d'index qui pointe vers la table d'enregistrement des fichiers ouverts par le processus maintenu par le noyau pour chaque processus. STDOUT_FILENO (valeur 1) - Le descripteur de fichier avec la valeur 1 est la sortie standard. Après avoir désactivé STDOUT_FILENO, utilisez dup pour renvoyer la plus petite valeur disponible (actuellement, 1). De cette manière, la sortie est redirigée vers le fichier pointé par le. paramètre appelant dup.
fonction de tuyau Cette fonction peut être utilisée pour créer un canal pour implémenter la communication entre les processus.
// 函数定义
// 参数文件描述符数组 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 ]);
Après avoir étudié le contenu suivant et compris la communication inter-processus, je reviendrai et ajouterai un exemple.
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 );
}
}
fonctions dup et dup2 Copier un descripteur de fichier existant
#include <unistd.h>
// 返回的文件描述符总是取系统当前可用的最小整数值
int dup ( int oldfd );
// 可以用newfd来制定新的文件描述符, 如果newfd已经被打开则先关闭
// 如果newfd==oldfd 则不关闭newfd直接返回
int dup2 ( int oldfd , int newfd );
La fonction dup crée un nouveau descripteur de fichier. Le nouveau descripteur de fichier et le file_descriptor d'origine pointent tous deux vers la même cible. Revenez et ajoutez un exemple. Dans cet exemple, parce que STDOUT_FILENO
est désactivé, le plus petit dup est STDOUT_FILENO
, donc le standard. la sortie va à ceci dans le fichier
int main ()
{
int filefd = open ( "/home/lsmg/1.txt" , O_WRONLY );
close ( STDOUT_FILENO );
dup ( filefd );
printf ( "123n" );
exit ( 0 );
}
lecture/écriture
#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 /* 这块内存长度*/
}
Revenez et ajoutez un exemple d'utilisation. Cet exemple écrit la représentation mémoire d'un int dans un fichier. Utilisez hexdump pour afficher le fichier 0000000 86a0 0001
Vous pouvez voir 186a0
vaut 100000.
// 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 );
}
fonction d'envoi de fichier
#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 );
La structure stat peut être générée avec fstat, qui est simplement la carte d'identité du fichier.
#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 - */
};
Fonction de génération de carte d'identité
// 第一个参数需要调用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 建立硬链接, 同一个文件使用多个不同的别名, 指向同一个文件数据块, 只要硬链接不被完全
* 删除就可以正常访问
* 文件数据块 - 文件的真正数据是一个文件数据块, 打开的`文件`指向这个数据块, 就是说
* `文件`本身就类似快捷方式, 指向文件存在的区域.
*/
fonctions mmap et munmap
mmap
crée une mémoire partagée par la communication des processus (les fichiers peuvent y être mappés) munmap
libère cette mémoire.
#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 );
fonction d'épissure Utilisé pour déplacer des données entre deux descripteurs de nom de fichier, 0 opération de copie
#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 );
}
fonction select La fonction select est renvoyée lorsque la deuxième liste de paramètres est lisible ou attend le temps spécifié pour revenir.
Après le retour, la collection pointée par le deuxième paramètre fdset est modifiée en une liste fd lisible. Cela nécessite de mettre à jour la collection fdset après chaque retour.
Après le retour, la valeur de retour de cette fonction est le nombre de fds lisibles. Elle parcourt la collection fdset et utilise FD_ISSET pour déterminer si fdset[i] s'y trouve, puis détermine si le fd est à l'écoute. Si tel est le cas, acceptez la nouvelle connexion. Sinon, cela signifie qu'il a été accepté par d'autres. fd détermine s'il y a des données à lire ou si la connexion est déconnectée.
#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 utilisateur réel EUID - ID utilisateur effectif - facilite l'accès aux ressources GID - ID de groupe réel EGID - ID de groupe effectif
#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 );
Vous pouvez changer d'utilisateur via setuid
et setgid
L'uid et le gid de l'utilisateur root sont tous deux 0.
PGID - ID du groupe de processus (chaque processus sous Linux appartient à un groupe de processus)
#include <unistd.h> pid_t getpgid(pid_t pid); Renvoie le pgid auquel le pid appartient en cas de succès. Renvoie -1 en cas d'échec int setpgid(pid_t pid, pid_t pgid);
session Certains groupes de processus associés formeront un saut de session
Vérifiez la relation de processus ps et moins
Limites des ressources Changer légèrement de répertoire légèrement
Modèle de serveur-modèle CS
avantage
Diagramme de modèle
La démo écrite n'utilise pas la fonction fork. Elle sera améliorée à l'avenir.
Modèle d'E/S du framework de serveur
Je peux probablement comprendre ce modèle et j'ai étudié Javaweb pendant six mois.
Le socket est bloquant par défaut lors de sa création, mais il peut être résolu en passant SOCK_NONBLOCK
. Les appels non bloquants seront renvoyés immédiatement, mais l'événement peut ne pas s'être produit (recv n'a pas reçu les informations). se produit ou une erreur se produit,返回-1
il doit donc être distingué par errno
Ces erreurs ne se sont pas produites accept, send, recv errno est défini sur EAGAIN(再来一次)
ou EWOULDBLOCK(期望阻塞)
connect. est réglé sur EINPROGRESS(正在处理中)
Les E/S non bloquantes doivent être appelées lorsque l'événement s'est déjà produit pour améliorer les performances.
La fonction de multiplexage IO couramment utilisée select
poll
epoll_wait
sera expliquée plus loin dans le chapitre 9. Le signal sera expliqué dans le chapitre 10.
Deux modes de traitement d'événements efficaces et un mode simultané
Les programmes sont divisés en programmes à forte intensité de calcul (utilisant beaucoup de CPU et peu de ressources d'E/S) et à forte intensité d'E/S (inversement). Les premiers réduiront l'efficacité lors de l'utilisation de la programmation simultanée, tandis que les seconds amélioreront l'efficacité de la programmation simultanée. et manière multi-thread.
Mode de concurrence - une méthode de coordination des tâches entre les unités IO et plusieurs unités logiques. Le serveur dispose de deux modes de concurrence principaux.
Mode semi-synchrone/semi-asynchrone Dans le modèle IO, la différence entre asynchrone et synchrone réside dans le type d'événement IO que le noyau notifie à l'application (événement de préparation ou événement d'achèvement) et qui termine la lecture et l'écriture des IO (application ou noyau).
Et ici (mode simultanéité), la synchronisation fait référence à l'exécution complètement dans l'ordre de la séquence de code - les threads exécutés de manière synchrone sont appelés threads synchrones et doivent être pilotés par les événements système (interruptions, signaux) - les threads s'exécutant de manière asynchrone. sont appelés threads asynchrones
Serveur (nécessite de bonnes performances en temps réel et peut gérer plusieurs demandes client en même temps) - généralement implémenté à l'aide de threads synchrones et de threads asynchrones, c'est-à-dire en mode semi-synchrone/semi-asynchrone. Threads synchrones - traitent la logique client et traitent les objets dans la file d'attente des requêtes de manière asynchrone Thread - gère les événements IO, après avoir reçu les requêtes des clients, les encapsule dans des objets de requête et les insère dans la file d'attente des requêtes
Il existe des variantes du modèle Semi-Sync/Semi-Async半同步/半反应堆模式
Thread asynchrone - thread principal - responsable de la surveillance des événements sur toutes les sockets
modèle leader/suiveur légèrement
Méthode de programmation efficace - machine à états finis
// 状态独立的有限状态机
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 ;
}
}
}
Il m'a fallu une heure pour enfin copier le code de 5 000 mots lettre par lettre @8 septembre 2019 22:08:46@
Pools - Échangez de l'espace contre des pools de processus temporels et des pools de threads
Réplication des données : les serveurs hautes performances doivent essayer d'éviter les réplications inutiles
Changements de contexte et verrouillages Réduire la portée des锁
. Vous ne devez pas créer trop de processus de travail, mais utiliser des threads de logique métier dédiés.
Le multiplexage d'E/S permet aux programmes de surveiller plusieurs descripteurs de fichiers en même temps.
Méthodes couramment utilisées 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; // 微秒
}
sélectionner
état prêt du descripteur de fichier
sondage
# 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, '