프로토콜 제품군에는 많은 프로토콜이 있습니다. 이 책에서는 네트워크 프로그래밍에 가장 직접적인 영향을 미치는 IP 및 TCP 프로토콜만 선택합니다.
동일한 7개의 레이어가 osi 참조 모델입니다. 단순화 후 4개의 서로 다른 레이어가 인터페이스를 통해 서로 통신하므로 각 레이어의 수정이 용이해집니다.
애플리케이션 레이어 애플리케이션 로직 처리를 담당합니다.
프리젠테이션 레이어 데이터의 형식과 암호화를 정의합니다.
세션 레이어 여러 양방향 메시지의 제어 및 관리를 포함하여 세션을 시작, 제어 및 종료하는 방법을 정의하므로 연속 메시지의 일부만 완료되면 애플리케이션에 알릴 수 있으므로 프레젠테이션 계층에서 표시되는 데이터는 연속적입니다.
전송 계층 두 호스트의 애플리케이션에 대한 엔드투엔드 통신을 제공합니다. 네트워크 계층에서 사용하는 다음 홉과 달리 시작과 끝만 고려하고 전송 프로세스는 두 가지 주요 프로토콜에 남아 있습니다. 이 계층: TCP 프로토콜 및 UDP 프로토콜 TCP 프로토콜(Transmission Control Protocol Transmission Control Protocol)
可靠的, 面向连接, 基于流的服务
제공超时重传
및数据确认
통해 데이터의 정상적인 전달을 보장합니다.不可靠的, 无连接的, 基于数据报的服务
제공합니다.数据确认
및超时重传
문제는 사용자가 직접 처리해야 합니다.有自己的长度
네트워크 계층 데이터 패킷의 라우팅 및 전달을 실현합니다. 데이터 패킷이 대상 주소에 도달할 수 없으면 다음 홉(홉 단위)下一跳
하고 가장 가까운 IP 프로토콜(인터넷 프로토콜) 및 ICMP 프로토콜(인터넷 제어 메시지 프로토콜)을 선택합니다. ) 후자의 프로토콜 IP 프로토콜의 보완으로 네트워크 연결을 감지하는 데 사용됩니다. 1. 오류 메시지, 상태에 응답하는 데 사용됩니다. 2. 쿼리 메시지(ping 프로그램은 이 메시지를 사용하여 정보가 전달되었는지 확인합니다.)
데이터링크 계층 네트워크 카드 인터페이스를 구현하는 네트워크 드라이버입니다. 여기서 드라이버는 제조업체의 하위 계층 수정을 용이하게 하며 상위 계층에 지정된 인터페이스만 제공하면 됩니다. ARP(Address Resolve Protocol)가 있습니다. RARP(Reverse ~, Reverse Address Resolution Protocol) 도 있습니다. 네트워크 계층은 컴퓨터의 주소를 지정하기 위해 IP 주소를 사용하지만 데이터 링크 계층은 물리적 주소(일반적으로 MAC 주소)를 사용하므로 이들 간의 변환에는 ARP 프로토콜 ARP 스푸핑이 포함됩니다. 지금은 공부하고 있지 않습니다.
캡슐화 상위계층 프로토콜은 하위계층 프로토콜로 전달되며, 계층간 전송 시 자신의 헤더 정보가 추가되어 TCP报文段
된다.
UDP로 캡슐화된 데이터는 UDP数据报
됩니다.
IP로 캡슐화되면 IP数据报
마지막으로 데이터 링크 계층으로 캡슐화되어帧
이더넷의 최대 데이터 프레임은 1518바이트이며 프레임 끝에서 14개의 헤더와 4개의 체크섬을 버립니다. MTU: 프레임의 최대 전송 단위는 일반적으로 1500바이트입니다. MSS: TCP 패킷의 최대 데이터 로드는 1460바이트입니다. = 1500바이트 - 20Ip 헤더 -20TCP 헤더에는 추가로 40바이트의 선택적 부분이 있습니다.
ARP ARP 프로토콜은 모든 네트워크 계층 주소를 물리적 주소로 변환할 수 있습니다.
IP 프로토콜은 TCP/IP 프로토콜 제품군의 핵심 프로토콜이자 소켓 네트워크 프로그래밍의 기초 중 하나입니다. IP 프로토콜은 상위 계층 프로토콜에 대해 무상태, 무연결 및 신뢰할 수 없는 서비스를 제공합니다.
IP 데이터그램의 최대 길이는 65535(2^16 - 1)바이트이지만 MTU 제한이 있습니다.
IP 데이터그램의 길이가 MTU를 초과하면 전송을 위해 조각화됩니다. 조각화는 발신자 또는 전송 라우터에서 발생하거나 최종 대상 시스템에서만 여러 번 조각화될 수 있습니다. 커널의 ip 모듈에 의해 재조립됩니다.
라우팅 메커니즘
대상 IP 주소가 제공된 후 라우팅 테이블의 어떤 항목이 일치합니까? 세 단계가 있습니다.
Tcp 읽기 및 쓰기는 모두 버퍼용이므로 읽기 및 쓰기 횟수 사이에 고정된 대응 관계는 없습니다.
UDP에는 버퍼가 없습니다. 데이터는 제 시간에 수신되어야 합니다. 그렇지 않으면 패킷이 손실되거나 수신 버퍼가 너무 작으면 데이터그램이 잘립니다.
ISN - 초기 시퀀스 번호 값 32비트 시퀀스 번호 후속 TCP 메시지 세그먼트의 시퀀스 번호 값 seq = ISN + 전체 바이트 스트림에서 메시지 세그먼트의 첫 번째 바이트의 오프셋 32비트 확인 번호의 시퀀스 번호 값 수신된 TCP 메시지 + 1 이 32비트 확인 번호는 마지막 응답일 때마다 전송됩니다.
ACK 플래그: 확인 번호가 유효한지 여부를 나타냅니다. ACK 플래그를 전달하는 메시지 세그먼트를确认报文段
라고 합니다. PSH 플래그: 후속 데이터를 위한 공간을 확보하기 위해 수신 애플리케이션에 메시지를 표시합니다. 요구 사항 상대방은 연결을 다시 설정하고...复位报文段
전달합니다. SYN 플래그: 연결 설정을 요청하는 플래그 및 전달...同步报文段
FIN 플래그: 로컬 연결이 닫힐 것임을 상대방에게 알립니다. , 그리고...结束报文段
전달합니다.
16비트 창 크기: 창은 상대방에게 로컬 TCP 수신 버퍼가 보유할 수 있는 데이터 바이트 수를 알려주는 수신 알림 창을 나타냅니다. 16비트 체크섬: 송신 측이 채우는可靠传输的重要保障
수신측에서는 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
헤더 파일에 있습니다. 소켓의 초기 의미는 IP 주소와 포트 쌍입니다. 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 유형 소켓에만 사용할 수 있습니다.) | 와이 | N |
MSG_DONTROUTE | 라우팅 테이블을 확인하지 않고 데이터를 로컬 LAN 호스트로 직접 전송합니다. (즉, 대상 호스트가 로컬 네트워크에 있다는 것을 보낸 사람이 알고 있음을 의미합니다.) | 와이 | N |
MSG_DONTWAIT | 비차단 | 와이 | 와이 |
MSG_MORE | 전송할 데이터가 더 있다는 것을 커널에 알리고, 데이터를 모두 함께 보내기 전에 데이터가 버퍼에 기록될 때까지 기다립니다. | 와이 | N |
MSG_WAITALL | 읽기 작업은 반환되기 전에 지정된 바이트를 읽을 때까지 기다립니다. | N | 와이 |
MSG_PEEK | 내부 캐시 데이터를 살펴보세요. 데이터에는 영향을 미치지 않습니다. | N | 와이 |
MSG_OOB | 긴급 데이터 보내기 또는 받기 | 와이 | 와이 |
MSG_NOSIGNAL | 읽기 폐쇄 파이프 또는 소켓 연결에 데이터를 쓰면 SIGPIPE 신호가 트리거되지 않습니다. | 와이 | 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 | 로컬 주소 재사용 | 이 속성으로 양말을 설정한 후에는 양말이 바인딩()된 후 TIME_WAIT 상태에 있더라도 바인딩된 소켓 주소를 즉시 재사용하여 새 양말을 바인딩할 수 있습니다. |
---|---|---|
SO_RCVBUF | TCP 수신 버퍼 크기 | 최소값은 256바이트입니다. 설정 후 시스템은 자동으로 사용자가 설정한 값을 두 배로 늘려 혼잡을 처리하는 데 사용됩니다. |
SO_SNDBUF | TCP 송신 버퍼 크기 | 최소값은 2048바이트입니다. |
SO_RCVLOWAT | 낮은 워터마크를 받았습니다. | 기본값은 1바이트입니다. TCP 수신 버퍼에서 읽을 수 있는 총 데이터 수가 해당 하위 워터마크보다 크면 IO 다중화 시스템 호출은 해당 소켓에서 데이터를 읽을 수 있음을 애플리케이션에 알립니다. |
SO_SNDLOWAT | 최고 워터마크가 전송됨 | 기본값은 1바이트입니다. TCP 송신 버퍼의 여유 공간이 로우 워터마크보다 클 때 데이터를 쓸 수 있습니다. |
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
#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
*/
다음 두 함수는 /etc/services 파일을 읽어 서비스 정보를 얻습니다. 다음 내용은 Wikipedia에서 가져온 것입니다.
서비스 파일은 최신 운영체제의 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
입니다. 출력은 파일에서 이것으로 이동합니다.
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 /* 这块内存长度*/
}
돌아와서 사용 예제를 추가하세요. 이 예제는 int의 메모리 표현을 파일에 기록합니다. hexdump를 사용하여 0000000 86a0 0001
파일을 봅니다. 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 );
}
sendfile 기능
#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 );
통계 구조는 단순히 파일의 ID 카드인 fstat를 사용하여 생성할 수 있습니다.
#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 - */
};
신분증 생성 기능
// 第一个参数需要调用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 );
접속 기능 두 파일 이름 설명자 간에 데이터를 이동하는 데 사용됩니다. 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 );
}
선택 기능 선택 기능은 두 번째 매개변수 목록을 읽을 수 있을 때 반환되거나 지정된 시간이 반환될 때까지 기다립니다.
반환 후 두 번째 매개변수 fdset가 가리키는 컬렉션은 읽을 수 있는 fd 목록으로 수정됩니다. 이를 위해서는 반환할 때마다 fdset 컬렉션을 업데이트해야 합니다.
반환 후 이 함수의 반환 값은 읽을 수 있는 fd의 수입니다. fdset 컬렉션을 순회하고 FD_ISSET을 사용하여 fdset[i]가 포함되어 있는지 확인한 다음 fd가 listeningfd인지 확인합니다. 그렇지 않은 경우에는 다른 사람이 승인한 것입니다. fd는 읽을 데이터가 있는지 또는 연결이 끊어졌는지 확인합니다.
#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
이벤트가 발생하지 않았습니다 . 수락, 전송, errno가 EAGAIN(再来一次)
또는 EWOULDBLOCK(期望阻塞)
으로 설정되어 있습니다. EINPROGRESS(正在处理中)
로 설정되었습니다.
성능 향상을 위해서는 이벤트가 이미 발생한 경우 Non-Blocking IO를 호출해야 합니다.
일반적으로 사용되는 IO 다중화 기능인 select
poll
epoll_wait
9장에서 나중에 설명합니다. 신호에 대해서는 10장에서 설명합니다.
두 가지 효율적인 이벤트 처리 모드 및 동시성 모드
프로그램은 컴퓨팅 집약적(많은 CPU와 적은 IO 리소스 사용)과 IO 집약적(반대로)으로 구분됩니다. 전자는 동시 프로그래밍을 사용할 때 효율성을 감소시키는 반면, 후자는 다중 프로세스를 모두 사용하여 효율성을 향상시킵니다. 그리고 멀티스레딩 방식.
동시성 모드 - IO 장치와 여러 논리 장치 간의 작업을 조정하는 방법입니다. 서버에는 두 가지 주요 동시성 모드가 있습니다.
반동기/반비동기 모드 IO 모델에서 비동기식과 동기식의 차이점은 커널이 애플리케이션에 알리는 IO 이벤트의 종류(준비 이벤트 또는 완료 이벤트)와 IO 읽기 및 쓰기를 완료한 사람(애플리케이션 또는 커널)입니다.
그리고 여기서(동시성 모드) 동기화는 완전히 코드 시퀀스 순서대로 실행되는 것을 의미합니다. 동기식으로 실행되는 스레드를 동기식 스레드라고 합니다. 비동기식은 시스템 이벤트(인터럽트, 신호)에 의해 구동되어야 합니다. 즉, 비동기식으로 실행되는 스레드입니다. 비동기 스레드라고 함
서버(우수한 실시간 성능이 필요하고 동시에 여러 고객 요청을 처리할 수 있음) - 일반적으로 동기 스레드 및 비동기 스레드를 사용하여 구현됩니다. 즉, 반동기/반비동기 모드 동기 스레드 - 고객 논리를 처리하고 개체를 처리합니다. 요청 큐를 비동기적으로 스레드 - 고객 요청을 받은 후 IO 이벤트를 처리하고 이를 요청 개체에 캡슐화하여 요청 큐에 삽입합니다.
Semi-Sync/Semi-Async 패턴의 변형이 있습니다半同步/半反应堆模式
비동기 스레드 - 메인 스레드 - 모든 소켓의 이벤트 모니터링을 담당합니다.
리더/팔로워 모델 약간
효율적인 프로그래밍 방법 - 유한 상태 기계
// 状态独立的有限状态机
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 단어의 코드 문자를 문자로 복사하는 데 한 시간이 걸렸습니다 @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, '