数据结构
这里仅讨论IPv4相关的数据,IPv6与之类似,可以查阅相关的资料。
网络套接字的地址结构:
1 | #include <netinet/in.h> |
1 | #include <netinet/in.h> |
网络协议规定必须指定一个网络字节序(大端还是小端),作为网络编程人员必须清楚不同字节之间的差异。比如,在每一个TCP分节中都有16位的端口号和32位的IPv4地址,发送协议栈和接收协议栈必须就这些多字节字段各个字节的传送顺序达成一致。网络协议使用大端字节序来传送这些多字节数据。
举例来说,如果有一个32位的IPv4地址,首先要转换字节序再使用。
1 | uint32_t ip_addr = htonl(ori_ip_addr); |
共有4个转换函数:
1 | #include <netinet/in.h> |
h
表示host,n
表示network,s
表示short,l
表示long
通常来说我们习惯用点分十进制来表示IP地址,如192.168.1.1
,因为这样易读易懂,但是在网络上传输时应该使用的32位的二进制来表示IP地址,于是就要进行转换,以下两个函数提供转换功能:
1 | #include <arpa/inet.h> |
family
即可以是AF_INET
,也可以是AF_INET6
。如果是不被支持的地址族作为family参数,返回一个错误,errno
被设置为EAFNOSUPPORT
。
这里n
表示numeric,p
表示presentation。
基本TCP套接字编程
sock函数
首先需要创建一个套接字,创建套接字的函数如下:
1 | #include <sys/socket.h> |
family
指定协议族,type
指定套接字类型,protocol
指定某个协议类型常值,当设置为0时,程序会选择和给定的family和type组合的系统默认值。
connect函数
TCP客户端用connect函数来与TCP服务器建立连接。
1 | #include <sys/socket.h> |
客户不必在使用connect前调用bind函数,因为需要的话内核会分配一个临时的端口给客户进程。
出错返回的错误情况:
- TCP客户等待一段时间后没有收到服务器返回的TCP分节,返回ETIMEDOUT错误。
- 对客户的SYN响应是RST(复位),表明服务器上并没有进程在等待与之连接,这是一种硬错误,客户收到后马上返回ECONNREFUSED。
- 客户发出的SYN在某个路由上引发了一个”destination unreachable”的ICMP错误,则认为是一种软错误。客户机内核保存该消息,并持续发送SYN包,如果等待一段时间后未收到响应,就把保存的信息(ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。
bind函数
绑定一个本地协议地址给一个套接字,对于网际协议,协议地址是32位的IPv4地址或128位的IPv6地址与16位的TCP或者UDP端口号的组合。
函数原型:
1 | #include <sys/socket.h> |
listen函数
listen
仅由TCP服务器调用,它把一个未连接的套接字转换成一个被动套接字,以接收客户的请求。
1 | #include <sys/socket.h> |
内核为任何一个给定的监听套接字维护两个队列:
- 未完成连接队列:客户已经发送一个SYN分节给服务器,而服务器正在等待完成对应的三次握手过程。
- 已完成连接队列:每个已完成TCP三次握手的客户对应启动一项。
backlog
指定两个队列总和的最大值。
要注意的是,在三次握手完成之后,但在服务器调用accept之前到达的数据应由服务器TCP排队。
accept函数
accept
函数用于TCP服务器调用,将从已完成队列返回队头的连接。如果已完成连接队列为空,那么服务器进程将被睡眠,直到下一个连接完成。
函数原型:
1 | #include <sys/socket.h> |
一个并发的服务器的例子
1 | #include "unp.h" |