TCP是Transmission Control Protocol(传输控制协议)简写。因为TCP套接字是面向连接的,因此又称为基于流的套接字。
把协议分为多个层次,设计更容易,通过标准化操作设计开放式系统
网络层介绍
链路层
链路层是物理连接领域标准化的结果,是最基本的领域,专门定义LAN,WAN,MAN等网络标准。
IP层
选择传输数据的路径。IP是面向消息的、不可靠的协议。每次传输数据会帮选择一条路径,但并不一致,若发送路径错误则选择其他路径;若发送数据丢失或错误则无法解决。IP协议无法应对数据错误。
TCP/UDP层
TCP和UDP层以IP层提供的路径信息为基础完成实际的数据传输,故该层又称为传输层。
IP层只关注一个数据包的传输过程。传输顺序以及传输本身不可靠,添加TCP可以在交换数据过程确认对方已经收到数据,并重传丢失数据,这类通信是可靠的。
应用层
数据传输路径、数据确认过程都被隐藏到套接字内部,只需利用套接字编出程序即可。编写软件过程中,需要根据程序特点决定服务器端和客户端之间的数据传输规定,这边是应用层协议。
实现基于TCP的服务器端/客户端
调用socket函数创建套接字,声明并初始化地址信息信息结构体变量,调用bind函数向套接字分配地址如上章所示。
通过调用listen函数进入等待连接请求状态,只有调用listen函数客户端才能进入可发出连接请求的状态,即调用connect函数。
#include<sys/socket.h>
int listen(int sock,int backlog);
//成功返回0,失败返回-1
参数:sock希望进入等待连接请求状态的套接字文件描述符,传递的描述符套接字成为服务器套接字(监听套接字)。backlog连接请求等待队列。
服务器端处于等待连接请求状态是指,客户端请求连接时,受理前一直使请求处于等待状态。
客户端如果向服务器端询问:“请问我是否可以发起连接?”服务器端套接字就会亲切应答:“您好!当然可以,但系统正忙,请到等候室排号等待,准备好后会立即受理您的连接。”同时将连接请求请到等候室。调用listen函数即可生成这种门卫(服务器端套接字),listen函数的第二个参数决定了等候室的大小。等候室称为连接请求等待队列,准备好服务器端套接字和连接请求等待队列后,这种可接收连接请求的状态称为等待连接请求状态。
受理客户端连接请求
#include<sys/socket.h>
int accept(int sock,struct sockaddr * addr, socklen_t * addrlen);
sock:服务器套接字的文件描述符。
addr:保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填
充客户端地址信息。
addrlen:第二个参数addr结构体的长度,但是存有长度的变量地址。函数调用完成后,该变量即被填入客户端地址长度。
TCP客户端得默认函数调用顺序
#include<sys/socket.h>
int connect(int sock,struct sockaddr * servaddr,socklen_t addrlen);
sock---------客户端套接字文件描述符。
servaddr----保存目标服务器端地址信息的变量地址值。
eaddrlen-----以字节为单位传递已传递给第二个结构体参数servaddr的地址变量长度。
函数返回情况:1.服务器端接收连接请求 2.发生断网等异常情况而中断连接请求
注意:接受连接并不意味着服务器端调用acccpet函数,是服务器将连接请求记录到等待队列。
客户端套接字地址信息
实现服务端必给套接字分配IP地址和端口号,但客户端实现过程未出现,而是创建套接字后立即调用connect函数。网络数据交换必须分配IP和端口号,怎么?
客户端分配地址:
- 何时:调用connect函数时
- 何地:操作系统,内核
- 如何:IP用计算机(主机)的IP,端口随机。
基于TCP的服务器端/客户端函数调用关系
流程:服务器端创建套接字后连续调用bind、listen函数进入等待状态,客户端通过调用connect函数发起连接请求。注意:服务器端调用listen函数才能调用connect函数。且客户端调用connect函数前,服务器端有可能率先调用accept函数。此时服务器在调用accept函数时进入阻塞状态,知道客户端调用connect函数为止。
实现迭代服务器端/客户端
服务器端将客户端传输的字符串数据原封不动地传回客户端。
实现迭代的服务器端
服务器端在同一时刻只与一个客户端相连,并提供回声服务。
服务器端依次向5个客户端提供服务并退出。
客户端接收用户输人的字符串并发送到服务器端。
服务器端将接收的字符串数据传回客户端,即“回声”服务器端与客户端之间的字符串回声一直执行到客户端输人Q为止。
存在问题:“每次调用read、write函数时都会以字符串为单位执行实际的/O操作。”
当然,每次调用write函数都会传递1个字符串,因此这种假设在某种程度上也算合理。“TCP不存在数据边界“。上述客户端是基于TCP的,因此,多次调用write函数传递的字符串有可能一次性传递到服务器端。此时客户端有可能从服务器端收到多个字符串,这不是我们希望看到的结果。还需考虑服务器端的如下情况:字符串太长,需要分2个数据包发送!在此过程中,客户端有可能在尚未收到全部数据包就调用read函数。
回声客户端传输的是字符串,且是通过调用write函数一次性发送的。之后还调用一次read函数,期待接收自己传输的字符串。问题所在。
解决方法,可以提前确定接收数据的大小。若之前传输了20字节长的字符串,则在接收时循环调用read函数读取20个字节。
若问题不在回声客服端:定义应用层协议
回声客户端可以提前知道接收的数据长度,但我们应该意识到,更多情况下这不太可能。既然如此,若无法预知接收数据长度时应如何收发数据?此时需要的就是应用层协议的定义。之前的回声服务器端/客户端中定义了如下协议。
“收到Q就立即终止连接。”
同样,收发数据过程中也需定好规则以表示数据的边界,或提前告知收发数据的大小。服务器端/客户端实现过程中逐步定义的这些规则集合就是应用层协议。