我們都知道在網絡編程中,可以調用socket()函數來獲取一個套接字描述符。但是,socket()函數的作用只是返回一個很小的非負整數值嗎?並非如此,socket()函數的作用是創建套接字的。
創建套接字的流程如下
socket()函數會觸發內核調用sys_socket()函數,然後sys_socket()函數會調用sock_create()函數。此函數
會根據我們在socket()函數的參數中指定的協議族類型調用不同的套接字創建函數。例如,如果在socket()
函數的參數表中指定協議族爲PF_INET時,sock_create()函數會調用inet_create()函數創建INFT套接字。
這些套接字函數首先創建套接字的內核表示結構,在返回一個套接字描述符來標識生成的套接字對象。
因此,套接字是有內核表示的結構的,但是我們的應用程序只需要獲取其描述符即可。並且在這裏可以看出,這裏的套接字結構十分簡單,沒有過多的信息。但是我們知道很多協議通信中,我們需要指定一些ip地址和端口號來創建連接。很明顯,這裏的套接字結構中並不包含這些信息。因此,我們在進行網絡通信的過程中,需要將由socket()函數創建的套接字與一個地址相關聯,這個地址包括ip地址和端口號等信息,這個地址我們使用套接字地址結構來表示。不同協議族的套接字地址結構也不相同,下面是IPv4和IPv6的套接字地址結構:
//IPv4
struct sockaddr_in
{
unsigned short sin_len; //IPv4地址長度
short int sin_family; //指代協議簇,在TCP套接字編程只能是AF_INET
unsigned short sin_port; //存儲端口號(使用網絡字節順序),數據類型是一個16爲的無符號整形類型
struct in_addr sin_addr;//存儲IP地址,IP地址是一個in_add結構體(結構在下面)
unsigned char sin_zero[8]; //爲了讓sockaddr與sockaddr_in兩個數據結構保持大小相同而保留的空字節
};
struct in_addr
{
unsigned long s_addr; //按照網絡字節順序存儲IP地址
};
//IPv6
struct sockaddr_in6
{
unsigned short int sin6_len; //IPv6結構長度,是一個無符號的8爲整數,表示128爲IPv6地址長度
short int sin6_family; //地址類型AF_INET6
unsigned short int sin6_port; //存儲端口號,按網絡字節順序
unsigned short int sin6_flowinfo; //低24位是流量標號,然後是4位的優先級標誌,剩下四位保留
struct in6_addr sin6_addr; //IPv6地址,網絡字節順序
};
struct in6_addr
{
unsigned long s6_addr; //128位的IPv6地址,網絡字節順序
};
struct sockaddr
{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
好了,現在套接字有了,套接字地址也有了,我們需要將這兩者進行綁定,這個過程就是通過bind()函數來實現的。bind()函數的原型如下:
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
struct sockaddr_in serv;
/***/
bind(sockfd, (struct sockaddr *) &serv, sizeof(serv));
注意理解這裏用來綁定套接字描述符的套接字地址結構和客戶端用來connect服務器的套接字描述符(connect()函數的第二個參數)。bind()是綁定的地址結構是本機的端口號,因此在TCP通信過程中,在客戶端connect()函數之前綁定似乎沒有什麼意義。即,在客戶端綁定套接字地址的前提是在與服務器建立連接之前就知道客戶端本機將要進行通信工作的端口從而指定該端口來通訊。但是這樣容易使得不同的某臺主機上面不同的客戶端進程爭奪一個端口從而發生搶佔。如果將分配端口的工作交給內核,內核會爲沒有綁定端口的套接字分配一個未被佔用的端口。
具體可以參考這篇博文http://blog.chinaunix.net/uid-23193900-id-3199173.html