医院网站制作设计,企业邮箱下载安装,阿里云邮箱企业邮箱,有名网站建设公司在传输层有两个主流的协议 TCP 和 UDP#xff0c;socket 程序设计也是主要操作这两个协议。这两个协议的区别是什么呢#xff1f;通常的答案是下面这样的。
TCP 是面向连接的#xff0c;UDP 是面向无连接的。TCP 提供可靠交付#xff0c;无差错、不丢失、不重复、并且按序…在传输层有两个主流的协议 TCP 和 UDPsocket 程序设计也是主要操作这两个协议。这两个协议的区别是什么呢通常的答案是下面这样的。
TCP 是面向连接的UDP 是面向无连接的。TCP 提供可靠交付无差错、不丢失、不重复、并且按序到达UDP 不提供可靠交付不保证不丢失不保证按顺序到达。TCP 是面向字节流的发送时发的是一个流没头没尾UDP 是面向数据报的一个一个地发送。TCP 是可以提供流量控制和拥塞控制的既防止对端被压垮也防止网络被压垮。
从本质上来讲所谓的建立连接其实是为了在客户端和服务端维护连接而建立一定的数据结构来维护双方交互的状态并用这样的数据结构来保证面向连接的特性。TCP 无法左右中间的任何通路也没有什么虚拟的连接中间的通路根本意识不到两端使用了 TCP 还是 UDP。
所谓的连接就是两端数据结构状态的协同两边的状态能够对得上。符合 TCP 协议的规则就认为连接存在两面状态对不上连接就算断了。
所谓的可靠也是两端的数据结构做的事情。不丢失其实是数据结构在“点名”顺序到达其实是数据结构在“排序”面向数据流其实是数据结构将零散的包按照顺序捏成一个流发给应用层。总而言之“连接”两个字让人误以为功夫在通路其实功夫在两端。
socket 函数用于创建一个 socket 的文件描述符唯一标识一个 socket。我们把它叫作文件描述符因为在内核中我们会创建类似文件系统的数据结构并且后续的操作都有用到它。
socket 函数有三个参数。
domain表示使用什么 IP 层协议。AF_INET 表示 IPv4AF_INET6 表示 IPv6。type表示 socket 类型。SOCK_STREAM顾名思义就是 TCP 面向流的SOCK_DGRAM 就是 UDP 面向数据报的SOCK_RAW 可以直接操作 IP 层或者非 TCP 和 UDP 的协议。例如 ICMP。protocol 表示的协议包括 IPPROTO_TCP、IPPTOTO_UDP。
通信结束后我们还要像关闭文件一样关闭 socket。
针对 TCP我们应该如何编程。 TCP 的服务端要先监听一个端口一般是先调用 bind 函数给这个 socket 赋予一个端口和 IP 地址。
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);struct sockaddr_in {__kernel_sa_family_t sin_family; /* Address family */__be16 sin_port; /* Port number */struct in_addr sin_addr; /* Internet address *//* Pad to size of struct sockaddr. */unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];
};struct in_addr {__be32 s_addr;
};
其中sockfd 是上面我们创建的 socket 文件描述符。在 sockaddr_in 结构中sin_family 设置为 AF_INET表示 IPv4sin_port 是端口号sin_addr 是 IP 地址。
服务端所在的服务器可能有多个网卡、多个地址可以选择监听在一个地址也可以监听 0.0.0.0 表示所有的地址都监听。服务端一般要监听在一个众所周知的端口上例如Nginx 一般是 80Tomcat 一般是 8080。
如果你看上面代码中的数据结构里面的变量名称都有“be”两个字母代表的意思是“big-endian”。如果在网络上传输超过 1 Byte 的类型就要区分大端Big Endian和小端Little Endian。
最低位放在最后一个位置我们叫作小端最低位放在第一个位置叫作大端。TCP/IP 栈是按照大端来设计的而 x86 机器多按照小端来设计因而发出去时需要做一个转换。
接下来就要建立 TCP 的连接了也就是著名的三次握手其实就是将客户端和服务端的状态通过三次网络交互达到初始状态是协同的状态。下图就是三次握手的序列图以及对应的状态转换。 接下来服务端要调用 listen 进入 LISTEN 状态等待客户端进行连接。
int listen(int sockfd, int backlog);
连接的建立过程也即三次握手是 TCP 层的动作是在内核完成的应用层不需要参与。
接着服务端只需要调用 accept等待内核完成了至少一个连接的建立才返回。如果没有一个连接完成了三次握手accept 就一直等待如果有多个客户端发起连接并且在内核里面完成了多个三次握手建立了多个连接这些连接会被放在一个队列里面。accept 会从队列里面取出一个来进行处理。如果想进一步处理其他连接需要调用多次 accept所以 accept 往往在一个循环里面。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接下来客户端可以通过 connect 函数发起连接。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
我们先在参数中指明要连接的 IP 地址和端口号然后发起三次握手。内核会给客户端分配一个临时的端口。一旦握手成功服务端的 accept 就会返回另一个 socket。
这里需要注意的是监听的 socket 和真正用来传送数据的 socket是两个 socket一个叫作监听 socket一个叫作已连接 socket。成功连接建立之后双方开始通过 read 和 write 函数来读写数据就像往一个文件流里面写东西一样。
针对 UDP 应该如何编程。 UDP 是没有连接的所以不需要三次握手也就不需要调用 listen 和 connect但是 UDP 的交互仍然需要 IP 地址和端口号因而也需要 bind。
对于 UDP 来讲没有所谓的连接维护也没有所谓的连接的发起方和接收方甚至都不存在客户端和服务端的概念大家就都是客户端也同时都是服务端。只要有一个 socket多台机器就可以任意通信不存在哪两台机器是属于一个连接的概念。因此每一个 UDP 的 socket 都需要 bind。每次通信时调用 sendto 和 recvfrom都要传入 IP 地址和端口。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
TCP 协议的 socket 调用的过程
服务端和客户端都调用 socket得到文件描述符服务端调用 listen进行监听服务端调用 accept等待客户端连接客户端调用 connect连接服务端服务端 accept 返回用于传输的 socket 的文件描述符客户端调用 write 写入数据服务端调用 read 读取数据。 此文章为11月Day23学习笔记内容来源于极客时间《趣谈Linux操作系统》推荐该课程。