socket允許在同一個主機或通過一個網絡連接起來的不同主機上的應用程序之間的通信,簡單來說,PHP連接MySQL就是一個socket
### socket domain
socket domain即通信范圍,主要有3種類型,如下圖所示,其中AF表示地址族

### socket類型
socket的類型用的最多的兩種,流(socket stream)和數據報(socket dgram);流使用了傳輸控制協議TCP,數據報使用了用戶數據報協議UDP,兩者socket的區別如下:
| 屬性 | 流 | 數據報 |
| :--- | :---: | :---: |
| 是否可靠 | 是 | 否 |
| 是否面向連接(雙向的,一個socket連接到另一個socket)| 是 | 否 |
| 是否保留消息邊界(字節流) | 否 | 是 |
### socket_stream流程
- socket; 服務端或客戶端創建一個socket
- bind; 將socket綁定到一個位置上,客戶端需要定位到這個位置才能知道這個socket
- listen; 服務端監聽來自客戶端的連接
- accept; 服務端接受來之客戶端的連接,在connect之前是阻塞的
- connect; 客戶端建立連接到服務端

socket I/O通過write和read或send或recv來完成,默認是阻塞的,可以通過fcntl()的F_SETFL操作來啟用O_NONBLOCK來執行非阻塞IO
#### 創建socket
```c
// returns file descripter on success, or -1 on error
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
```
- domain; 通信domain,包括AF_UNIX,AF_INET,AF_INET6
- type; socket類型,包括SOCKET_STREAM,SOCKET_DGRAM
- protocol; 一般為0
#### bind綁定地址
```c
// returns 0 on success, or -1 on error
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
```
- sockfd; 由socket創建返回的文件描述符
- addr; 綁定的地址,它是一個結構體(各個domain對應的結構地址:sockaddr_un,sockaddr_in,sockaddr_in6)
- addrlen
通用socket地址結構如下:
```c
struct sockaddr {
sa_family_t sa_family; // AF_XXX
char sa_data[14];
}
```
#### 監聽listen
```c
// returns 0 on success, or -1 on error
int listen(int sockfd, int backlog)
```
- sockfd; 由socket創建的文件描述符
- backlog; 最大處理連接數,例如backlog等于10表示可以10個客戶端同時嘗試連接服務器,他們不會立即得到響應,但是可以等待;而第11個客戶端會被告知服務器繁忙,
如收到ECONNREFUSED錯誤,backlog可以用SOMAXCONN常量表示,該常量被定義為128
#### 接受連接accept
```c
// returns file descripter on suceess, or -1 on error
int accept(int sockfd, stuct sockaddr *addr, socklen_t *addrlen)
```
- sockfd; 由socket創建的文件描述符
- addr; 客戶端socket的地址結構,它保存連接客戶端的詳細信息
- addrlen; 指向客戶端socket結構大小的指針
```c
#include <sys/socket.h>
struct sockaddr client_addr;
int addlen = sizeof(client_addr);
int fd;
fd = accept(sockfd, &client_addr, &addrlen); // 服務器返回新的描述符
if (fd == -1) {
error("accept failed \n");
}
```
#### 客戶端連接到服務端connect
```c
// returns 0 on success, or -1 on error
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
```
#### 關閉連接close
一對連接的流socket,如果調用close關閉了,當對端的socket繼續讀取數據會收到文件結束,當對端的socket繼續發送數據則會收到SIGPIPE信號,并且系統調用會返回
EPIPE錯誤
### socket_dgram流程

#### recvfrom 接收數據
數據報使用recvfrom和sendto來接收和發送數據,recvfrom接收從bind綁定的數據,所以recvfrom之前,需要bind
```c
// returns number of bytes received, 0 on EOF, or -1 on error
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
```
- buffer 接收的數據會保存到buffer
- length 指定buffer的長度
- struct sockaddr *src_addr 從src_addr地址接收的數據,即源socket地址
- socklen_t *addrlen 指例如sockaddr_un結構地址大小
#### sendto 發送數據
```c
// returns number of bytes send, or -1 on error
ssize_t sendto(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
```
- buffer 要發送的數據緩沖區
- length 發送長度
- struct sockaddr *src_addr 發送的目標地址
- socklen_t *addrlen 指例如sockaddr_un結構地址大小
#### demo
以下是我實現的php擴展的兩個方法,unix domain中的數據報 socket
```c
// header
#ifndef DOMORE_SOCKET
#define DOMORE_SOCKET
#include <sys/socket.h>
#include <sys/un.h>
#define DOMORE_ERROR_DOCREF(msg) php_error_docref(0, E_ERROR, msg)
#define UNIX_SOCK_PATH "/tmp/mysock3"
#define BUF_SIZE 10
#endif
// server
PHP_METHOD(domore_socket, unix_dgram_sv)
{
int sfd;
char buf[BUF_SIZE];
ssize_t numBytes;
socklen_t len;
struct sockaddr_un addr, cl_addr;
sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sfd == -1)
{
DOMORE_ERROR_DOCREF("socket failed");
}
// remove old socket unix path
if(remove(UNIX_SOCK_PATH) == -1 && errno != ENOENT)
{
DOMORE_ERROR_DOCREF("remove failed");
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, UNIX_SOCK_PATH, sizeof(addr.sun_path) - 1);
// bind unix path
if(bind(sfd, (struct sockaddr_un *)&addr, sizeof(struct sockaddr_un)) == -1)
{
DOMORE_ERROR_DOCREF("bind failed");
}
// recv from client
len = sizeof(struct sockaddr_un);
while((numBytes = recvfrom(sfd, buf, BUF_SIZE, 0, (struct sockaddr_un *)&cl_addr, &len)) > 0)
{
php_printf("recvfrom %d bytes \n", numBytes);
for (int i = 0; i < numBytes; ++i)
{
buf[i] = toupper((unsigned char)buf[i]);
}
if (sendto(sfd, buf, numBytes, 0, (struct sockaddr_un *)&cl_addr, len) != numBytes)
{
DOMORE_ERROR_DOCREF("sendto failed");
}
}
if (numBytes == -1)
{
DOMORE_ERROR_DOCREF("recvfrom failed");
}
}
// client
PHP_METHOD(domore_socket, unix_dgram_cl)
{
int sfd;
ssize_t numBytes;
size_t msglen;
struct sockaddr_un claddr, svaddr;
char buf[BUF_SIZE];
char *msg[3] = {"one", "two", "three"};
sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sfd == -1)
{
DOMORE_ERROR_DOCREF("socket failed");
}
// client path
memset(&claddr, 0, sizeof(struct sockaddr_un));
claddr.sun_family = AF_UNIX;
snprintf(claddr.sun_path,sizeof(claddr.sun_path),"/tmp/ud_ucase_cl.%ld",(long)getpid());
// bind unix path
if(bind(sfd, (struct sockaddr_un *)&claddr, sizeof(struct sockaddr_un)) == -1)
{
DOMORE_ERROR_DOCREF("bind failed");
}
// server path
memset(&svaddr, 0, sizeof(struct sockaddr_un));
svaddr.sun_family = AF_UNIX;
strncpy(svaddr.sun_path, UNIX_SOCK_PATH, sizeof(svaddr.sun_path) - 1);
for (int i = 0; i < sizeof(msg)/sizeof(char *); ++i)
{
msglen = strlen(msg[i]) + 1; // 字符串以'\0'結尾
if (sendto(sfd, msg[i], msglen, 0, (struct sockaddr_un *)&svaddr, sizeof(struct sockaddr_un)) != msglen)
{
DOMORE_ERROR_DOCREF("sendto failed");
}
if ((numBytes = recvfrom(sfd, buf, BUF_SIZE, 0, NULL, NULL)) > 0)
{
php_printf("recvfrom server %d bytes: %s \n", numBytes, buf);
}
}
}
```
### 數據報也可以使用connect
當數據報使用connect連接到對端的socket,那么可以使用簡單系統IO調用,如write,無需為發送出去的數據報指定目標地址
### 套接字選項
```c
// returns 0 on success, or -1 on error
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
```
- level 套接字選項所使用的協議,可以設置為SOL_SOCKET
- optname 標識套接字選項,如SO_TYPE, SO_REUSEADDR
- optval 指向返回值的指針
- optlen 指向返回值大小的指針
```c
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
```
- level 套接字選項所使用的協議,可以設置為SOL_SOCKET
- optname 標識套接字選項,如SO_TYPE, SO_REUSEADDR
- optval 指向返回值指針
- optlen 由optval指向緩沖區空間大小
```C
#include <sys/socket.h>
int sockfd, optval;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
optval = 1;
setsockopt(sockfd, SO_REUSEADDR, &optval, sizeof(optval));
bind(sockfd, &addr, addrlen);
listen(sockfd, backlog);
```
### 問題
- 數據報傳輸大小限制?
### 參考
- 嗨翻C語言
- Linux系統編程手冊(下冊)
- 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算法
- 紅包金額分配