一、Socket模型
套接字对:由客户端IP、Port和服务端IP、Port组成的四元组
(clientaddr:clientport, serveraddr: serverport)
TCP叫字节流套接字、UDP叫数据包套接字
Socket创建函数:
int socket(int domain, int type, int protocol)
函数参数说明:
- domain 就是指 PF_INET、PF_INET6 以及 PF_LOCAL 等,表示什么样的套接字。
- type 可用的值是:
SOCK_STREAM: 表示的是字节流,对应 TCP;
SOCK_DGRAM: 表示的是数据报,对应 UDP;
SOCK_RAW: 表示的是原始套接字。- 参数 protocol 原本是用来指定通信协议的,但现在基本废弃。
因为协议已经通过前面两个参数指定完成。protocol 目前一般写成 0 即可。
几种套接字地址格式
- AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,这种情况一般用于本地 socket 通信,很多情况下也可以写成 AF_UNIX、AF_FILE;
- AF_INET:因特网使用的 IPv4 地址;
- AF_INET6:因特网使用的 IPv6 地址。
二、TCP Socket实现
tcp-socket.png- 服务器端通过创建 socket,bind,listen 完成初始化,通过 accept 完成连接的建立。
- 客户端通过创建 socket,connect 发起连接建立请求。
TCP三次握手过程
tcp three-way handshake.png服务端
int socket(int domain, int type, int protocol)
bind(int fd, sockaddr * addr, socklen_t len)
int listen (int socketfd, int backlog)
int accept(int listensockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
客户端
int socket(int domain, int type, int protocol)
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)
TCP读写数据
发送数据时常用的有三个函数,分别是 write、send 和 sendmsg。
常见的文件写函数
ssize_t write (int socketfd, const void *buffer, size_t size)
可指定选项,发送带外数据(所谓带外数据,是一种基于 TCP 协议的紧急数据,用于客户端 - 服务器在特定场景下的紧急处理)
ssize_t send (int socketfd, const void *buffer, size_t size, int flags)
指定多重缓冲区传输数据
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
发送缓冲区的大小可以通过套接字选项来改变,当我们的应用程序调用 write 函数时,实际所做的事情是把数据从应用程序中拷贝到操作系统内核的发送缓冲区中,并不一定是把数据通过套接字写出去。
读取数据时常用函数有read
ssize_t read (int socketfd, void *buffer, size_t size)
read 函数要求操作系统内核从套接字描述字 socketfd读取最多多少个字节(size),并将结果存储到 buffer 中。返回值告诉我们实际读取的字节数目,也有一些特殊情况,如果返回值为 0,表示 EOF(end-of-file),这在网络中表示对端发送了 FIN 包,要处理断连的情况;如果返回值为 -1,表示出错。
未完全理解部分:
阻塞式套接字最终发送返回的实际写入字节数和请求字节数是相等的。
发送成功仅仅表示的是数据被拷贝到了发送缓冲区中,并不意味着连接对端已经收到所有的数据。至于什么时候发送到对端的接收缓冲区,或者更进一步说,什么时候被对方应用程序缓冲所接收,对我们而言完全都是透明的。
对于 send 来说,返回成功仅仅表示数据写到发送缓冲区成功,并不表示对端已经成功收到。
对于 read 来说,需要循环读取数据,并且需要考虑 EOF 等异常条件。
三、UDP Socket实现
udp-socket.png服务器端创建 UDP 套接字之后,绑定到本地端口,调用 recvfrom 函数等待客户端的报文发送;客户端创建套接字之后,调用 sendto 函数往目标地址和端口发送 UDP 报文,然后客户端和服务器端进入互相应答过程。
recvfrom 和 sendto 是 UDP 用来接收和发送报文的两个主要函数:
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);
四、本地套接字
本地文件路径标识说明:
1.本地文件路径必须是“绝对路径”;
2.这个本地文件,必须是一个“文件”,不能是一个“目录”。如果文件不存在,后面 bind 操作时会自动创建这个文件。
本地字节流套接字识别服务器不再通过 IP 地址和端口,而是通过本地文件。
- 本地套接字的编程接口和 IPv4、IPv6 套接字编程接口是一致的,可以支持字节流和数据报两种协议。
- 本地套接字的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报套接字实现
网友评论