所谓 Socket,通常称为 “套接字”,网络应用程序通过套接字向网络发送请求或者应答网络请求。Socket 通常用于描述 IP 地址和端口,是应⽤层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口,是一个通信链的句柄,可以用来实现不同虚拟机或者不同计算机之间的通信。在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接⼝后面。
Socket 起源于 Unix,而 Unix/Linux 基本哲学之一就是 “一切皆文件”,都可以用 “打开 open –> 读写 write/read –> 关闭 close” 模式来操作。我的理解就是 Socket 就是该模式的一个实现,Socket 即是一种特殊的文件,一些 Socket 函数就是对其进行的操作(读/写 IO、打开、关闭)。
Socket 一词的起源。在组网领域的首次使用是在 1970 年 2 月 12 日发布的文献 IETF RFC33 中发现的,撰写者为 Stephen Carr、Steve Crocker 和 Vint Cerf。根据美国计算机历史博物馆的记载,Croker 写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比 BSD 的套接字接口定义早了大约 12 年。”
Socket 的英文原义是 “孔” 或 “插座”。作为 BSD UNIX 的进程通信机制,取后一种意思。通常也称作 "套接字",用于描述 IP 地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在 Internet 上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个 Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket 正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供 220 伏交流电,有的提供 110 伏交流电,有的则提供有线电视节目。客户软件将插头插到不同编号的插座,就可以得到不同的服务。
Socket 开始是纯 C 语言的,是跨平台的。
网络中进程之间如何通信
本地的进程间通信(IPC)有很多种方式,但可以总结为下面 4 类:
网络中进程之间如何通信,首要解决的问题是如何唯一标识一个进程,否则通信无从谈起。在本地可以通过进程 PID 来唯一标识一个进程,但是在网络中这是行不通的。其实 TCP/IP 协议族已经帮我们解决了这个问题,网络层的 “ip 地址” 可以唯一标识网络中的主机,而传输层的 “协议 + 端口” 可以唯一标识主机中的应用程序(进程)。这样利用三元组(协议,ip 地址,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
使用 TCP/IP 协议的应用程序通常采用应用编程接口:UNIX BSD 的套接字(socket)和 UNIX System V 的 TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在。
1、传输协议(通讯的规则):
1) TCP:传输控制协议:
2) UDP:用户数据报协议:
3) 常见网络协议:
应用层协议 | 端口 | 说明 |
---|---|---|
HTTP | 80 | 超文本传输协议 |
HTTPS | 443 | HTTP+SSL,HTTP 的安全版 |
FTP | 20, 21, 990 | 文件传输 |
POP3 | 110 | 邮局协议 |
SMTP | 25 | 简单邮件传输协议 |
telnet | 23 | 远程终端协议 |
2、IP 地址(主机名):
网络中设备的唯一标示。不易记忆,可以用主机名(域名)。
1) IP V4:
2) 本地回环地址:
每台机器都有自己的本地回环地址,IP 为 127.0.0.1 ,主机名为 localhost。如果 127.0.0.1 ping 不通,则网卡不正常。
本地 hosts 文件修改,终端:
$ cd /etc
$ sudo vim hosts
$ 输入密码进入 hosts 文件编辑界面
$ 将光标移动到指定位置
英文输入模式下按 i 键进入编辑状态,
英文输入模式下按 esc 键进入命令状态,
在命令状态下输入 :wq 回车,保存退出 hosts 文件。
3、端口号:
有效端口为 0 ~ 65535,其中 0 ~ 1024 由系统使用或者保留端口,开发中不要使用 1024 以下的端口。
1) Netcat 的使用:
$ nc -lk 12345
,开启监听,终端将始终监听本地计算机 12345 端口的数据。4、网络参考模型:
ISO 参考模型 | TCP/IP 参考 | 说明 |
---|---|---|
应用层 | 应用层 | FTP,HTTP,TELNET,SMTP,DNS 等协议 |
表示层 | ||
会话层 | ||
传输层 | 传输层 | Socket 开发,TCP 协议,UDP 协议 |
网络层 | 网络互连层 | 路由器,IP 协议,ICMP 协议,ARP 协议,RARP 协议和 BOOTP 协议 |
数据链路层 | 网络接口层 | 交换机 |
物理层 | 网线 |
1、TCP 通信
TCP 通信面向连接,可靠传输(保证数据正确性,顺序性),用于传输大量数据(流模式)、速度慢,建立连接开销比较大(时间,系统资源)。
流模式:在连接持续的过程中,基本上都是从同一个主机发出的,因此,需要保证数据是有序的到达。
三次握手(建立 TCP 连接,需要 C 和 S 发送三个包),四次挥手(TCP 连接的断开需要发送 4 个包)。
TCP 通信流程图
根据连接启动的方式以及本地套接字要连接的目标,Socket 之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
2、UDP 通信
3、TCP 的三次握手建立连接
4、TCP 的四次握手释放连接
iOS 中 Socekt 编程框架:
BSDSocket(纯 C):
一套 unix 系统下的 socket API。
iOS 系统基于 unix,所以支持底层的 BSD Socket,在 Xcode 中可以直接使用。
CFSocket(纯 C):
苹果对对底层 BSD Socket 进行轻量级的封装。
主要使用的 API:CFSocekt 用于建立连接,CFStream 用于读写数据。
CFNetwork(纯 C):
基于 OS 层 BSDSocket 封装,用于网络通信,早期的网络请求框架 ASIHTTPRequest 就是基于 CFNetwork 进行的封装。
主要使用的 API:CFSocket 用于底层的通信,CFStream 用于数据的读写。
CocoaAsyncSocket(OC):
目前比较常用。
基于 CFSocket、GCD 进行的封装。
支持 TCP 和 UDP。
完整的回调函数(用于处理各种回调事件,连接成功,断开连接,收到数据等)。
需要注意的问题:
[self.socket readDataWithTimeout:-1 tag:0];
,相当于主动添加一个读取请求,不然不会执行读取信息回调方法。[self.socket readDataWithTimeout:-1 tag:0];
,读取完信息后,重新向队列中添加一个读取请求,不然当收到信息后不会执行读取回调方法。WebSocket(OC):
适用于 web 应用的可持久连接的全双工通讯协议,被称为 “Web 的 TCP”,实现了浏览器和服务器的双向通信同样也适用于原生应用,协议本身使用 “ws://URL” 格式,是在标准 http 协议之上实现的,浏览器和服务器之间只需做一次握手操作后,就会创建一个快速通信通道。
解决问题:以前的服务器推送是通过浏览器轮询的方式进行,时间间隔太长:不实时,体验差,太短:消耗资源,服务器负载太大。
SocketRocket(OC):
SocketIO(OC):
BSDSocket
1) 创建:
函数原型:
int socket(int domain, int type, int protocol);
参数说明:
domain:协议域,又称协议族(family),协议族决定了 socket 的地址类型,在通信中必须采用对应的地址
常用的协议族:
AF_INET (ipv4):用 ipv4 地址(32 位的)与端口号(16 位的)的组合
AF_INET6(ipv6):用 ipv6 地址(128 位的)与端口号(16 位的)的组合
AF_LOCAL(或称 AF_UNIX,Unix 域 Socket):用一个绝对路径名作为地址
AF_ROUTE :
type:指定 Socket 类型
常用的 socket 类型:
SOCK_STREAM(流式/TCP) :是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用
SOCK_DGRAM (数据报式/UDP):是一种无连接的 Socket,对应于无连接的 UDP 服务应用
SOCK_RAW :
SOCK_PACKET :
SOCK_SEQPACKET :
protocol:指定协议
常用协议:
IPPROTO_TCP(TCP 传输协议) :
IPPROTO_UDP(UDP 传输协议) :
IPPROTO_STCP(STCP 传输协议):
IPPROTO_TIPC(TIPC 传输协议):
注意:1. type 和 protocol 不可以随意组合,如 SOCK_STREAM 不可以跟 IPPROTO_UDP 组合。当第三个参数为 0 时,
会自动选择第二个参数类型对应的默认协议。
2. Windows Socket 下 protocol 参数中不存在 IPPROTO_STCP。
返回值:
如果调用成功就返回新创建的套接字的描述符(大于 0),如果失败就返回 INVALID_SOCKET(Linux 下失败返回 -1)。
2) 绑定:
函数原型:
int bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
参数说明:
sockfd :套接字描述符
addr :是一个 sockaddr 结构指针,该结构中包含了要结合的地址和端口号
addrlen:addr 的长度
返回值:
如果函数执行成功,返回值为 0,否则为 -1。
当我们调用 socket() 创建一个 socket 时,返回的 socket 描述字它存在于协议族(address family,AF_XXX
)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用 bind() 函数,否则就当调用 connect()、listen() 时系统会自动随机分配一个端口。
通常服务器在启动的时候都会绑定一个众所周知的地址(如 ip 地址 + 端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的 ip 地址组合。这就是为什么通常服务器端在 listen 之前会调用 bind(),而客户端就不会调用,而是在 connect() 时由系统随机生成一个。
3) 监听:
函数原型:
int listen(int sockfd, int backlog);
参数说明:
sockfd :套接字描述符
backlog:socket 可以排队的最大连接个数
返回值:
如果函数执行成功,返回值为 0,否则为 -1。
4) 接受连接:
函数原型:
int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen);
参数说明:
sockfd :监听套接字描述符
addr :返回客户端的地址
addrlen:addr 的长度
返回值:
成功返回由内核自动生成的一个全新的描述符(即连接成功的客户端的套接字描述符),失败返回 -1。
TCP 服务器端依次调用 socket()、bind()、listen() 之后,就会监听指定的 socket 地址了。TCP 客户端依次调用 socket()、connect() 之后就向 TCP 服务器发送了一个连接请求。TCP 服务器监听到这个请求之后,就会调用 accept() 函数去接收请求,这样连接就建立好了。之后就可以开始网络 I/O 操作了,即类同于普通文件的读写 I/O 操作。
accept() 的第一个参数为服务器的 socket 描述字,是服务器开始调用 socket() 函数生成的,称为监听 socket 描述符;而 accept() 函数返回的是已连接的 socket 描述字。一个服务器通常通常仅仅只创建一个监听 socket 描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接 socket 描述符,当服务器完成了对某个客户的服务,相应的已连接 socket 描述符就被关闭。
5) 连接:
函数原型:
int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);
参数说明:
sockfd :套接字描述符
addr :指定数据发送的目的地,也就是服务器端的地址
addrlen:addr 的长度
返回值:
如果函数执行成功,返回值为 0,否则为 -1。
6) 断开连接:
函数原型:
int close(int sockfd);
参数说明:
sockfd:套接字描述符
返回值:
成功返回客户端的文件描述符,失败返回 -1。
close 一个 TCP socket 的缺省行为是把该 socket 标记为以关闭,然后立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为 read 或 write 的第一个参数。
注意:close 操作只是使相应 socket 描述字的引用计数 -1,只有当引用计数为 0 的时候,才会触发 TCP 客户端向服务器发送终止连接请求。
7) 发送数据:
函数原型:
ssize_t write(int sockfd, const void * buf, size_t size);
参数说明:
sockfd:套接字描述符
buf :发送内容地址,message.UTF8String 将字符串转换成 UTF8 的 ASCII 码,一个汉字需要 3 个字节
size :发送内容长度,是字节的个数,需使用 strlen() 计算所有字节的长度
返回值:
如果成功,则返回发送的字节数,失败则返回 -1,并设置 errno 变量。
如果错误为 EINTR 表示在写的时候出现了中断错误。如果为 EPIPE 表示网络连接出现了问题(对方已经关闭了连接)。
函数原型:
ssize_t send(int sockfd, const void * buf, size_t size, int flags);
参数说明:
sockfd:套接字描述符
buf :发送内容地址,message.UTF8String 将字符串转换成 UTF8 的 ASCII 码,一个汉字需要 3 个字节
size :发送内容长度,是字节的个数,需使用 strlen() 计算所有字节的长度
flags :发送方式标志,一般为 0
返回值:
如果成功,则返回发送的字节数,失败则返回 -1。
函数原型:
ssize_t sendto(int sockfd, const void * buf, size_t size, int flags, const struct sockaddr * dest_addr, socklen_t addrlen);
参数说明:
sockfd :套接字描述符
buf :待发送数据的缓冲区
size :缓冲区长度,是字节的个数,需使用 strlen() 计算所有字节的长度
flags :调用方式标志位, 一般为 0, 改变 Flags,将会改变 Sendto 发送的形式
dest_addr:可选指针,指向目的套接字的地址
addrlen :dest_addr 的长度
返回值:
如果成功,则返回发送的字节数,失败则返回 -1。
函数原型:
ssize_t sendmsg(int sockfd, const struct msghdr * msg, int flags);
参数说明:
sockfd:套接字描述符
msg :送数据的内容
flags :调用方式标志位
返回值:
如果成功,则返回发送的字节数,失败则返回 -1。
8) 接收数据:
函数原型:
ssize_t read(int sockfd, void * buf, size_t size);
参数说明:
sockfd:套接字描述符
buf :用于接收数据的缓冲区
size :缓冲区长度
返回值:
如果成功,返回实际所读的字节数,如果返回的值是 0 表示已经读到文件的结束了,小于 0 表示出现了错误。
如果错误为 EINTR 说明读是由中断引起的,如果是 ECONNREST 表示网络连接出了问题。
函数原型:
ssize_t recv(int sockfd, void * buf, size_t size, int flags);
参数说明:
sockfd:套接字描述符
buf :用于接收数据的缓冲区
size :缓冲区长度
flags :指定调用方式
取值:
MSG_PEEK:查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除;
MSG_OOB :指示接收到 out-of-band 数据(即需要优先处理的数据)。
返回值:
如果成功,返回实际所读的字节数,如果返回的值是 0 表示已经读到文件的结束了,小于 0 表示出现了错误。
函数原型:
ssize_t recvfrom(int sockfd, void * buf, size_t size, int flags, struct sockaddr * src_addr, socklen_t * addrlen);
参数说明:
sockfd :套接字描述符
buf :接收数据缓冲区
size :缓冲区长度
flags :调用操作方式。是以下一个或者多个标志的组合体,可通过 or 操作连在一起:
MSG_DONTWAIT:操作不会被阻塞
MSG_ERRQUEUE:指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,
使用者应该提供足够大的缓冲区。导致错误的原封包通过 msg_iovec 作为一般的数据来传递。导致错误
的数据报原目标地址作为 msg_name 被提供。错误以 sock_extended_err 结构形态被使用
MSG_PEEK :指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据
MSG_TRUNC :返回封包的实际长度,即使它比所提供的缓冲区更长, 只对 packet 套接字有效
MSG_WAITALL :要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被
接收的数据类型不同,仍会返回少于请求量的数据
MSG_EOR :指示记录的结束,返回的数据完成一个记录
MSG_CTRUNC :指明由于缓冲区空间不足,一些控制数据已被丢弃
MSG_OOB :指示接收到 out-of-band 数据(即需要优先处理的数据)
MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据
src_addr:可选指针,指向装有源地址的缓冲区
addrlen :可选指针,指向 address 缓冲区长度值
返回值:
如果成功,返回实际所读的字节数,如果返回的值是 0 表示已经读到文件的结束了,小于 0 表示出现了错误。
函数原型:
ssize_t recvmsg(int sockfd, struct msghdr * msg, int flags);
参数说明:
sockfd:套接字描述符
buf :用于接收数据的缓冲区
size :缓冲区长度
返回值:
如果成功,返回实际所读的字节数,如果返回的值是 0 表示已经读到文件的结束了,小于 0 表示出现了错误。
9) 获取本地协议地址:
函数原型:
int getsockname(int sockfd, struct sockaddr * addr, socklen_t * addrlen);
参数说明:
sockfd :套接字描述符
addr :返回本地协议的地址
addrlen:addr 的长度
返回值:
成功返回 0,失败返回 1。
10) 获取远程协议地址:
函数原型:
int getpeername(int sockfd, struct sockaddr * addr, socklen_t * addrlen);
参数说明:
sockfd :套接字描述符
addr :返回远程协议的地址
addrlen:addr 的长度
返回值:
成功返回 0,失败返回 1。
getsockname 和 getpeername 调度时机很重要,如果调用时机不对,则无法正确获得地址和端口。
TCP:
对于服务器来说,在 bind 以后就可以调用 getsockname 来获取本地地址和端口,虽然这没有什么太多的意义。getpeername 只有在连接建立以后才调用,否则不能正确获得对方地址和端口,所以他的参数描述字一般是链接描述字而非监听套接口描述字。
对于客户端来说,在调用 socket 时候内核还不会分配 IP 和端口,此时调用 getsockname 不会获得正确的端口和地址(链接没建立更不可能调用 getpeername),当然如果调用了 bind 以后可以使用 getsockname。想要正确得到对方地址(一般客户端不需要这个功能),则必须在连接建立以后,同样连接建立以后,此时客户端地址和端口就已经被指定,此时是调用 getsockname 的时机。
UDP
11) 获取套接字选项当前值:
函数原型:
int getsockopt(int sockfd, int level, int optname, void * optval, socklen_t * optlen);
参数说明:
sockfd :套接字描述符
level :选项定义的层次。例如,支持的层次有 SOL_SOCKET、IPPROTO_TCP。、
optname:需获取的套接字选项
optval :指向存放所获得选项值的缓冲区
optlen :指向 optval 缓冲区的长度值
返回值:
成功返回 0。
1、IPv4 地址用一个 32 位整数来表示:
struct sockaddr_in {
__uint8_t sin_len; // 地址长度
sa_family_t sin_family; // IP 地址协议族,必须设为 AF_INET
in_port_t sin_port; // 通信端口
struct in_addr sin_addr; // 以网络字节排序的 4 字节 IPv4 地址
char sin_zero[8]; // 填充项,是为了让 sockaddr 与 sockaddr_in 两个数据结构保持大小相同而保留的空字节
};
struct sockaddr {
__uint8_t sa_len; // 地址长度
sa_family_t sa_family; // IP 地址协议族,必须设为 AF_INET
char sa_data[14]; // 地址值
};
struct in_addr {
uint32_t s_addr; // 按照网络字节顺序存储 IP 地址
};
sockaddr_in 和 sockaddr 是并列的结构,指向 sockaddr_in 的结构体的指针也可以指向 sockaddr 的结构体,并代替它。
也就是说,你可以使用 sockaddr_in 建立你所需要的信息。
struct sockaddr_in address;
// 清空内存
memset(&address, 0, sizeof(address)); // 清空指向的内存中的存储内容,因为分配的内存是随机的,
// 如果不清空可能会因为垃圾数据产生不必要的麻烦
bzero(& address, sizeof(address)); // 清空指向的内存中的存储内容
// 设置地址值
ser_addr.sin_len = sizeof(address); // 地址长度
address.sin_family = AF_INET; // 协议族
address.sin_port = htons(12345); // 端口数据高位在前低位在后 12345 => 3039 => 3930
address.sin_addr.s_addr = inet_addr("192.168.88.100"); // inet_addr 函数可以把 ip 地址转换成一个整数
// 设置所有地址值
address.sin_addr.s_addr = INADDR_ANY; // 指定地址为 0.0.0.0 的地址,这个地址事实上表示不确定地址,
// 或所有地址、所有 ip、任意地址。一般来说,在各个系统中均定义成为 0 值
// 获取地址值
int ip_port = ntohs(address.sin_port); // 获取端口,如获取到:53746
char *ip_addr = inet_ntoa(address.sin_addr); // 获取 IP 地址,如获取到:192.168.88.100
// 获取本地地址
socklen_t addrLen = sizeof(address);
getsockname(self.clientSockfd, (struct sockaddr *)&address, &addrLen);
int ip_port = ntohs(address.sin_port); // 本地端口
char *ip_addr = inet_ntoa(address.sin_addr); // 本地 IP 地址
2、IPv6 地址用一个 128 位整数来表示:
struct sockaddr_in6 {
__uint8_t sin6_len; // length of this struct(sa_family_t) 地址长度
sa_family_t sin6_family; // AF_INET6 (sa_family_t) IP 地址协议族族,必须为 AF_INET6
in_port_t sin6_port; // Transport layer port # (in_port_t) 通信端口
__uint32_t sin6_flowinfo; // IP6 flow information 标记连接通信量
struct in6_addr sin6_addr; // IP6 address 以网络字节排序的 6 字节 IPv6 地址
__uint32_t sin6_scope_id; // scope zone index 地址所在的接口索引(范围 ID)
};
struct in6_addr {
union {
__uint8_t __u6_addr8[16];
__uint16_t __u6_addr16[8];
__uint32_t __u6_addr32[4];
} __u6_addr;
};
3、Unix 域对应的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; // AF_UNIX
char sun_path[UNIX_PATH_MAX]; // pathname
};
4、网络字节序与主机字节序:
主机字节序:就是我们平常说的大端和小端模式:不同的 CPU 有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的 Big-Endian 和 Little-Endian 的定义如下:
网络字节序:4 个字节的 32 bit 值以下面的次序传输:首先是 0~7bit,其次 8~15bit,然后 16~23bit,最后是 24~31bit,这种传输次序称作大端字节序。由于 TCP/IP 首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。
所以在将一个地址绑定到 socket 的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是 Big-Endian。由于这个问题曾引发过血案,公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给 socket。
1、TCP 是面向链接的,虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性,但 TCP 的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性;而 UDP 不是面向连接的,UDP 传送数据前并不与对方建立连接,对接收到的数据也不发送确认信号,发送端不知道数据是否会正确接收,当然也不用重发,所以说 UDP 是无连接的、不可靠的一种数据传输协议。
2、也正由于 1 所说的特点,使得 UDP 的开销更小数据传输速率更高,因为不必进行收发数据的确认,所以 UDP 的实时性更好。知道了 TCP 和 UDP 的区别,就不难理解为何采用 TCP 传输协议的 MSN 比采用 UDP 的 QQ 传输文件慢了,但并不能说 QQ 的通信是不安全的,因为程序员可以手动对 UDP 的数据收发进行验证,比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的,即使是这样,UDP 因为在底层协议的封装上没有采用类似 TCP 的 “三次握手” 而实现了 TCP 所无法达到的传输效率。
1、TCP/IP 连接
手机能够使用联网功能是因为手机底层实现了 TCP/IP 协议,可以使手机终端通过无线网络建立 TCP 连接。TCP 协议可以对上层网络提供接口,使上层网络数据的传输建立在 “无差别” 的网络之上。建立起一个 TCP 连接需要经过“三次握手”:
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。断开连接时服务器和客户端均可以主动发起断开 TCP 连接的请求,断开过程需要经过 “四次握手”,服务器和客户端交互,最终确定断开。
2、HTTP 连接
HTTP 协议即超文本传送协议(Hypertext Transfer Protocol),是 Web 联网的基础,也是手机联网常用的协议之一,HTTP 协议是建立在 TCP 协议之上的一种应用。
HTTP 连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为 “一次连接”。
3、Socket 连接
套接字是通信的基石,是支持 TCP/IP 协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的 IP 地址,本地进程的协议端口,远地主机的 IP 地址,远地进程的协议端口。
建立 Socket 连接至少需要一对套接字,其中一个运行于客户端,称为 ClientSocket ,另一个运行于服务器端,称为 ServerSocket。
4、Socket 连接与 TCP/IP 连接
5、Socket 连接与 HTTP 连接
6、Socket 与 http 协议
一个是发动机(Socket),提供了网络通信的能力 ,一个是轿车(Http),提供了具体的方式。两个计算机之间的交流无非是两个端口之间的数据通信,具体的数据会以什么样的形式展现是以不同的应用层协议来定义的。如 HTTP、FTP、...
http 协议是应用层的协义。
Socket 是对端口通信开发的工具,它要更底层一些。
7、网络请求方式 get 和 post
8、TCP/IP、Http、Socket 的区别