网络编程之TCP和UDP

对于网络编程来说,我们至少要理解网络的四层结构【数据通信为 OSI 7层结构】:
应用层、传输层、IP层、链路层 
其中传输层主要就是TCP和UDP,因此传输层又称为TCP/UDP层  

1、链路层:物理连接   

2、IP层:数据传输的路径  

 IP层本身是面向消息的、不可靠的,每次传输都会重新选择路线, 

 如果传输中发生路径错误,则选择其他路径;无法解决数据丢失或错误 
   

3、TCP/UDP层: TCP,面向连接的可靠传输,UDP,无连接的不可靠传输  

  

4、应用层:根据程序特点决定服务器端和客户端之间的数据传输规则 

 

理解TCP和UDP        

网络编程的大部分内容就是设计并实现应用层协议   

1、TCP
TCP即Transmission Control Protocol(传输控制协议) 
TCP是面向连接的,TCP套接字又称为基于流(stream)的套接字  
TCP套接字具有I/O缓冲,输入输出都是从缓冲开始  
 
I/O缓冲特性如下  
1、I/O缓冲在每个TCP套接字中单独存在 
2、I/O缓冲在创建套接字时自动生成 
3、即使关闭套接字也会继续传递输出缓冲中遗留的数据 
4、关闭套接字将丢失输入缓冲中的数据

TCP特点:  

       1、传输过程中数据不会丢失、按序传输数据、传输的数据不存在数据边界 
           2、套接字连接必须一一对应  
           3、面向连接的套接字会根据接收端的状态传输数据    
           4、TCP会控制数据流,不会发生超过输入缓冲大小的数据传输 
           5、TCP中有滑动窗口协议,会动态控制输入缓冲 
           6、write函数和Windows的send函数不会在完成向对方主机的数据传输后返回,而是数据移动到输出缓冲时。 但TCP会保证对输出缓冲数据的传输, 因此write函数在数据传输完成时返回 
TCP服务器端默认函数调用顺序 
socket()创建套接字 
bind()分配套接字地址 
listen()等待连接请求状态 
accept()允许连接 
read()/write()数据交换 
close()断开连接  
 
TCP客户端默认函数调用顺序 
socket() connect() read()/write() close()  
 
TCP的生命有三步 
1、与对方套接字建立连接   
三次握手:
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认; 
        SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,再次发送SYN包(syn=j+1),向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据
2、与对方套接字进行数据交换 
3、断开与对方套接字的连接 
四次挥手 【 连接终止协议】
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。 
这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。 
收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。 
首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户端到服务器的数据传送。
(2) 服务器收到这个FIN,它发回一个ACK,此时客户端到服务器的连接 
     进入关闭等待状态 
     确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭服务器到客户端的连接,发送一个FIN给客户端。
(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。 此时服务器到客户端的连接进入关闭等待状态
四次挥手完成,连接关闭 
   
当然TCP套接字是可以半关闭的: 
断开一部分连接,只能接受或传输数据,关闭流的一半  
Linux的close函数和Windows的closesocket函数意味着完全断开连接 
 
针对优雅断开的shutdown函数 
#include  
int shutdown(int sock, int howto); 
成功返回0,失败返回-1 
sock,需要断开的套接字文件描述符 
howto,传递断开方式信息 
第二个参数决定断开连接的方式 
     SHUT_RD:断开输入流 
     SHUT_WR:断开输出流 
     SHUT_RDWR:同时断开 
      断开输入流【SHUT_RD】,即使输入缓冲收到数据也会抹去 
断开输出流【SHUT_WR】,如果输出缓冲还有未传输数据,将传递到目标主机 
传入SHUT_RDWR相当于分两次调用shutdown,一次参数是SHUT_RD,一次是SHUT_WR
 
服务器断开连接时会发送EOF表示文件传输结束,客户端收到EOF也会发送回复字符, 
服务器发送结束后半关闭流
 
下面介绍TCP常用的一个内容 
Nagle算法,用于TCP层,TCP套接字默认使用该算法 
只有收到前一个数据的ACK消息,Nagle算法才发送下一个数据 
为了提高网络传输效率,必须使用Nagel算法
网络流量未受太大影响时,不使用Nagle算法传输速度更快,如大文件传输,即使不使用Nagle算法,也是在装满输出缓冲时传输数据包 
不会增加数据包的数量,在不等待ACK的前提下连续传输,可提高效率 
 
禁用Nagel算法:将套接字可选项TCP_NODELAY改为1 
         int opt_val=1; 
         setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val,sizeof(opt_val)); 
     通过TCP_NODELAY的值可查看Nagle算法的设置状态 
     int opt_val; 
     socklen_t opt_len; 
     opt_len=sizeof(opt_val); 
     getsockopt(sock, IPPROTO_TCP, TCP_NODELY, (void *) &opt_val, &opt_len); 
     使用Nagel时,opt_val变量中保存0,否则为1  

2、UDP  

UDP和TCP都属于传输层的协议,某些情况下,UDP的速度比TCP更快,每次交换的数据量越大,TCP的速度越快,但无法超过UDP  

     UDP的服务器端和客户端没有连接 
     UDP中只有创建套接字和数据交换的过程 
     UDP服务器端和客户端都只需要一个套接字,一个UDP套接字能和多台主机通信  
     创建好TCP套接字之后,传输数据时无需添加地址信息,因为TCP套接字将与对方套接字保持连接 
     
有一点需要注意:UDP套接字每次传输数据都要添加目标地址信息 
UDP填写地址并传输数据的函数 
 #include  
 ssize_t sendto(int sock, void *buff, size_t nbytes, int flags, 
                struct sockaddr *to, socklen_t addrlen); 
 成功时返回传输的字符串,失败时返回-1; 
 sock  用于传输数据的UDP套接字文件描述符 
 buff  保存待传输数据的缓冲地址值 
 nbytes  待传输的数据长度,单位是字节 
 flags  可选项参数,没有则传递0 
 to  存有目标地址信息的sockaddr结构体变量的地址值 
 addrlen  传递给参数to的地址值结构体变量长度 
  
  
 接收UDP数据的函数【UDP数据的发送端并不固定】 
 #include  
 ssize_ recvfrom(int sock, void *buff, size_t nbytes, int flags, 
                struct sockaddr *from, socklen_t *addrlen);  
 成功时返回接收的字节数,失败时返回-1; 
 sock  用于接收数据的UDP套接字文件描述符 
 buff  保存接收数据的缓冲地址值 
 nbytes  可接收的最大字节数,故无法超过参数buff所指的缓冲大小  
 flags  可选项参数,没有则传递0 
 to  存有发送端地址信息的sockaddr结构体变量的地址值 
 addrlen  传递给参数from的结构体变量长度的变量地址值 
  
 UDP不存在请求连接和受理过程,在某种意义上无法明确区分服务器端和客户端。 
 
  
 UDP客户端套接字的地址分配
 调用sendto函数传输数据前应完成对套接字的地址分配工作,因此调用bind函数。 
 bind函数不区分TCP和UDP。 
 
 调用sendto函数时自动分配IP和端口号 
 
UDP是存在数据边界的套接字,所以UDP中输入函数的调用次数应和输出函数的调用次数完全一致  
TCP传数据就像传送带,可以几个一起发或者一次接受几个,而UDP就像接力跑,一次只能传输或接受一个。
 
UDP数据报,UDP中存在数据边界,1个数据包即可成为一个完整数据, 
因此称数据报,数据报也是数据包的一种 
 
TCP套接字中需注册待传输的数据的目标IP和端口号,UDP中无需注册 
通过sendto函数传输数据大致三个阶段 
1、向UDP套接字注册目标IP和端口号 
2、传输数据 
3、删除UDP套接字中注册的目标地址信息  
每次调用sendto函数重复上述过程,每次都变更目标地址,可重复利用同一套接字向不同目标传输数据 
未注册目标地址信息的套接字称为未连接套接字,反之,称为connect套接字 
UDP套接字默认属于未连接套接字 
可以创建已连接套接字,针对UDP套接字调用connect函数【只是向UDP套接字注册目标IP和端口信息】即可  
因为指定了收发对象,还可以使用write和read函数 

你可能感兴趣的:(网络编程)