### 網絡字節序
不同的主機會以不同的順序存儲一個多字節整數,為了所有主機能夠理解端口和IP地址,需要一個標準字節序,在socket地址結構中,port和ip都需要轉換成網絡字節序
主機字節序 -> 網絡字節序 -> 主機字節序
### internet socket地址結構
internet socket地址結構定義在<netinet/in.h>
#### IPV4地址結構
```c
struct in_addr {
in_addr_t s_addr;
}
struct sockaddr_in {
sa_family_t sin_family; // 地址族,如AF_INET
in_port_t sin_port; // 端口(網絡字節序,無符號整型,16位)
struct in_addr sin_addr; // ip地址(網絡字節序,無符號整型,32位)
}
```
- ipv4通配地址常量: INADDR_ANY
- ipv4回環地址常量: INADDR_LOOPBACK
#### IPV6地址結構
```c
struct in6_addr {
unit8_t s6_addr[16];
}
struct sockaddr_in6 {
sa_family_t sin6_family; // 地址族,AF_INET6
in_port_t sin6_port; // 端口(網絡字節序,無符號整型,16位)
struct in_addr6 sin6_addr; // ip地址(網絡字節序,無符號整型,128位)
}
```
- ipv6通配地址常量: IN6ADDR_ANY_INIT
- ipv6回環地址常量: IN6ADDR_LOOPBACK_INIT
大概可以這么使用:
```c
const struct in_addr6 in6addr_any = IN6ADDR_ANY_INIT;
struct sockadd_in6 addr;
memset(&addr, 0, sizeof(struct sockadd_in6));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(9501);
addr.sin_addr = in6addr_any;
```
#### 轉換函數
計算機以二進制來表示IP地址和端口號,二進制和可讀性形式之間的轉換函數如下:
#### inet_pton
```c
// returns 1 on success, or -1 on error
#include <arpa/inet.h>
int inet_pton(int domain, const char *src_str, void *addrptr);
```
將src_str中的字符串轉換成網絡字節序的二進制IP地址,根據domain的值,將轉換結果存放在指向in_addr或in6_addr的結構中
#### inet_ntop
```c
// returns pointer to dst_str on success, or null on error
#include <arpa/inet.h>
const char *inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
```
- domain AF_INET或AF_INET6
- addrptr 一個指向in_addr或in6_addr的指針
- dst_str 存放轉換結果
- len 指定dst_str長度,使用INET_ADDRSTRLEN或INET6_ADDRSTRLEN表示ipv4或ipv6可讀形式的最大長度
#### getaddrinfo
給定主機名和服務名返回網絡字節序的二進制IP地址和端口號結構(轉換的結構放在in_addr或in6_addr指向的結構中),用于代替gethostbyname()和getservbyname();
getaddrinfo調用是可能會發送一個DNS查詢請求;返回結果不為0時表示錯誤,具體的錯誤碼可以根據gai_strerror(int errcode)返回錯誤描述
```c
// returns 0 on success, or nonzero on error
#include <netdb.h>
int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result);
```
- host 主機名或ip展示地址
- service 服務名或十進制的端口號
- hints 指向一個addrinfo結構,規定了通過result返回socket地址結構的標準
- result指向一個addrinfo結構的鏈表
#### addrinfo結構
```c
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char *ai_cannoname;
struct sockaddr *ai_addr;
struct sockaddr *ai_next;
}
```
使用addrinfo結構前,調用memset設置addrinfo結構每個字段為0
- ai_flags
- AI_PASSIVE 當host設置為NULL時,將綁定到通配地址
- AI_NUMERICSERV 防止服務名解析
- ai_addr 指向socket地址結構
- ai_next 指向下個addrinfo結構
- ai_family AF_INET,AF_INET6,AF_UNSPEC(表示ipv4和ipv6)
#### freeaddrinfo 釋放getaddrinfo分配內存
getaddrinfo會動態為result引用的結構分配內存,所以需要釋放內存,調用freeaddrinfo()
```c
#include <netdb.h>
void freeaddrinfo(struct addrinfo *result)
```
#### getnameinfo
給定地址結構返回主機名和服務名,用于代替gethostbyaddr()和getservbyport()
```c
// returns 0 on success, or nonzero on error
#include <netdb.h>
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *serv, size_t servlen, int flags)
```
- addr 待轉換的地址結構
- host 轉換后主機名保存在host,可以為NULL
- hostlen NI_MAXHOST表示返回主機名字符串最大字節,1025;需要定義_GNU_SOURCE特性宏
- serv 轉換戶服務名保存在serv,可以為NULL
- servlen NI_MAXSERV表示返回服務名字符串最大字節,32;需要定義_GNU_SOURCE特性宏
- flags 位掩碼,控制這getnameinfo行為
```c
// header
#ifndef DOMORE_SOCKET
#define DOMORE_SOCKET
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h>
#include <arpa/inet.h>
#define UNIX_SOCK_PATH "/tmp/mysock3"
#define PORT_NUM "9501"
#define BUF_SIZE 100
#define BACKLOG 10
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
// server
PHP_METHOD(domore_socket, inet_server)
{
int errcode, sfd, cfd, optval;
zend_string *host, *port;
socklen_t addrlen;
struct addrinfo hints;
struct addrinfo *result, *rp;
struct sockaddr_storage claddr; // 通用地址結構
char cl_host[NI_MAXHOST]; // NI_MAXHOST需要定義特性宏_GNU_SOURCE
char cl_port[NI_MAXSERV];
char recv_data[BUF_SIZE];
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "SS", &host, &port) == FAILURE)
{
DOMORE_ERROR_DOCREF("parse parameters failed");
}
// 忽略SIGPIPE,防止對一個關閉的socket對端寫入數據會收到SIGPIPE信號,從而是write失敗并返回EPIPE錯誤
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
{
DOMORE_ERROR_DOCREF("signal failed");
}
// getaddrinfo獲取網絡字節序的二進制ip地址和端口號結構體
// hints.ai_flags = AI_PASSIVE | AI_NUMRICSERV; // 通配地址,數字端口
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_NUMERICSERV;
hints.ai_family = AF_UNSPEC; // 允許ipv4和ipv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_canonname = NULL;
hints.ai_next = NULL; // 指向下一個addrinfo結構
hints.ai_addr = NULL; // 指向socket地址結構
errcode = getaddrinfo(ZSTR_VAL(host), ZSTR_VAL(port), &hints, &result);
if (errcode != 0)
{
DOMORE_ERROR_DOCREF(gai_strerror(errcode));
}
for (rp = result; rp != NULL; rp = rp->ai_next)
{
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1)
{
continue;
}
// 重復使用端口,停止的socket的端口再一段時間內不能重復使用
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1)
{
DOMORE_ERROR_DOCREF("setsockopt failed");
}
// 綁定成功,終止
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
{
break;
}
close(sfd);
}
if (rp == NULL)
{
DOMORE_ERROR_DOCREF("can not bind socket to any address");
}
if (listen(sfd, BACKLOG) == -1)
{
DOMORE_ERROR_DOCREF("listen failed");
}
freeaddrinfo(result); // 釋放addrinfo內存
for (;;)
{
addrlen = sizeof(struct sockaddr_storage);
cfd = accept(sfd, (struct sockaddr *)&claddr, &addrlen);
if (cfd == -1)
{
continue;
}
// 打印客戶單IP和端口號
if(getnameinfo((struct sockaddr *)&claddr, addrlen, cl_host, NI_MAXHOST, cl_port, NI_MAXSERV, 0) == 0)
{
php_printf("client ip and port is : %s %s \n", cl_host, cl_port);
}
else
{
php_printf("get client name info failed \n");
}
memset(recv_data, 0, strlen(recv_data));
if (read(cfd, recv_data, BUF_SIZE) == -1)
{
DOMORE_ERROR_DOCREF("read failed");
}
else
{
php_printf("client receive data is : %s \n", recv_data);
}
if (close(cfd) == -1)
{
DOMORE_ERROR_DOCREF("close failed");
}
}
}
// client
PHP_METHOD(domore_socket, inet_client)
{
int cfd, errcode;
zend_string *host, *port, *msg;
struct addrinfo hints;
struct addrinfo *result, *rp;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSS", &host, &port, &msg) == FAILURE)
{
DOMORE_ERROR_DOCREF("parse parameters failed");
}
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_addr = NULL;
hints.ai_next = NULL;
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_UNSPEC;
errcode = getaddrinfo(ZSTR_VAL(host), ZSTR_VAL(port), &hints, &result);
if (errcode != 0)
{
DOMORE_ERROR_DOCREF(gai_strerror(errcode));
}
for (rp = result; rp != NULL; rp = rp->ai_next)
{
cfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (cfd == -1)
{
continue;
}
if (connect(cfd, (struct sockaddr *)rp->ai_addr, rp->ai_addrlen) == 0)
{
break;
}
if (close(cfd) == -1)
{
DOMORE_ERROR_DOCREF("close failed");
}
}
if (rp == NULL)
{
DOMORE_ERROR_DOCREF("can not connect any socket");
}
freeaddrinfo(result);
if (write(cfd, ZSTR_VAL(msg), ZSTR_LEN(msg)) != ZSTR_LEN(msg))
{
DOMORE_ERROR_DOCREF("write failed");
}
}
```
- php
- 編譯安裝
- 基本概念
- 垃圾回收機制
- 生命周期
- zval底層實現
- c擴展開發
- gdb調試工具
- 自定義擴展簡單demo
- 鉤子函數
- 讀取php.ini配置
- 數組
- 函數
- 類
- yaf擴展底層源碼
- swoole擴展底層源碼
- memoryGlobal內存池
- swoole協程使用記錄
- 單點登錄sso原理
- compser使用
- session實現機制
- c & linux
- gcc
- 指針
- 結構體,聯合和位字段
- 宏定義井號說明
- printf家族函數和可變參數
- 共享函數
- 靜態庫和動態庫
- makefile自動化構建
- 信號一
- 信號二
- inotify監控文件事件
- socket編程
- 簡介
- UNIX DOMAIN
- Internet DOMAIN
- TCP/IP
- 文件IO多路復用
- 內存管理
- 進程組,會話和控制終端
- daemon守護進程
- 多進程
- 多線程
- 常用進制轉換
- go
- 入門知識
- 字節和整數裝換
- python
- redis
- 應用場景
- 消息隊列
- 熱點數據
- 掃碼登錄
- 訂閱發布
- 次數限制
- 搶購超賣
- 持久化機制
- mysql
- 工作流程
- MyISAM和InnoDB區別
- 用戶和權限管理
- 執行計劃
- sql優化
- 事務和鎖
- 慢查詢日志
- case...when...then...end用法
- sql
- 參考
- linux
- 內核參數優化
- 防火墻設置
- docker
- docker入門知識
- 算法
- 多維數組合
- DFA算法
- 紅包金額分配