1. 基本知识
(1)socket
int socket(int domain, int type, int protocol);
domain:确定通信特征,如地址格式
AF(address family) 地址族 AF_INET IPv4因特网域 AF_INET6 IPv6因特网域 AF_UNIX UNIX域 AF_UNSPEC 未指定
type:限定套接字类型,主要设置运输层特征
SOCK_STREAM 有序,可靠,双向面向字节流 SOCK_SEQPACKET 长度固定,有序,面向连接,报文传输 SOCK_DGRAM 长度固定,无连接,不可靠报文 SOCK_RAM IP协议层的数据包接口,即不使用预定义运输层
protocol:通常为0,表示按照给定的域和套接字类型选择默认协议。
socket返回fd,使其能使用部分文件IO的api
close dup dup2 poll select write read
套接字是双向的,可以禁止其输入/输出
int shutdown(int sockfd, int how) how: SHUT_RD SHUT_WR SHUT_RDWR
(2)字节序
网络传输途径的设备不同,字节序不同,为了让所有设备都能理解数据包的包头信息,需要统一成大端字节序,
被统一的内容是路由设备会读取的内容,即 目标地址,源地址,目标端口号,源端口号。
uint32_t htonl(uint32); uint16_t htons(uint16); uint32_t ntohl(uint32); uint16_t ntohs(uint16); const char * inet_ntop(int domain, const void *addr, char *str, soklen_t size); int inet_pton(int domain, const char *str, void *addr);
(3)地址格式
不同通信域,地址格式定义不同,为了统一使用 socket api,统一为 struct sockaddr 为接口类型。
IPv4域,地址格式为 struct sockaddr_in
2.因特网下的客户端,服务器
(1)绑定地址
int bind(int sock, const struct sockaddr *addr, socklen_t len);
调用 bind,让socket与具体网卡绑定,如果IP设置为INADDR_ANY,则socket可以接受所有网卡的数据包。
对于客户端,绑定特定网卡是没有意义的,如果不绑定,在调用connect或listen时,会自动随机绑定网卡。
使用 getsockname 获得 socket 绑定的地址。
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *len);
使用 getpeername 获得建立连接的对方的地址
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *len);
(2)建立连接
如果是面向连接的套接字(SOCK_STREAM、SOCK_SEQPACKET),可以调用 connect 建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
连接可能失败,需要视情况处理。
connect 也可用于无连接套接字,效果:
发送报文的目的地址设置为connect的地址,这样之后发送数据时,不需要再指定地址。
只能接受目的地址的数据。
面向连接的套接字,使用listen来宣告可以接受连接
int listen(int sockfd, int backlog);
套接字listen返回后,就开始接受连接,连接请求会保存到监听队列,
并使用 accept 接受队列中的连接请求。
int accept(int sockfd, struct sockaddr *addr, socklen_t *len);
accept涉及三个套接字,服务器原始套接字sockfd, 服务器处理客户端连接的套接字 accept的返回值,客户端的套接字。
原始套接字必须和客户端的套接字domain和 type相同,才能建立连接。
addr :用于接受客户端套接字信息,如果不关心,可以为NULL
len :addr大小的传输传出参数
如果监听队列为空时,调用accept,会阻塞,如果使用非阻塞模式,会立即返回-1,errno被设置为 EAGAIN或EWOULDBLOCK.
可以使用 select poll 来处理阻塞的accpet。
3.数据发送/接受
套接字使用fd,所以可以使用read,write进行数据接受,发送。
如果想进行更多功能,可以使用 send,recv
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
使用send时,sockfd必须已经确定目标地址,即使用connect。
flags用于设置数据发送时的功能
MSG_DONTROUTE 勿将数据路由出本地网络 MSG_DONTWAIT 非阻塞 MSG_OOB 带外数据
如果send成功返回,并不代表对方已经接受到数据,只是说明数据成功发送到网络。
与 send 类似的 sendto
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *desaddr, socklen_t deslen);
sendto 允许在发送时指定目标地址
另一个发送api : sendmsg,允许指定多重缓冲区发送数据,与writev很相像。
recv和read相像,但可以指定flags
sszie_t recv(int sockfd, void *buf, size_t nbytes, int flags);
MSG_OOB 接受带外数据 MSG_PEEK 查看报文内容,但不取走报文 MSG_TRUNC 即使报文被截断,要求返回报文的实际长度 MSG_WAITALL 等待直到所有数据可用(仅SOCK_STREAM)
MSG_WAITALL:对于 SOCK_STREAM,接受的数据可能比请求的少,使用 MSG_WAITALL保证完整的接受数据。MSG_WAITALL对于 SOCK_DGRAM 和 SOCK_SEQPACKET 没任何作用,因为基于报文套接字一次读就返回整个报文。
当发送者调用shutdown结束传输,或发送端按照协议默认关闭,那么当数据接受完后,recv返回0。
如果要定位发送者,可以调用 recvfrom
recvfrom 通常用于无连接套接字, 否则 recvfrom 同 recv
recvmsg将接受数据放入多个缓冲区(类似readv)
4.套接字选项
套接字行为通过两个接口控制,一个设置选项,一个查看选项
int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
int getsockopt(int sockfd, int level, int option, const void *val, socklen_t *len);
TCP不允许一段时间内绑定同一地址,使用端口复用可以解决
int reuse = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
5.带外数据
一些通信协议可选特征,允许更高优先级的数据先传输,即使普通数据已经在发送缓冲区。
TCP支持该特征,支持一个字节的带外数据传输。UDP不支持。
原文地址:https://www.cnblogs.com/yangxinrui/p/11324489.html