1、什么是socket
我們知道進程通信的方法有管道、命名管道、信號、消息隊列、共享內存、信號量,這些方法都要求通信的兩個進程位於同一個主機。但是如果通信雙方不在同一個主機又該如何進行通信呢?在計算機網絡中我們就學過了tcp/ip協議族,其實使用tcp/ip協議族就能達到我們想要的效果,如下圖(圖片來源於《tcp/ip協議詳解卷一》第一章1.3)
、
圖一 各協議所處層次
當然,這樣做固然是可以的,但是,當我們使用不同的協議進行通信時就得使用不同的接口,還得處理不同協議的各種細節,這就增加了開發的難度,軟件也不易於擴展。於是UNIX BSD就發明了socket這種東西,socket屏蔽了各個協議的通信細節,使得程序員無需關注協議本身,直接使用socket提供的接口來進行互聯的不同主機間的進程的通信。這就好比操作系統給我們提供了使用底層硬件功能的系統調用,通過系統調用我們可以方便的使用磁盤(文件操作),使用內存,而無需自己去進行磁盤讀寫,內存管理。socket其實也是一樣的東西,就是提供了tcp/ip協議的抽象,對外提供了一套接口,同過這個接口就可以統一、方便的使用tcp/ip協議的功能了。百說不如一圖,看下面這個圖就能明白了。
圖二 socket所處層次
那么,在BSD UNIX又是如何實現這層抽象的呢?我們知道unix中萬物皆文件,沒錯,bsd在實現上把socket設計成一種文件,然后通過虛擬文件系統的操作接口就可以訪問socket,而訪問socket時會調用相應的驅動程序,從而也就是使用底層協議進行通信。(vsf也就是unix提供給我們的面向對象編程,如果底層設備是磁盤,就對磁盤讀寫,如果底層設備是socket就使用底層協議在網中進行通信,而對外的接口都是一致的)。下面再看一下socket的結構是怎樣的(圖片來源於《tcp/ip協議詳解卷二》章節一,1.8描述符),注意:這里的socket是一個實例化之后的socket,也就是說是一個具體的通信過程中的socket,不是指抽象的socket結構,下文還會進行解釋。
圖三 udp socket實例的結構
很明顯,unix把socket設計成文件,通過描述符我們可以定位到具體的file結構體,file結構體中有個f_type屬性,標識了文件的類型,如圖,DTYPE_VNODE表示普通的文件DTYPE_SOCKET表示socket,當然還有其他的類型,比如管道、設備等,這里我們只關心socket類型。如果是socket類型,那么f_ops域指向的就是相應的socket類型的驅動,而f_data域指向了具體的socket結構體,socket結構體關鍵域有so_type,so_pcb。so_type常見的值有:
- SOCK_STREAM 提供有序的、可靠的、雙向的和基於連接的字節流服務,當使用Internet地址族時使用TCP。
- SOCK_DGRAM 支持無連接的、不可靠的和使用固定大小(通常很小)緩沖區的數據報服務,當使用Internet地址族使用UDP。
- SOCK_RAW 原始套接字,允許對底層協議如IP或ICMP進行直接訪問,可以用於自定義協議的開發。
so_pcb表示socket控制塊,其又指向一個結構體,該結構體包含了當前主機的ip地址(inp_laddr),當前主機進程的端口號(inp_lport),發送端主機的ip地址(inp_faddr),發送端主體進程的端口號(inp_fport)。so_pcb是socket類型的關鍵結構,不亞於進程控制塊之於進程,在進程中,一個pcb可以表示一個進程,描述了進程的所有信息,每個進程有唯一的進程編號,該編號就對應pcb;socket也同時是這樣,每個socket有一個so_pcb,描述了該socket的所有信息,而每個socket有一個編號,這個編號就是socket描述符。說到這里,我們發現,socket確實和進程很像,就像我們把具體的進程看成是程序的一個實例,同樣我們也可以把具體的socket看成是網絡通信的一個實例。
2、具體socket實例如何標識
我們知道具體的一個文件可以用一個路徑來表示,比如/home/zzy/src_code/client.c,那么具體的socket實例我們該如何表示呢,其實就是使用上面提到的so_pcb的那幾個關鍵屬性,也就是使用so_type+ip地址+端口號。如果我們使用so_type+ip地址+端口號實例一個socket,那么互聯網上的其他主機就可以與該socket實例進行通信了。所以下面我們看一下socket如何進行實例化,看看socket給我們提供了哪些接口,而我們又該如何組織這些接口
3、socket編程接口
3.1、socket接口
int socket(int protofamily, int so_type, int protocol);
- protofamily 指協議族,常見的值有:
AF_INET,指定so_pcb中的地址要采用ipv4地址類型
AF_INET6,指定so_pcb中的地址要采用ipv6的地址類型
AF_LOCAL/AF_UNIX,指定so_pcb中的地址要使用絕對路徑名
當然也還有其他的協議族,用到再學習了 - so_type 指定socket的類型,也就是上面講到的so_type字段,比較常用的類型有:
SOCK_STREAM
SOCK_DGRAM
SOCK_RAW - protocol 指定具體的協議,也就是指定本次通信能接受的數據包的類型和發送數據包的類型,常見的值有:
IPPROTO_TCP,TCP協議
IPPROTO_UDP,UPD協議
0,如果指定為0,表示由內核根據so_type指定默認的通信協議
這里解釋一下圖三,圖三其實是使用AF_INET,SOCK_DGRAM,IPPRTO_UDP實例化之后的一個具體的socket。
那為什么要通過這三個參數來生成一個socket描述符?
答案就是通過這三個參數來確定一組固定的操作。我們說過抽象的socket對外提供了一個統一、方便的接口來進行網絡通信,但對內核來說,每一個接口背后都是及其復雜的,同一個接口對應了不同協議,而內核有不同的實現,幸運的是,如果確定了這三個參數,那么相應的接口的映射也就確定了。在實現上,BSD就把socket分類描述,每一個類別都有進行通信的詳細操作,分類見下圖。而對socket的分類,就好比對unix設備的分類,我們對設備write和read時,底層的驅動是有各個設備自己提供的,而socket也一樣,當我們指定不同的so_type時,底層提供的通信細節也由相應的類別提供。
圖4 socket層次圖
更詳細的socket()函數參數描述請移步:
http://blog.csdn.net/liuxingen/article/details/44995467
http://blog.csdn.net/qiuchangyong/article/details/50099927
3.2、bind接口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函數就是給圖三種so_pcb結構中的地址賦值的接口
- sockfd 是調用socket()函數創建的socket描述符
- addr 是具體的地址
- addrlen 表示addr的長度
struct sockaddr其實是void的typedef,其常見的結構如下圖(圖片來源傳智播客邢文鵬linux系統編程的筆記),這也是為什么需要addrlen參數的原因,不同的地址類型,其地址長度不一樣:
圖5 地址結構圖
- AF_INET:
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; struct in_addr { uint32_t s_addr; /* address in network byte order */ };
- AF_INET6:
struct sockaddr_in6 { sa_family_t sin6_family; /* AF_INET6 */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ }; struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
- AF_UNIX:
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
3.3、connect接口
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
這三個參數和bind的三個參數類型一直,只不過此處strcut sockaddr表示對端公開的地址。三個參數都是傳入參數。connect顧名思義就是拿來建立連接的函數,只有像tcp這樣面向連接、提供可靠服務的協議才需要建立連接
3.4、listen接口
int listen(int sockfd, int backlog)
告知內核在sockfd這個描述符上監聽是否有連接到來,並設置同時能完成的最大連接數為backlog。3.6節還會繼續解釋這個參數。當調用listen后,內核就會建立兩個隊列,一個SYN隊列,表示接受到請求,但未完成三次握手的連接;另一個是ACCEPT隊列,表示已經完成了三次握手的隊列
- sockfd 是調用socket()函數創建的socket描述符
- backlog 已經完成三次握手而等待accept的連接數
關於backlog , man listen的描述如下:
- The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and this setting is ignored. See tcp(7) for more information.
- If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that value; the default value in this file is 128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128.
3.5、accept接口
int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen)
這三個參數與bind的三個參數含義一致,不過,此處的后兩個參數是傳出參數。在使用listen函數告知內核監聽的描述符后,內核就會建立兩個隊列,一個SYN隊列,表示接受到請求,但未完成三次握手的連接;另一個是ACCEPT隊列,表示已經完成了三次握手的隊列。而accept函數就是從ACCEPT隊列中拿一個連接,並生成一個新的描述符,新的描述符所指向的結構體so_pcb中的請求端ip地址、請求端端口將被初始化。
從上面可以知道,accpet的返回值是一個新的描述符,我們姑且稱之為new_sockfd。那么new_sockfd和listen_sockfd有和不同呢?不同之處就在於listen_sockfd所指向的結構體so_pcb中的請求端ip地址、請求端端口沒有被初始化,而new_sockfd的這兩個屬性被初始化了。
3.6、listen、connect、accept流程及原理
以AF_INET,SOCK_STREAM,IPPROTO_TCP三個參數實例化的socket為例,通過一個副圖來講解這三個函數的工作流程及粗淺原理(圖片改自http://blog.csdn.net/russell_tao/article/details/9111769)
圖6 listen、accept、connect流程及原理圖
- 服務器端在調用listen之后,內核會建立兩個隊列,SYN隊列和ACCEPT隊列,其中ACCPET隊列的長度由backlog指定。
- 服務器端在調用accpet之后,將阻塞,等待ACCPT隊列有元素。
- 客戶端在調用connect之后,將開始發起SYN請求,請求與服務器建立連接,此時稱為第一次握手。
- 服務器端在接受到SYN請求之后,把請求方放入SYN隊列中,並給客戶端回復一個確認幀ACK,此幀還會攜帶一個請求與客戶端建立連接的請求標志,也就是SYN,這稱為第二次握手
- 客戶端收到SYN+ACK幀后,connect返回,並發送確認建立連接幀ACK給服務器端。這稱為第三次握手
- 服務器端收到ACK幀后,會把請求方從SYN隊列中移出,放至ACCEPT隊列中,而accept函數也等到了自己的資源,從阻塞中喚醒,從ACCEPT隊列中取出請求方,重新建立一個新的sockfd,並返回。
這就是listen,accept,connect這三個函數的工作流程及原理。從這個過程可以看到,在connect函數中發生了兩次握手。
更加詳細的accept建立連接流程及原理請移步下面這個博文,該博文博主是個大牛,講解的通熟易懂並且有深度:
http://blog.csdn.net/russell_tao/article/details/9111769
3.7、發送消息接口
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
這幾個接口都比較好理解,查一下man pages就知道什么含義了,man pages中講解的非常清楚。這里只說一下flags參數,也是摘抄自man pages。
flags:
- MSG_DONTWAIT (since Linux 2.2),不阻塞
Enables nonblocking operation; if the operation would block, EAGAIN or EWOULDBLOCK is returned (this can also be enabled using
the O_NONBLOCK flag with the F_SETFL fcntl(2)).
- MSG_DONTROUTE,數據包不允許通過網關
Don't use a gateway to send out the packet, only send to hosts on directly connected networks. This is usually used only by
diagnostic or routing programs. This is only defined for protocol families that route; packet sockets don't.
- MSG_OOB,帶外數據
Sends out-of-band data on sockets that support this notion (e.g., of type SOCK_STREAM); the underlying protocol must also sup‐
port out-of-band data.
- 其他
3.8、接受消息接口
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
這幾個接口都比較好理解,查一下man pages就知道什么含義了,man pages中講解的非常清楚。
4、socket編程流程及tcp狀態變遷
先做一個說明,下面的圖都不是原創,是本人收藏已久的一些原理圖,來源已經不記得了,如果大家知道來源的可以留言。
socket編程的一般模型是固定的,下面我就以幾幅圖來說明,由於插圖中已經有說明,我就不在做補充說明了。
4.1 c/s模式之TCP
圖8 c/s模型tcp編程流程圖及tcp狀態變遷圖
4.2 c/s模式之UDP
圖9 c/s模型udp編程流程圖
參考資料:
《tcp/ip協議詳解卷一、卷二》
socket函數的domain、type、protocol解析
轉自:http://www.cnblogs.com/zengzy/p/5107516.html#undefined