1.socket文件:
(1)socket是实现网络主机进程间通讯的一种机制;
(2)从用户空间来看,它就是一个文件描述符,在使用socket函数创建一个socket对象后返回这个对象的文件描述符,在建立起双方的连接之后,就可以使用read write close函数来操作;
(3)从内核空间来看,socket不是一个物理上真实存在的磁盘文件,而是一个内核内存空间中的 struct sk_buff结构体对象,这个对象中描述着通信双方的基本信息,缓冲的文件等。而相应的对其读写操作是通过对网卡驱动程序提供的数据发送和数据接收函数完成的。
2.服务端的建立:
(1)建立socket对象:extern int socket(int _domain,int _type,int _proctocol)
第一个参数:地址簇或协议簇,一般设置为AF_INET,ip协议簇;第二个参数:socket的协议类型,包括tcp:SOCK_STREAM 和udp:SOCK_DGREAM;第三个参数:标识采用协议簇中的那个协议,一般设置为0,自动设置默认协议。成功返回文件描述符,错误返回-1。
(2)绑定服务器的本机ip和端口:extern int bind(int _fd, _CONST_SOCKADDR_ARG _addr,socklea_t _len)
第一个参数:建立的socket对象文件描述符;第二个参数:服务器本机相关的ip信息、端口、使用的网络协议等地址信息;第三个参数:传入的地址信息结构体的长度;成功返回0,错误返回-1。
③对网络进行监听:extern int listen(int _fd, int _n),
第一个参数:建立的socket对象文件描述符;第二个参数:请求队列的最大长度,当多个客户端并发访问服务端时,此值表示可以使用的处于等待的队列长度。成功返回0,错误返回-1; Listen函数将socket文件描述符变成监听套接字,到此服务端已经做好了一切的准备,等待客户端的连接connect就可以监听成功; 成功返回0,失败返回-1。
④监听到客户端的请求后接受客户端的请求:extern int accept(int _fd, _CONST_SOCKETADDR_ARG _addr, socketlen_t _len)
第一个参数:上一步中监听网络后的由socket文件描述符变成的监听套接字;第三个参数:定义一个新的结构体,用来存放客户端的地址信息,用来标识不同客户端的连接;如果没有listen监听到客户端的连接请求,则accept会一直阻塞住;函数在使用前需要定义一个新的文件描述符,accept函数在成功执行后返回的这个文件描述符将以此标识这个客户端与服务端之间的连接。
3.客户端的连接
①建立socket对象:extern int socket(int _domain,int _type,int _proctocol)
第一个参数:地址簇或协议簇,一般设置为AF_INET,ip协议簇;第二个参数:socket的协议类型,包括tcp:SOCK_STREAM 和udp:SOCK_DGREAM;第三个参数:标识采用协议簇中的那个协议,一般设置为0,自动设置默认协议。成功返回文件描述符,错误返回-1。
②客户端主动发起连接:extern int connect(int _fd, _CONST_SOCKADDR_ARG _addr, socklen_t _len)
第一个参数:第一步创建的socket文件描述符;三个参数:用来存放待连接的服务端的地址信息,用于与指定地址信息的服务端发起连接;成功返回0;失败返回-1。
4.数据间的通讯:
Socket编程提供的函数:
①数据的发送: extern ssize_t send(int _fd, void * _buf, size_t _n, int _flags)
②数据的接收:extern ssize_t recv(int _fd, void * _buf, size_t _n, int _flags)
文件操作提供的函数:
由于socket文件也是一种文件,只不过他与普通意义的文件不太相同(对其读写操作是通过对网卡驱动程序提供的数据发送和数据接收函数完成的),但终归还是可以使用文件操作的函数。
发送:相当于对socket文件的写入 write;
接收:相当于对socket文件的读出:read。
1.Socket与三次握手:
(1)前提条件:客户端建立完socket对象,然后根据程序的设定,在合适的时机主动发起连接; 服务端建立完socket对象,并使用bind函数绑定了服务端的本机地址信息,最后使用listen函数持续的监听,等待客户端的连接;
(2)第一次握手:客户端主动打开连接请求:客户端使用connect()函数向服务端发送请求报文,报文中SYN=1,同时选择一个序列号seq=x,然后connect函数阻塞等待服务端的握手反馈。 而服务端监听到客户端的连接请求,即接收到了服务端发送的请求报文;
(3)第二次握手:服务端被动打开,接收请求:服务端调用accept函数发送请求确认报文,报文中SYN=1 ACK=1,ack=x+1(对客户端的请求序列号+1)seq=y(服务端的请求序列号),然后accept函数阻塞等待第三次握手。 而客户端收到服务端给自己连接请求确认,connect函数返回成功的标志0;
(4)三次握手:客户端发送确认:客户端对服务端发送的连接请求确认报文再次做一次确认,发送报文,报文中ACK=1,seq=x+1;ack=y+1。 而服务端收到客户端发送的确认报文,accept函数返回正确标志0,完成三次握手,建立起通讯。
总结:客户端的connect在三次握手的第二次返回,而服务器端的accept在三次握手的第三次返回。
2.Tcp三次握手的原因:
(1)为什么第三次A还要发送一次确认呢?
原因:为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误:
错误情况:客户端发出请求连接,但因连接请求报文丢失则不得已重发一次连接请求,在成功建立连接,完成传输任务之后释放了连接,而后发现客户端的连接请求并未丢失,而是由于网络的延迟,在释放后到达服务端欲与服务端建立连接。
(2)防止服务端主动向客户端发起连接而后一直等待接收的现象。
如果客户端未主动向服务端发起连接,则不会对服务端的数据做出响应。假设服务端主动发送数据给客户端而后就移植等到接受数据,这样会造成很多的资源浪费。
3.Socket与四次挥手:
(1)第一次挥手:客户端释放连接,不再发送数据:客户端完成数据传输后,主动的close关闭客户端打开的socket文件,主动向服务器发送一个终止报文,报文中FIN=1,seq=u,这也是客户端最后发送的数据。 而服务端接收到终止包之后被动的关闭,然后read读取之前建立的socket文件返回0,表示这包报文数据之后再无数据能够接收;
(2)第二次挥手:服务端通知主机进程,并发送确认: 服务端将上一步read读取的结果传递给应用程序,通知应用进程,并发送确认报文,报文中ACK=1,seq=v,ack=u+1。 到这里客户端->服务端的完全连接释放,但是服务端仍然可以向客户端发送数据,连接处于半关闭状态;
(3)第三次挥手:若服务端不需要再向客户端发送数据,则应用进程通知释放连接:服务端被动的close关闭服务端的socket文件,并发送报文,报文中FIN=1,ACK=1,seq=v,ack=u+1,至此服务端不再向客户端发送数据。
(4)第四次挥手:客户端对释放连接做出最后的确认:客户端收到服务端最后发出的释放报文,发送确认请求报文,报文中ack=1,seq=u+1,ack=v+1,到此服务端->客户端的连接释放,双方的连接完成释放。
4.Tcp四次挥手的原因:
(1)保证客户端最后发送的ACK确认报文段能够到达服务端。
如果确认报文可能丢失,则服务端回启动超时重传,然后客户端再次确认,同时启动2MSL的计时器等待,如果在这段时间结束还未收到重传通知则意味着服务端收到确认报文并已经完成关闭,则客户端正常的close,双方均close完成,连接才完全释放;
(2)防止已失效的连接请求报文出现在连接中。
客户端经过2MSL的时间等待,那些在这个连接持续时间内产生的所有报文段都会从网络中消失。即在这个连接释放的过程中会有一些无效的报文段滞留在楼阁结点,但经过2MSL这些无效报文段就能够发送到目的地,不再滞留在网络中。这样的话,在下一个连接中就不会出现上一个连接遗留下来的请求报文段了。