socket学习1
总结学习socket的知识点
什么是Socket
socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
简单来说,socket 是对底层网络通信的一层抽象,让程序员可以像文件那样操作网络上发送和接收的数据。
再说的详细一点,假设现在你要编程网络程序,进行服务器端和客户端的通信(数据交换)。不适用 socket 的话,你会做下面的一堆事情:
- 管理缓存区来接收和发送数据
- 告诉操作系统自己要监听某个端口的数据,还有自己处理这些系统调用来读取数据
- 当没有连接的时候或者另外一端要还没有发送数据时候,要处理 IO 阻塞,把自己挂起
- 封装和解析 tcp/ip 协议的数据,维护数据发送的顺序
- 等等
做了一大堆东西,发现最重要的还没有做:发送/接收数据。如果有一个程序能够自动帮我们把上面的东西都做掉,这样我们就可以只关心数据的读写,编程就简单的多了。那么这样一个程序就是 socket,它现在已经是操作系统的一部分,在 linux 中是标准的系统调用,只要调用它提供的一组接口(下面会详解常用函数的使用),就能轻松地建立连接,读写数据,关闭连接,让网络操作就像文件操作一样简单。
首先我们知道在Linux中一切皆是文件个概念,所以网络连接也是文件,其和普通的文件一样也有文件描述符。
我们可以通过 socket() 函数来创建一个网络连接,或者说打开一个网络文件,socket() 的返回值就是文件描述符。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如:
- 用 read() 读取从远程计算机传来的数据;
- 用 write() 向远程计算机写入数据。
TCP 和 UDP 的端口是互不干扰的,也就是说系统可以同时开启 TCP 80 端口和 UDP 80 端口。
socket 不属于任何一层网络协议,它是对 TCP 层的封装,方便网络编程。
现实生活中,两个人要邮寄信件,必须知道对方的地址。网通信也是如此,只不过这里通信的是程序。程序的地址由三元组(ip 地址,端口,协议)界定。
如果你了解网络协议模型的话,你就会知道,ip 地址是网络层用来路由和通信的标识符,端口(port) 是传输层管理的。而 socket 是在这两层之上,所以需要这两个地址来标识。这里的协议指的是 ipv4,ipv6 或者其他协议。
Socket的种类
Socket有许多种类包括:DARPA Internet 地址(Internet 套接字)、本地节点的路径名(Unix套接字)、CCITT X.25地址(X.25 套接字)
创建 socket 的时候需要指定 socket 的类型,一般有三种:
- SOCK_STREAM:面向连接的稳定通信,底层是 TCP 协议,我们会一直使用这个。
- SOCK_DGRAM:无连接的通信,底层是 UDP 协议,需要上层的协议来保证可靠性。
- SOCK_RAW:更加灵活的数据控制,能让你指定 IP 头部
套接字和TCP/IP协议
实际上,套接字是一种包装。套接字是对 TCP/IP 协议的一种抽象,它屏蔽了底层网络通信的复杂性,使得程序员可以更加简单地进行网络通信。所以,我们还需要知道 socket 与 TCP/IP 协议之间的关系。
三次握手建立连接
我们知道,TCP 协议是建立可靠连接的协议,所以在通信之前,需要先建立连接。建立连接的过程称为三次握手(Three-way Handshake)。
- 客户端向服务器发送一个SYN J
- 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
- 客户端再想服务器发一个确认ACK K+1
那么,TCP 协议和 socket 之间有什么关系呢?
从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。
总结就是:总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。
四次挥手释放连接
图示过程如下:
- 某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;
- 另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;
- 一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;
- 接收到这个FIN的源发送端TCP对它进行确认。
这样每个方向上都有一个FIN和ACK。
Internet套接字
主要的传输方式
https://c.biancheng.net/view/2124.html
流格式套接字:SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。
数据报套接字:数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,在代码中使用 SOCK_DGRAM 表示。
计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。
流格式套接字的特点:
- 发送、接受不是同步的
- 具有自我修复功能
- 按照顺序传输
这个流格式套接字就像传送带一样。
Socket编程中的常用函数
Socket中常用函数之间的关系
Socket()
这个函数用来创建一个套接字,函数原型如下:
1 |
|
为什么需要第三个参数:一般情况下有了 af 和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。如果我们不指明使用哪种协议,操作系统是没办法自动推演的。
https://c.biancheng.net/view/2344.html
bind()函数和connect()函数
bind()
这个函数用来将Socket和相应的ip地址、接口绑定起来。
函数原型:
1 |
|
sockaddr_in结构体成员:
1 |
|
sockaddr结构体与sockaddr_in结构体:
为什么要将sockaddr
结构体强制转换为sockadr_in
结构体:
sockaddr 和 sockaddr_in
的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data
表示。要想给 sa_data
赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in
来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。
sockaddr
是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in
是专门用来保存 IPv4 地址的结构体。另外还有 sockaddr_in6
,用来保存 IPv6 地址。
connect()函数
函数原型:
1 |
|
listen()函数
开始监听指定的套接字
函数原型:
1 |
|
使用示例:
1 |
|
accept()函数
网络编程的核心一步就是建立客户端和服务器端的连接,使用 accept 来建立 2 者的连接:
函数原型:
1 |
|
使用示例:
1 |
|
send()函数和sendto()函数
send()函数
使用 send 函数发送数据,不同于使用 write 函数。send 函数可以进行 flag 的指定,使之控制更加详细。
函数原型:
1 |
|
使用示例:
1 |
|
recv()函数和recvfrom()函数
既然有发送数据,必然有接收数据的函数,与 send 类似,recv 的功能也跟 read 几乎相同:
1 |
|
使用示例:
1 |
|
参考资料
- https://beej-zhcn.netdpi.net/system_call/sendto_yu_recvfrom__lai_dian_dgram
- https://www.csd.uoc.gr/~hy556/material/tutorials/cs556-3rd-tutorial.pdf
- https://hit-alibaba.github.io/interview/basic/network/IP.html
- https://dlonng.com/posts/network
- https://cizixs.com/2015/03/29/basic-socket-programming/#%E7%BB%91%E5%AE%9A%EF%BC%88bind%EF%BC%89%E5%9C%B0%E5%9D%80%E4%B8%89%E5%85%83%E7%BB%84%E5%88%B0-socket