創建 Socket:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain: AF_INET AF_UNIX(AF_LOCAL 在某些系統) 規定通信的本質,比如地址的格式
type: SOCK_STREAM SOCK_DGRAM SOCK_RAW SOCK_SEQPACKET 規定數據的傳輸方式
protocol: 通常爲0,使用默認值。
四種 type 區別:
感覺聽上去很簡單,其實還是需要理解的
- SOCK_DGRAM: 數據報方式。無連接,發出去的數據不保證能收到,但是收到的字節數和發出去的肯定相等。
- SOCK_STREAM: 面向連接的流。注意是流,所以一次收到的字節數和一次發出的字節數不一定相等。有可能一次發出的數據,要分幾次收到;有可能幾次發出的數據,一次都能收到。但是肯定是面向連接的。消息之間沒有界限。
- SOCK_SEQPACKET: 面向連接的基於消息的傳輸。消息之間是有界限的。協議: SCTP
- SOCK_RAW: 數據報接口,面向IP層,需要自己添加協議頭什麼的。需要權限
Socket 常用的普通IO函數:
close fcntl ioctl select poll dup/dup2 read/readv write/writev
關閉 Socket:
close 直接關閉
shutdown 可以關閉socket某個方向的傳輸
#include <sys/socket.h>
int shutdown (int sockfd, int how);
Returns: 0 if OK, 1 on error
how: SHUT_RD/SHUT_WR/SHUT_RDWR
爲什麼需要shutdown?
- close 需要等待所有引用的描述符都關閉才最終關閉,shutdown不會
- shutdown可以關閉指定方向的傳輸
設置socket選項
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int option, const void *val,socklen_t len);
Returns: 0 if OK, 1 on error
int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp);
常用的選項:
SO_REUSEADDR 防止服務器突然重啓時重新綁定失敗。
地址格式
struct sockaddr {
sa_family_t sa_family; /* address family */
char sa_data[]; /* variable-length address */
.
.
.
};
這個是POSIX規定的格式,系統調用用的都是這個接口,所以需要強制轉換。不同的平臺會有不同的實現方式。
AF_INET 地址格式:
#include <netinet/in.h>
struct
in_addr {
in_addr_t s_addr; /* IPv4 address */
};
struct
sockaddr_in {
sa_family_t sin_family; /* address family */
in_port_t sin_port; /* port number */
struct in_addr sin_addr; /* IPv4 address */
};
上面是 Single UNIX Specification 規定的標準,每個平臺有自己的實現。Linux平臺的實現如下:
struct
sockaddr_in {
sa_family_t sin_family; /* address family */
in_port_t sin_port; /* port number */
struct in_addr sin_addr; /* IPv4 address */
unsigned char sin_zero[8]; /* filler */
};
其中 sin_zero 必須全部設爲0.
地址和可讀字符串之間的轉換:
#include <arpa/inet.h>
const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size);
Returns: pointer to address string on success, NULL on error
int inet_pton(int domain, const char *restrict str, void *restrict addr);
Returns: 1 on success, 0 if the format is invalid, or -1 on error
size 表示能容納輸出字符的數組大小(INET_ADDRSTRLEN 或者 INET6_ADDRSTRLEN )
domain 只能是 AF_INET 或者 AF_INET6
地址查找:
最常用的函數:
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host, const char *restrict service, const struct addrinfo *restrict hint, struct addrinfo **restrict res);
Returns: 0 if OK, nonzero error code on error
void freeaddrinfo(struct addrinfo *ai);
返回錯誤的時候錯誤信息由下面的函數取得:
#include <netdb.h>
const char *gai_strerror(int error);
error 是 函數的返回值
getnameinfo 通過地址來查找主機名和服務名:
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *restrict addr, socklen_t alen, char *restrict host, socklen_t hostlen, char *restrict service, socklen_t servlen, unsigned int flags);
Returns: 0 if OK, nonzero on error
注意:
host 指向 主機名稱或者 ip地址
service 指向服務名稱或者端口號 (/etc/drivers 裏面有服務名稱到端口號的映射)
填充 hint 會縮小查詢結構的範圍
注意 flag 參數的選取。
綁定地址
系統調用:
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
Returns: 0 if OK
注: addr 參數一般是有 sockaddr_in 強制轉換來的, len參數一般是sizeof 來獲取的。
在AF_INET 中,地址被指定 INADDR_ANY 將綁定本機所有的IP地址的對應端口。
獲取socket綁定的地址:
#include <sys/unistd.h>
int getsockname (int sockfd, struct sockaddr *localaddr, int *addrlen);
獲取本機
int getpeername(int sockfd, struct sockaddr *peeraddr, int *addrlen);
獲取對方
注意: getpeername 只能在面向連接的連接中使用。在UDP中調用recvfrom函數會返回對方的地址。
建立連接:
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
Returns: 0 if OK
注:
在非阻塞模式中,如果不能馬上建立連接,將返回1,errno設置爲EINPROGRESS. 程序可以使用select或者poll來確定socket什麼時候是可寫的。
connect可以用在UDP中,這樣以後發數據的時候就不用每次都指定發送地址。
監聽:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
Returns: 0 if OK, 1 on error
backlog 指定最大允許接入的連接數量。
接入連接:
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
Returns: file (socket) descriptor if OK, 1 on error
返回接入的socket 描述符。addr 返回接入的地址和端口號,注意接入的客戶地址中的端口號是對方主機隨即分配的哦。
數據傳輸
發送: write / send / sendto / sendmsg
接收: read / recv / recvfrom / recvmsg
注意:
sendto recvfrom 一般用在 UDP中
一些常用的flags: MSG_OOB MSG_PEEK MSG_NOBLOCK
帶外數據(Out of band data)
TCP支持,緊急數據,高優先級發送。
帶外數據只支持一個字節,發送的時候指定 MSG_OOB
接收到數據的時候,產生 SIGURG 信號,但是必須先進行設置才能接收到信號:
- fcntl(sockfd, F_SETOWN, pid); 設置socket所屬的進程。F_GETOWN可以得到socket所屬的進程或進程組
關於 urgent mark:
TCP支持在普通數據流中接受緊急數據。必須設置socket的選項爲 SO_OOBINLINE.
可以使用sockatmark來判斷當前是否在讀urgent mark。
#include <sys/socket.h>
int sockatmark(int sockfd);
Returns: 1 if at mark, 0 if not at mark, -1 on error
最後
- TCP可以在正常數據中接收OOB,也可以單獨recv(指定MSG_OOB).
- TCP 只支持一個字節的OOB,後面的會沖掉前面未接收的
關於非阻塞和異步
設置費阻塞模式:
fcntl 打開O_NOBLOCK 開關,這樣send recv 暫時不能執行的時候返回錯誤,errno設置EWOULDBLOCK 或者 EAGAIN. 然後可以用poll/select/epoll來查詢什麼時候可以寫或讀。
異步socket:
當socket可讀或者可寫的時候會發信號SIGIO,估計也要先設置socket的所屬進程 F_SETOWN
啓用異步的步驟:
- 設置socket所屬進程
- fcntl(fd, F_SETOWN, pid)
- ioctl(fd, FIOSETOWN, pid)
- ioctl(fd, SIOCSPGRP, pid)
- 對socket啓用異步
- fcntl(fd, F_SETFL, flags|O_ASYNC)
- ioctl(fd, FIOASYNC, &n)
推薦使用fcntl,貌似所有平臺都支持。