Chapter2 基本的TCP套接字
套接字是一个抽象层,应用程序通过套接字发送或接受数据,其方式类似打开文件句柄写入或读取。TCP/IP中的套接字主要是流套接字和数据报套接字。流套接字基于端到端协议,提供可靠的服务;数据报套接字基于UDP协议,提供尽力而为服务。
一个程序可以对应多个套接字,同样,一个套接字可以对应多个程序。套接字通过Internet地址、协议、端口号唯一标识。
文章目录
套接字的创建和销毁
在进行通信之前,需要先创建套接字抽象层。
int socket(int domian,int type,int protocol)
- 参数
domian
指定套接字的通信领域,是IPv4(AF_INET
)还是IPv6(AF_INET6
) - 参数
type
指定套接字的类型,是用于可靠字节流(SOCK_STREAM
)还是尽力而为(SOCK_DGRAM
) - 参数
protocol
指定套接字使用的协议,流套接字使用TCP(IPPROTO_TCP
),数据报套接字使用UDP协议(IPPROTO_UDP
) - 函数返回为套接字描述符,非负值表示创建成功, − 1 -1 −1表示失败。套接字描述符可以传递给其他API,以标识要在其上执行操作的套接字抽象层。
通信完成后,要关闭通信,并释放与套接字关联的资源。
int close(int socket)
如果成功会返回 0 0 0,否则返回 − 1 -1 −1
Internet地址数据结构
Sockets API提供了一种泛型数据结构(sockaddr
),以存储网络地址和端口。IPv4和IPv6有其特化的数据结构(sockaddr_in
和sockaddr_in6
),但是在为API提供参数时,需要强制转换成sockaddr
,函数会检查sa_family
项以获知真实类型,并强制转换回合适类型。
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];// not usedd
}
sin_family
负责表示地址族(AF_INET
或AF_INET6
),其中的s_addr
项可以标定sin_addr
的内存地址sin_port
为端口号,需要转换成二进制,htons(int port)
将主机字节序转换成网络字节序sin_addr
为网络地址,也得是二进制
struct sockaddr_in6
{
sa_family sin6_family;
in_port_6 sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
}
书上没讲
打印字符到网络二进制的相互转换
前面提到sin_port
和sin_addr
需要表示为网络二进制,sin_port
由htons()
负责,sin_addr
由inet_pton()
负责。
int inet_pton(int addressFamily, const char *src, void *dst);
addressFamily
为地址族(AF_INET
或AF_INET6
)src
为IP地址字符串dst
为地址存储结构中IP地址所应存放的内存块,即s_addr
- 如果成功则返回
1
1
1,如果
src
指向的内容无效则返回 0 0 0,如果addressFamily
无效则返回 − 1 -1 −1
相似的,从网络二进制到可打印字符由inet_ntop()
负责
const char *inet_ntop(int addressFamily, const void *src, char *dst, socklen_t dstBytes)
src
为被转换的网络二进制所在的内存空间,即s_addr
dst
为程序员为可打印字符分配的空间,IPv4使用INET_ADDRSTRLEN
指定大小,IPv6使用INET6_ADDRSTRLEN
dstBytes
为dst
所指向空间的大小- 如果成功,返回可打印字符所在的空间;如果失败,返回
NULL
客户与服务器建立链接
在服务器的IP地址和端口处,客户和服务器进行接触。对于客户机而言,它需要将它的套接字,连接到服务器的IP地址和端口处。
int connect(int socket,const struct sockaddr *foreignAddress,soklen_t addressLength)
对于服务器而言,它需要将它的套接字绑定到它自己的IP地址和端口上。
int bind(int socket,struct sockaddr *loackAddress,socklen_t addresslen)
一个主机会有多个IP地址,如果服务器希望接受发送到该主机的所有地址上的信息,它可以将loackAddress
中的sin_addr
项置为INADDR_ANY
(或者sin6_addr
置为INADDR6_ANY
),但是要注意,INADDR_ANY
是主机字节序,需要专场网络字节序(honl(INADDR_ANY)
负责这个工作)。INADDR6_ANY
不必。
通信
服务器的套接字要转换成侦听状态
服务器端的通信并不又原先的套接字完成,而是由原先的套接字根据建立的链接新建套接字。这点容易理解:服务要处理很多请求,但原套接字只有一个,肯定是新建套接字更容易理解。
int listen(int sockdt,int queuelen)
listen
函数负责将原套接字转成侦听状态,queuelen
为队列长度
根据链接新建套接字
int accept(int socket,struct sockaddr *clientAddress,socklen_t addresslen)
socket
项为原套接字,alientAddress
为客户端的IP地址和端口号信息,addresslen
为前者的长度。
通过accept
操作,服务器可以知道客户机的IP地址和端口号,并返回一个新套接字,用于处理该链接。
通信
一旦连接建立,客户与服务器之间的区别就会消失,可以通过各自的套接字使用send()
和recv()
互相通信。
ssize_t send(int socket,const void *msg,size_t msgLength,int flags)
ssize_t recv(int socket,void *rcvBuffer,size_t bufferLength,int flags)
socket
是用于通信的套接字msg
是发送数据所在的内存区域,rcvBuffer
是接收缓存msgLength
为发送数据的长度,bufferLength
为接收缓存的长度,是的,后者应该很大(BUFSIZ
)flags
指定行为send()
的默认行为是阻塞,直到发送了msgLength
长度的字符为止。如果成功则返回发送的字符长度,否则返回 − 1 -1 −1recv()
的默认行为是阻塞,直到接收到了一些字符为止。并返回接收到的字符长度,如果失败返回 − 1 -1 −1,也就是说,如果recv()
不可能为 0 0 0。- 要注意
send()
一次发送的字符,recv()
可能要多次接收;反之亦然,send()
多次发送的字符,recv()
可能一次就能完成接收。