UDP和TCP协议及代码实例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HaloTrriger/article/details/80945034

一、UDP

1.UDP

  User Datagram Protocol 用户数据报协议,是OSI(Open System Interconnection开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
  UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。
  我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。
  这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效果高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。

2. UDP特点:

无连接:知道对方IP和端口号就可以直接连接进行传输,无需connect函数进行连接
不可靠:没有确认机制,没有重传机制;消息丢失对方也不会知道。
面向数据报:不能够灵活的控制读写数据的次数和数量。对于上层交付下的数据既不切分,也不合并,一次发送一个完整的报文。

3. 报文内容

这里写图片描述

4. UDP的缓冲区:

没有发送缓冲区,上层交付数据会调用sendto函数直接交给内核,由内核将数据进行网络协议传输。
有接收缓冲区,但不能保证报文的顺序性和一致性。如果缓冲区满了,再到达的数据就会被丢弃。

5. 简单的UDP程序(基于Linux环境)

说明:使用套接字基于UDP协议,服务端先bind一个ip和port,然后进行收消息并把消息返还给客户端。而客户端无需bind连接服务器,只需知道它的ip和port就可以给它发消息。
使用:main函数使用可变参数列表,接收程序名,IP和端口号。
执行步骤:
1. 服务器,执行服务端程序,IP地址(自己拥有的IP,ifconfig查看),端口号。
2. 客户端,执行客户端程序,IP地址,端口号(和服务端的IP和端口号一致)
3. 客户端发送消息,服务端接收显示并返回给客户端,客户端显示自己发送的内容。

client.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        perror("argc\n");
        return 1;
    }
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        perror("socket\n");
        return 2;
    }

    char buf[128];
    struct sockaddr_in server;
    server.sin_family=AF_INET;
    server.sin_port=htons(atoi(argv[2]));
    server.sin_addr.s_addr=inet_addr(argv[1]);
    while(1)
    {
        ssize_t r=read(0,buf,sizeof(buf));  
        if(r<0)
        {
            printf("read error\n");
            continue;
        }
        buf[r-1]='\0';

        sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));
        ssize_t recv=recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
        buf[recv]=0;
        printf("server > %s\n",buf);
    }
    close(sockfd);//use signal to delet
    return 0;
}      
server.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        perror("argc\n");
        return 1;
    }
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        perror("socket\n");
        return 2;
    }

    char buf[128];
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(atoi(argv[2]));
    local.sin_addr.s_addr=inet_addr(argv[1]);

    if(bind(sockfd,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        return 2;
    }

    struct sockaddr_in client;

    while(1)
    {
        socklen_t len=sizeof(client);
        size_t recv=recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&client,&len);
        if(recv<0)
        {
            printf("recv error\n");
            return 4;
        }
        buf[recv]='\0';

        printf("[%s,%d] > %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
        sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client));
    }
    close(sockfd);//use signal to delet
    return 0;
}      

6. 演示效果如图(第一张服务端,第二张客户端):

这里写图片描述
这里写图片描述

二、TCP

1. TCP

  Transmission Control Protocol 传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
  在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来。
  我们来看看这三次对话的简单过程:主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

2. TCP特点

面向连接:双方必须先建立连接才能进行数据的读写,双方都必须为该链接分配必要的内核资源,以管理连接的状态和连接上的传输。
可靠传输:采用多种应答机制保证数据的可靠性
基于字节流:发送端执行多次写操作时,TCP模块必须先把这些数据放入TCP发送缓冲区中,当TCP模块真正发送数据时,才把TCP发送缓冲区等待发送的数据封装成一个或多个TCP报文段发出

3. 报文格式

这里写图片描述

4. 图中说明:

1.源端口号和目的端口号就不解释了。
2.序号和确认序号:因为TCP将每个字节的数据都进行了编号。发送方把数据编号发送后,接收方确认都收到后会发送一个确认序号给发送方,保证了数据的可靠性。
3.首部长度:就是指图中前20字节的长度。
4.保留:现在还没确定把这6位做什么用。
5.6位标志位:

URG:紧急指针是否有效
ACK:接收号是否有效
PSH:提示接收端立刻从TCP缓冲区把数据读走
RST:对方要求重新建立连接;携带RST标识称为复位报文段
SYN:请求建立连接;携带SYN标识称为同步报文段
FIN:通知对方,本端要关闭了;携带FIN标识的为结束报文段

6.16为窗口大小:填充自己接受缓冲区剩余空间
7.16位检验和:检验传输过程中可能产生的错误,接收端用同样算法检查,正确则接受;错误则丢弃
8.16为紧急指针:标识那部分是紧急数据
9.选项:包含了选项表结束、空操作、最大报文段长度、窗口扩大因子、选择确认、SACK实际工作、时间戳选项。

5. TCP为了确保可靠性做了哪些工作?

  1. 校验和:是指传输位数的累加,当传输结束时,接收者可以根据这个数值判断是否接到了所有的数据。如果数值匹配,那么说明传送已经完成。
  2. 序列号:发送方将数据进行排序然后发送
  3. 确认应答:响应方完整接收到消息后返回给对方一个确认号(下一个数据的编号)
  4. 连接管理:三次握手和四次挥手
  5. 超时重传:当数据丢包,发送方在一定时间内没有收到确认号就将该内容重新发送
  6. 流量控制:根据接收方接受能力的大小决定发送多少数据。
  7. 拥塞控制:使用拥塞窗口,表示发送数据的大小。
         拥塞窗口初始为1,每收到一个ACK,拥塞窗口加1。
         这样,第一次发一个,收到一个确认后拥塞窗口变为2。
         然后拥塞窗口依次变为8,16……呈指数增长。
         当拥塞窗口大小超过阈值(阈值为窗口的最大值)时采用线性增长。且一旦产生超时重传,就将阈值减半。

6. TCP为了保障性能做了哪些工作?

  1. 滑动窗口:接收方还有多大的缓冲区可以用于接收数据
  2. 快速重传:当数据丢包,发送方收到接收方三次相同确认序号,就将该内容重新发送
  3. 延迟应答:接收到N个包或一定时间才给对方确认
  4. 捎带应答:将确认报文粘在下一个要发送的报文上

7. 简单的TCP程序(Linux环境下,多线程)

说明:基于套接字的TCP协议协议编程。不同于UDP的是,客户端需和服务端建立连接,才能相互进行收发消息。使用线程处理多个用户的请求,使其收发数据。

client.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main(int argc,char *argv[])
{
        if(argc!=3)
        {
                perror("argc\n");
                return 1;
        }
        int sockfd=socket(AF_INET,SOCK_STREAM,0);
        if(sockfd<0)
        {
                perror("socket\n");
                return 2;
        }

        char buf[128];
        struct sockaddr_in server;
        server.sin_family=AF_INET;
        server.sin_port=htons(atoi(argv[2]));
        server.sin_addr.s_addr=inet_addr(argv[1]);
        if(connect(sockfd,(struct sockaddr*)&server,sizeof(server))<0)
        {
                perror("connect\n");
                return 3;
        }

        printf("connetc success\n");

        while(1)
        {
                memset(buf,0,sizeof(buf));
                ssize_t r=read(0,buf,sizeof(buf));  
                if(r<0)
                {
                        printf("read error\n");
                        continue;
                }
                buf[r-1]=0;
                if(send(sockfd,buf,strlen(buf),0)==0)
                        continue;
                memset(buf,0,sizeof(buf));
                ssize_t rec=recv(sockfd,buf,sizeof(buf),0);
                printf("server > %s\n",buf);
        }
        close(sockfd);//use signal to delet
        return 0;
}      

server.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/types.h>

int main(int argc,char *argv[])
{
        if(argc!=3)
        {
                perror("argc\n");
                return 1;
        }
        int sockfd=socket(AF_INET,SOCK_STREAM,0);
        if(sockfd<0)
        {
                perror("socket\n");
                return 2;
        }

        char buf[128];
        struct sockaddr_in local;
        local.sin_family=AF_INET;
        local.sin_port=htons(atoi(argv[2]));
        local.sin_addr.s_addr=inet_addr(argv[1]);

        if(bind(sockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
                perror("bind");
                return 2;
        }

        if(listen(sockfd,5)<0)
        {
                perror("listen\n");
                return 5;
        }
        while(1)
        {
                int new_sockfd=accept(sockfd,NULL,NULL);
                if(new_sockfd<0)
                {
                        perror("accept\n");
                        return 6;
                }
                pid_t pid=fork();
                if(pid==0)
                {
                        if(fork()==0)
                        {
                                struct sockaddr_in client;

                                while(1)
                                {

                                        socklen_t len=sizeof(client);
                                        size_t rec=recv(new_sockfd,buf,sizeof(buf),0);
                                        if(rec<0)
                                        {
                                                printf("recv error\n");
                                                return 4;
                                        }
                                        if(rec==0)
                                        {
                                                break;;
                                        }

                                        buf[rec]='\0';
                                        printf("[%s,%d] > %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
                                        send(new_sockfd,buf,strlen(buf),0);
                                }
                                close(new_sockfd);//use signal to delet
                        }

                        else
                        {
                                exit(1);
                        }
                }
                else
                {
                        waitpid(pid);
                }
        }

        close(sockfd);
        return 0;
}     

8. 演示效果:

这里写图片描述

三、最后,TCP和UDP的区别:

  1. TCP提供的是面向连接的、可靠的数据流传输;
    UDP提供的是非面向连接的、不可靠的数据流传输。

  2. TCP提供可靠的服务,通过TCP连接传送的数据,无差错、不丢失,不重复,按序到达;
    UDP尽最大努力交付,即不保证可靠交付。

  3. TCP面向字节流;
    UDP面向报文。

  4. TCP连接只能是点到点的;
    UDP支持一对一、一对多、多对一和多对多的交互通信。

  5. TCP首部开销20字节;
    UDP的首部开销小,只有8个字节。

  6. TCP的逻辑通信信道是可靠信道;
    UDP的逻辑通信信道是不可靠信道。

欢迎各位提建议或指错给我,谢谢~

猜你喜欢

转载自blog.csdn.net/HaloTrriger/article/details/80945034