想不想自己写一个简单的QQ?想不想自己写一个聊天室?想不想知道2000年的人是如何上网的?本节讲解一些基础的网络函数,带你看看,编写一个完整的TCP客户端、服务端需要掌握哪些函数?之后,我们要开发自己的QQ。
1 socket函数
// sys/socket.h
/* 使用协议__protocol在域__domain中创建一个__type类型的新套接字。
如果__protocol为零,则自动选择一个。
返回新套接字的文件描述符,或者返回-1表示错误。
*/
extern int socket (int __domain, int __type, int __protocol) __THROW;
__domain
:即family,AF_INET
IPv4协议,AF_INET6
IPv6协议__type
:SOCK_STREAM
字节流套接字,SOCK_DGRAM
数据报套接字,SOCK_RAW
原始套接字__protocol
:IPPROTO_TCP
TCP传输协议,IPPROTO_UDP
UDP传输协议
socket函数在成功时返回一个小的非负整数值,它与文件描述符类似,我们把它称为套接字描述符(socket descriptor),简称socketfd。
AF_
前缀表示地址族,PF_
前缀表示协议族 ,因为历史上曾想让一个协议族(PF)支持多个地址族(AF),用PF来创建套接字,用AF来创建套接字地址结构,然而就只是想想,没有实现。现在AF和PF的值是相等的。
// bits/socket.h
/* Protocol families. */
#define PF_INET 2 /* IP protocol family. */
#define PF_INET6 10 /* IP version 6. */
/* Address families. */
#define AF_INET PF_INET
#define AF_INET6 PF_INET6
2 connect函数
// sys/socket.h
#define __CONST_SOCKADDR_ARG const struct sockaddr *
extern int connect (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len);
__fd
:socket函数返回的套接字描述符__addr
:指向套接字结构地址的指针__len
:该套接字的大小
TCP客户用connect函数和TCP服务器建立连接。客户在调用connect函数前可以不调用bind函数,因为如果有需要,内核会确定本机IP地址,并选择一个临时端口作为源端口。
3 bind函数
// sys/socket.h
/* 把一个本地协议地址赋值给一个套接字 */
#define __CONST_SOCKADDR_ARG const struct sockaddr *
extern int bind (int __fd, __CONST_SOCKADDR_ARG __addr, socklen_t __len) __THROW;
- 对于IPv4来说,协议地址是32位的IPv4地址和16位的端口号组合。
- 对于IPv6来说,协议地址是128位的IPv6地址和16位的端口号组合。
绑定操作涉及3个对象:套接字,地址和端口。其中套接字是捆绑的主体,地址和端口是捆绑的客体。在套接字上绑定地址和端口表示:该地址和端口已经被套接字使用。
- 如果指定端口号为0,那么内核在bind被调用的时候选择一个临时端口。
- 如果指定IP地址是通配地址,那么内核将等到套接字已连接(TCP)或在套接字上发出数据报(UDP)时才选择一个本地IP地址。
对于IPv4来说,通配地址是0(INADDR_ANY
),如果让内核帮套接字选择一个端口,那么必须注意,bind函数并不返回所选择的端口号。因为bind函数的_addr参数有const限定词,它无法返回所选的值。为了拿到内核选择的临时端口,必须调用getsockname
函数来返回协议地址。
4 listen函数
// sys/socket.h
extern int listen (int __fd, int __n) __THROW;
listen函数仅有TCP服务器调用,它做两件事:
- 当socket函数创建一个套接字时,默认是主动套接字,listen函数将它变成被动套接字,指示内核应接受指向该套接字的连接请求。调用listen将导致套接字从
CLOSED
状态转换到LISTEN
状态。 - 第二个参数
__n
规定了内核应该为相应套接字排队的最大连接个数。内核为任意一个监听套接字维护两个队列,一个叫未完成连接队列
,一个叫已完成队列
。
- 未完成连接队列:每个处于三次握手中的TCP连接,套接字处于
SYN_RCVD
状态。 - 已完成连接队列:每个已完成三次握手的TCP连接,套接字处于
ESTABLISHED
状态。
这2个队列之和不能超过__n
,否则就无法新建TCP连接,这就是SYN Flood攻击的原理。
5 accept函数
// sys/socket.h
extern int accept (int __fd, __SOCKADDR_ARG __addr,socklen_t *__restrict __addr_len);
accept函数由TCP服务器调用,用于从已完成连接队列
的队头返回下一个已完成连接。如果该队列为空,那么进程被投入睡眠。(默认套接字为阻塞方式)
参数__addr
和__addr_len
返回已连接的对端进程的协议地址,即返回客户端的协议地址。__addr_len
是值-结果参数,返回内核存放在协议地址中实际的字节数。如果对客户端的协议地址不感兴趣,可以将指针置为NULL。
注意: 如果accept成功,返回值是又内核自动生成的全新描述符,称为已连接套接字
。服务端与客户通信,将用这个套接字,服务完成后,这个套接字就被关闭。 而参数__fd
称为监听套接字
, 一个服务仅仅创建一个监听套接字
。
6 close函数
// /usr/include/unistd.h
extern int close (int __fd);
close函数一般可以用来关闭套接字,并终止TCP连接。但是在并发服务器中,close函数仅仅将套接字描述符引用计数减1。所以并不能一定在TCP连接上发送FIN报文,如果想在TCP连接上发送FIN报文,可以用shutdown
函数。
参考文献:《UNIX网络编程 卷1:套接字联网API》