TCP UDP模型

本文详细介绍了套接字(socket)的概念和在TCP及UDP编程中的应用,包括socket函数创建套接字、bind函数绑定地址、listen函数设置监听、accept函数接收连接、recv和send函数进行数据传输以及connect函数建立连接等关键步骤,同时提供了TCP和UDP服务器及客户端的代码示例。
摘要由CSDN通过智能技术生成

目录

socket

套接字概念

 socket函数

TCP编程

TCP流程图

 TCP编程

socket

bind

listen

accept(阻塞函数)

recv

send

connect

代码示例

服务器

客户端

UDP编程

UDP流程图

UDP编程 

socket

bind

recvfrom

sendto

示例代码

客户端

UDP中connect函数

示例代码(服务端)

基于UDP的tftp协议

tftp协议概述

tftp通信过程总结

 tftp协议分析

 下载模板示例代码


socket

套接字概念

最早的套接字和共享内存,消息队列,管道一样,只能实现一个主机内部的进程间通信。

后期加入了TCP/IP协议,使得套接字能够支持不同主机之间的进程间通信。

socket函数,可以在内核空间中创建两块缓冲区,供于发送数据,接收数据。

 socket函数

功能:在内核空间中创建接收缓冲区和发送缓冲区,并在用户空间获取到该缓冲区的文件描述符;
        用户可以通过该文件描述符操作缓冲区;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);
参数:
    int domain:地址族、协议族
       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       
    int type:
        SOCK_STREAM: 字节流式套接字,流式套接字,----》默认指定TCP协议
        SOCK_DGRAM:  数据报式套接字,报式套接字,----> 默认指定UDP协议。
        SOCK_RAW:   原始套接字,协议需要在第三个参数指定。
        
    int protocol:0,使用默认协议。
            IPPROTO_TCP  IPPROTO_UDP
返回值:
    成功,返回的套接字文件描述符 >0;
    失败,返回-1,更新errno;

TCP编程

TCP流程图

 TCP编程

socket

功能:在内核空间中创建接收缓冲区和发送缓冲区,并在用户空间获取到该缓冲区的文件描述符;
        用户可以通过该文件描述符操作缓冲区;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);
参数:
    int domain:地址族、协议族
       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       
    int type:
        SOCK_STREAM: 字节流式套接字,流式套接字,----》默认指定TCP协议
        SOCK_DGRAM:  数据报式套接字,报式套接字,----> 默认指定UDP协议。
        SOCK_RAW:   原始套接字,协议需要在第三个参数指定。
        
    int protocol:0,使用默认协议。
            IPPROTO_TCP  IPPROTO_UDP
返回值:
    成功,返回的套接字文件描述符 >0;
    失败,返回-1,更新errno;

bind

功能:绑定地址信息到套接字文件中;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);
参数:
    int sockfd:指定要绑定到哪个套接字文件上,填对应的文件描述符;
    struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定。
                            绑定IP和端口号到服务器套接字上;
        AF_INET: man 7 ip
           struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */     必须填AF_INET
               in_port_t      sin_port;   /* port in network byte order */  端口号的网络字节序:1024~49151
               struct in_addr sin_addr;   /* internet address */            本机IP地址的网络字节序。ifconfig        
           };

           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };
    socklen_t addrlen:真实的地址信息结构体的大小;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;
  1. bind: Address already in use : 端口号被占用
  2. bind: Cannot assign requested address IP地址错误

listen

功能:将套接字设置为被动监听状态,监听是否有客户端连接.
    让内核去维护两个队列:未完成连接的队列,已完成连接的队列;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);
参数:
    int sockfd:指定要将哪个文件描述符,设置为被动监听状态;
    int backlog:指定未完成连接队列的容量; 允许同时有多少个客户端未完成连接,不要填0,1即可,一般填128;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

accept(阻塞函数)

功能:从已完成连接的队列头中获取一个客户端信息,生成一个新的文件描述符,该文件描述符才是通信使用的文件描述符;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
    int sockfd:被转换为监听状态的文件描述符;
    struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定。
                            存储获取到的客户端的地址信息,若不想获取,则填NULL;
        AF_INET: man 7 ip
           struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */     必须填AF_INET
               in_port_t      sin_port;   /* port in network byte order */  端口号的网络字节序:1024~49151
               struct in_addr sin_addr;   /* internet address */            本机IP地址的网络字节序。ifconfig        
           };

           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };
    socklen_t *addrlen:真实的地址信息结构体大小,注意是指针类型。
返回值:
    成功,返回新的文件描述符,该文件描述符才是通信使用的文件描述符;
    失败,返回-1,更新errno;

recv

功能:通过套接字文件描述符从接收缓冲区读取数据;
原型:
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
    int sockfd:填accept函数获取到的新的文件描述符;
    void *buf:存储获取到的数据;可以读取任意类型数据;
    size_t len:指定要读取多少个字节;
    int flags:
            0:阻塞方式读取,若没有数据可读,则recv函数阻塞。当flags填0,完全等价于read函数
            MSG_DONTWAIT:非阻塞方式,若没有数据可读,recv函数不阻塞,但是函数会运行失败;
返回值:
    >0, 成功读取到的字节数;
    =0, 在流式套接字中,对端关闭, udp中不会出现该情况。
    =-1,函数运行失败;
    
recv(sockfd, buf, len, flags);
 等价于 
recvfrom(sockfd, buf, len, flags, NULL, NULL);

recv是否可以用其他函数替换?        read recvfrom

send

功能:通过套接字文件描述符向指定的发送缓冲区发送数据;
原型:
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
    int sockfd:填accept函数获取到的新的文件描述符;
    void *buf:存储获取到的数据;可以读取任意类型数据;
    size_t len:指定要读取多少个字节;
    int flags:
            0:阻塞方式发送,若缓冲区满,则该函数阻塞;当flags填0,则完全等价于write.
            MSG_DONTWAIT:非阻塞方式,若缓冲区满,则该函数不阻塞,但是函数会运行失败;
返回值:
    >0, 成功发送的字节数;
    =-1,函数运行失败;
    
send(sockfd, buf, len, flags);等价于sendto(sockfd, buf, len, flags, NULL, 0);

send是否可以用其他函数替换?        write sendto

connect

功能:连接服务器;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);
参数:
    int sockfd:指定要连接到服务器的套接字文件描述符;
    struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定。  
                            指定要连接的服务器的地址信息结构体;
    socklen_t addrlen:真实的地址信息结构体的大小;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

代码示例

服务器

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

#define ERR_MSG(msg)  do{\
    fprintf(stderr, "__%s__ __%s__  __%d__ ", __FILE__, __func__, __LINE__);\
    perror(msg);\
}while(0)

#define IP "192.168.50.103"     //ifconfig
#define PORT 6666               //端口号 1024~49151

int main(int argc, const char *argv[])
{
    //创建流式套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("socket success sfd=%d\n", sfd);

    //允许端口快速被覆盖重用。
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
    {
        ERR_MSG("setsockopt");
        return -1;
    }
    printf("允许端口快速被覆盖重用成功\n");



    //填充地址信息结构体,真实的地址信息结构体AF_INET: man 7 IP
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;      //必须填AF_INET;
    sin.sin_port        = htons(PORT);  //端口号的网络字节序1024~49151
    sin.sin_addr.s_addr = inet_addr(IP);//本机IP地址的网络字节序


    //绑定服务器的地址信息
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("bind success __%d__\n", __LINE__);


    //将套接字设置为被动监听状态
    if(listen(sfd, 128) < 0)
    {
        ERR_MSG("listen");
        return -1;
    }
    printf("listen success __%d__\n", __LINE__);                                    


    struct sockaddr_in cin;     //存储客户端地址信息
    socklen_t addrlen = sizeof(cin);

    //生成新的文件描述符与客户端通信
    int newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
    if(newfd < 0)
    {
        ERR_MSG("accept");
        return -1;
    }
    printf("[%s:%d] newfd=%d 客户端连接成功 __%d__\n", \
            inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);

    char buf[128] = "";
    ssize_t res = 0;
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收
        res = recv(newfd, buf, sizeof(buf), 0);
        if(res < 0)
        {
            ERR_MSG("recv");
            return -1;
        }
        else if(0 == res)
        {
            printf("[%s:%d] newfd=%d 客户端下线 __%d__\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);
            break;
        }

        printf("[%s:%d] newfd=%d:%s __%d__\n", \
                inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, buf, __LINE__);


        //发送
        /*
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf)-1] = 0;
        */

        strcat(buf, "*_*");

        if(send(newfd, buf, sizeof(buf), 0) < 0)
        {
            ERR_MSG("send");
            return -1;
        }
        printf("send success __%d__\n", __LINE__);
    }

    //关闭
    if(close(newfd) < 0)
    {
        ERR_MSG("close");
        return -1;
    }

    if(close(sfd) < 0)
    {
        ERR_MSG("close");
        return -1;
    }
    return 0;
}

客户端

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

#define ERR_MSG(msg)  do{\
    fprintf(stderr, "__%s__ __%s__  __%d__ ", __FILE__, __func__, __LINE__);\
    perror(msg);\
}while(0)

#define IP "192.168.50.103"     //ifconfig
#define PORT 6666               //端口号 1024~49151

int main(int argc, const char *argv[])
{
    //创建流式套接字
    int cfd = socket(AF_INET, SOCK_STREAM, 0);                                
    if(cfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("create socket success cfd=%d __%d__\n", cfd, __LINE__);

    //绑定客户端自身的地址信息结构体---》非必须绑定
    //若客户端没有绑定地址信息,
    //则操作系统会自动帮客户端绑定本机IP,以及49151~65535内的随机端口

    //填充服务器的地址信息结构体,给connect函数使用
    //要连接哪个服务器,就填哪个服务器的地址信息
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(PORT);  //服务器的端口号
    sin.sin_addr.s_addr = inet_addr(IP); //服务器的IP

    //连接服务器
    if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("connect");
        return -1;
    }
    printf("connect success __%d__\n", __LINE__);

    char buf[128] = "";
    ssize_t res = 0;
    while(1)
    {
        //从终端获取数据
        printf("请输入>>> ");
        fgets(buf,sizeof(buf), stdin);
        buf[strlen(buf)-1] = 0;

        //发送
        if(send(cfd, buf, sizeof(buf), 0) < 0)
        {
            ERR_MSG("send");
            return -1;
        }
        printf("send success __%d__\n", __LINE__);

        //接收
        bzero(buf, sizeof(buf));        //memset
        res = recv(cfd, buf, sizeof(buf), 0);
        if(res < 0)
        {
            ERR_MSG("recv");
            return -1;
        }
        else if(0 == res)
        {
            printf("服务器离线\n");
            break;
        }

        printf(":%s __%d__\n", buf, __LINE__);

    }
    //关闭
    if(close(cfd) < 0)
    {
        ERR_MSG("close");
        return -1;
    }

    return 0;
}

UDP编程

UDP流程图

UDP编程 

socket

功能:在内核空间中创建接收缓冲区和发送缓冲区,并在用户空间获取到该缓冲区的文件描述符;
        用户可以通过该文件描述符操作缓冲区;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);
参数:
    int domain:地址族、协议族
       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       
    int type:
        SOCK_STREAM: 字节流式套接字,流式套接字,----》默认指定TCP协议
        SOCK_DGRAM:  数据报式套接字,报式套接字,----> 默认指定UDP协议。
        SOCK_RAW:   原始套接字,协议需要在第三个参数指定。
        
    int protocol:0,使用默认协议。
            IPPROTO_TCP  IPPROTO_UDP
返回值:
    成功,返回的套接字文件描述符 >0;
    失败,返回-1,更新errno;

bind

功能:将地址信息结构体与套接字文件绑定;
原型:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);
参数:
    int sockfd:套接字文件描述符,其实就是socket函数的返回值;
    struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体,根据地址族指定
                            需要手动填充要绑定到套接字上的IP和端口;
        AF_INET: man 7 ip
            struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */      必须填AF_INET;
               in_port_t      sin_port;   /* port in network byte order */   端口号的网络字节序,1024~49151
               struct in_addr sin_addr;   /* internet address */             本机IP地址的网络字节序
           };

           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };   
    socklen_t addrlen:真实的地址信息结构体的大小,sizeof(struct sockaddr_in)
返回值:  
    成功,返回0;
    失败,返回-1,更新errno;               

recvfrom

功能:从接收缓冲区中接收数据,同时可以获取到该数据包从哪里来,即发送方的地址信息。
原型:
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
参数:
    int sockfd:填accept函数获取到的新的文件描述符;

    void *buf:存储获取到的数据;可以读取任意类型数据;

    size_t len:指定要读取多少个字节;

    int flags:
            0:阻塞方式读取,若没有数据可读,则recv函数阻塞。当flags填0,完全等价于read函数
            MSG_DONTWAIT:非阻塞方式,若没有数据可读,recv函数不阻塞,但是函数会运行失败;

    struct sockaddr *addr:通用地址信息结构体,真实的地址信息结构体根据地址族指定。  
                            函数运行完毕后,会存储这个数据包从谁那里发送过来。即发送方的地址信息。若不想接收则填NULL;
    socklen_t *addrlen:真实的地址信息结构体的大小; 注意是指针类型。
返回值:
    >0, 成功读取到的字节数;
    =0, 在流式套接字中,对端关闭
    =-1,函数运行失败;   
    
recv(sockfd, buf, len, flags);
 等价于 
recvfrom(sockfd, buf, len, flags, NULL, NULL);

sendto

功能:发送数据到指定接收方;
原型:
       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
    int sockfd:填accept函数获取到的新的文件描述符;
    void *buf:存储获取到的数据;可以读取任意类型数据;
    size_t len:指定要读取多少个字节;
    int flags:
            0:阻塞方式发送,若缓冲区满,则该函数阻塞;当flags填0,则完全等价于write.
            MSG_DONTWAIT:非阻塞方式,若缓冲区满,则该函数不阻塞,但是函数会运行失败;
    struct sockaddr *dest_addr:需要手动填充地址信息,表示该数据包应该发给谁。要发给谁就填谁的地址信息。
    socklen_t addrlen:真实的地址信息结构体大小;
    
返回值:
    >0, 成功发送的字节数;
    =-1,函数运行失败;
send(sockfd, buf, len, flags);等价于sendto(sockfd, buf, len, flags, NULL, 0);

示例代码

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

#define ERR_MSG(msg)  do{\
    fprintf(stderr, "__%s__ __%s__  __%d__ ", __FILE__, __func__, __LINE__);\
    perror(msg);\
}while(0)

#define PORT 6666               //1024~49151
#define IP  "192.168.50.103"        //ifconfig


int main(int argc, const char *argv[])
{
    //创建报式套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("sfd = %d __%d__\n", sfd, __LINE__);

    //填充服务器的地址信息结构体, AF_INET: man 7 ip
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(PORT);      //1024~49151
    sin.sin_addr.s_addr = inet_addr(IP);    //ifconfig

    //绑定服务器自身的地址信息
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("bind success __%d__\n", __LINE__);

    char buf[128] = "";
    struct sockaddr_in cin;     //存储客户端的地址信息
    socklen_t addrlen = sizeof(cin);

    ssize_t res = 0;
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收
        res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);
        //res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
        //res = recv(sfd, buf, sizeof(buf), 0);
        if(res < 0)
        {
            ERR_MSG("recvfrom");
            return -1;
        }                                                                             
        printf("[%s:%d] : %s __%d__\n", \
                inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf, __LINE__);

        //发送 --> 谁发给我,我发还给谁
        strcat(buf, "*_*");

        if(sendto(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, sizeof(cin)) < 0)
        {
            ERR_MSG("sendto");
            return -1;
        }
        printf("sendto success __%d__\n", __LINE__);

    }

    //关闭
    if(close(sfd) < 0)
    {
        ERR_MSG("close");
        return -1;
    }
    return 0;
}

客户端

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

#define ERR_MSG(msg)  do{\
    fprintf(stderr, "__%s__ __%s__  __%d__ ", __FILE__, __func__, __LINE__);\
    perror(msg);\
}while(0)

#define PORT 6666               //1024~49151
#define IP  "192.168.50.103"        //ifconfig


int main(int argc, const char *argv[])
{
    //创建报式套接字
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(cfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("cfd = %d __%d__\n", cfd, __LINE__);

    //绑定客户端自身的地址信息---》非必须绑定
    //若不绑定则操作系统会自动给客户端绑定上一个可用IP以及随机端口。

    /*

    //绑定服务器自身的地址信息
    if(bind(cfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("bind success __%d__\n", __LINE__);
    */

    //填充服务器的地址信息结构体,给sendto函数使用
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(PORT);      //服务器的端口
    sin.sin_addr.s_addr = inet_addr(IP);    //服务器的IP


    char buf[128] = "";
    struct sockaddr_in dstAddr;
    socklen_t addrlen = sizeof(dstAddr);

    ssize_t res = 0;
    while(1)
    {
        bzero(buf, sizeof(buf));
        printf("请输入>>> ");
        fgets(buf, sizeof(buf), stdin);                                                 
        buf[strlen(buf)-1] = 0;

        if(sendto(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
        {
            ERR_MSG("sendto");
            return -1;
        }
        printf("sendto success __%d__\n", __LINE__);

        //接收
        res = recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&dstAddr, &addrlen);
        //res = recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);
        //res = recv(cfd, buf, sizeof(buf), 0);
        //res = read(cfd, buf, sizeof(buf));
        if(res < 0)
        {
            ERR_MSG("recvfrom");
            return -1;
        }
        printf("[%s:%d] : %s __%d__\n", \
                inet_ntoa(dstAddr.sin_addr), ntohs(dstAddr.sin_port), buf, __LINE__);
    }

    //关闭
    if(close(cfd) < 0)
    {
        ERR_MSG("close");
        return -1;
    }
    return 0;
}

UDP中connect函数

udp中可以调用connect函数。

1. TCP中的connect函数会产生三次握手,将server和client连接起来。

    UDP中的connect函数不会产生连接,UDP中的connect函数仅仅是将对端的IP地址和端口号填充到内核套接字中,此时UDP只能与记录的对端进行通信。

2. TCP的connect函数只能调用一次。

    UDP中的connect函数可以调用多次,用于刷新内核中对端的地址信息。若想清空内核中对端的地址信息,可以将sin_family设置为AF_UNSPEC(sin_family=AF_UNSPEC)。

3. 当UDP采用connect的方式收发报文后,可以调用以下形式:

  1.         sendto        send        write
  2.         recvfrom        recv        read

使用recvfrom和sendto的时候,建议使用以下形式:

recvfrom(sfd,buf,sizeof(buf),0,NULL,NULL);

sendto(sfd,buf,sizeof(buf),0,NULL,0);

优点

1. 提高传输效率

不调用connect函数传输:

        填充对端地址信息到内核中 --> 发送数据 --> 清空内核中对端的信息 --> 填充对端地址信息到内核中 --> 发送数据 --> 清空内核中的对端信息

除第一次外,每次发送信息都需要先清空内核中对端的信息再发送。

调用connect函数传输:

        填充对端地址信息到内核中 -->发送数据 --> 发送数据 --> 发送数据 --> ...... --> 清空内核中的对端信息

客户端和服务端连接成功后,所有信息都发送完再清空内核中对端的信息。

2.增加传输的稳定性

防止AB两个进程在做大量数据传输的时候接收到C进程的数据,从而导致数据错乱。

问题:

1.UDP是否能用connect函数?

        可以,并且需要将上述的内容一一阐述清楚

2.UDP中的recvfrom是否可以替换成其他函数?

        可以,UDP采用connect的方式收发报文时可以将recvfrom替换成reccv函数或write函数

3.UDP中的sendto是否可以替换成其他函数?

        可以,UDP采用connect的方式收发报文时可以将sendto替换成send函数或read函数

T1和T3问题其实一样

示例代码(服务端)

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

#define ERR_MSG(msg)  do{\
    fprintf(stderr, "__%s__ __%s__  __%d__ ", __FILE__, __func__, __LINE__);\
    perror(msg);\
}while(0)
                                                                                         
#define PORT 6666               //1024~49151
#define IP  "192.168.50.103"        //ifconfig


int main(int argc, const char *argv[])
{
    //创建报式套接字
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("sfd = %d __%d__\n", sfd, __LINE__);

    //填充服务器的地址信息结构体, AF_INET: man 7 ip
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(PORT);      //1024~49151
    sin.sin_addr.s_addr = inet_addr(IP);    //ifconfig

    //绑定服务器自身的地址信息
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("bind success __%d__\n", __LINE__);


    char buf[128] = "";
    struct sockaddr_in cin;     //存储客户端的地址信息
    socklen_t addrlen = sizeof(cin);

    ssize_t res = 0;
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收
        res = recvfrom(sfd, buf, sizeof(buf), 0, (struct sockaddr*)&cin, &addrlen);
        if(res < 0)
        {
            ERR_MSG("recvfrom");
            return -1;
        }
        printf("[%s:%d] : %s __%d__\n", \
                inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf, __LINE__);

        //connect函数,将对端的地址信息填充到内核中
        //调用connect函数,此时该udp只能与记录的对端进行通信
        if(connect(sfd, (struct sockaddr*)&cin, sizeof(cin)) < 0)
        {
            ERR_MSG("connect");
            return -1;
        }
        printf("udp connect [%s:%d] success __%d__\n", \
                inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), __LINE__);

        while(1)
        {
            bzero(buf, sizeof(buf));

            res = recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL);
            if(res < 0)
            {
                ERR_MSG("recvfrom");
                return -1;
            }
            printf("[%s:%d] : %s __%d__\n", \
                    inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf, __LINE__);


            if(strcmp(buf, "quit") == 0)
                break;

            //发送 --> 谁发给我,我发还给谁
            strcat(buf, "*_*");

            if(write(sfd, buf, sizeof(buf)) < 0)
            {
                ERR_MSG("sendto");
                return -1;
            }
            printf("sendto success __%d__\n", __LINE__);
        }

        cin.sin_family      = AF_UNSPEC;        //清空内核中对端的地址信息
        //调用connect函数,此时该udp只能与记录的对端进行通信
        if(connect(sfd, (struct sockaddr*)&cin, sizeof(cin)) < 0)
        {
            ERR_MSG("connect");
            return -1;
        }
        printf("udp connect AF_UNSPEC success\n");

    }

    //关闭
    if(close(sfd) < 0)
    {
        ERR_MSG("close");
        return -1;
    }
    return 0;
}

基于UDP的tftp协议

tftp协议概述

tftp是简单文件传输协议,适用于在网络上进行文件传输的一套标准协议,使用UDP传输。

特点:

  1. tftp是应停用层协议
  2. 基于UDP协议实现

数据传输模式:

  1. octet:二进制模式(常用)
  2. mail:已经不再支持

tftp通信过程总结

  1. 服务器在69号端口等待客户端的请求
  2. 服务器若批准请求,则使用临时端口与客户端进行通信
  3. 每个数据包的编号都有变化(从1开始)
  4. 每个数据包都要得到ACK的确认,如果出现超时,则需要重新发送最后的数据包或ACK包
  5. 数据长度以512Byte传输,小于512Byte的数据意味着数据传输结束

 tftp协议分析

差错码:

0 未定义,差错错误信息

1 File not found.

2 Access violation.

3 Disk full or allocation exceeded.

4 illegal TFTP operation.

5 Unknown transfer ID.

6 File already exists.

7 No such user.

8 Unsupported option(s) requested.

 下载模板示例代码

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

#define ERR_MSG(msg)  do{\
    fprintf(stderr, "__%s__ __%s__  __%d__ ", __FILE__, __func__, __LINE__);\
    perror(msg);\
}while(0)

#define PORT 69                 //固定69号端口
#define IP  "192.168.50.183"        //windowsIP


int do_download(int cfd, struct sockaddr_in sin);

int main(int argc, const char *argv[])
{
    //创建报式套接字
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(cfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
    printf("cfd = %d __%d__\n", cfd, __LINE__);

    //绑定客户端自身的地址信息---》非必须绑定
    //若不绑定则操作系统会自动给客户端绑定上一个可用IP以及随机端口。

                                                                                             

    //填充服务器的地址信息结构体,给sendto函数使用
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(PORT);      //服务器的端口
    sin.sin_addr.s_addr = inet_addr(IP);    //服务器的IP 

    char choose = 0;
    while(1)
    {
        printf("----------------------------\n");
        printf("----------1. 下载-----------\n");
        printf("----------2. 上传-----------\n");
        printf("----------3. 退出-----------\n");
        printf("----------------------------\n");
        printf("请输入>>> ");
        choose = getchar();
        while(getchar()!=10);

        switch(choose)
        {
        case '1':
            do_download(cfd, sin);
            break;
        case '2':
            //do_upload();
            break;
        case '3':
            goto END;
        default:
            printf("输入错误,请重新输入\n");

        }

    }


END:
    //关闭
    if(close(cfd) < 0)
    {
        ERR_MSG("close");
        return -1;
    }
    return 0;
}


int do_download(int cfd, struct sockaddr_in sin)
{
    printf("请输入要下载的文件名>>> ");
    char name[20] = "";
    scanf("%s", name);
    while(getchar()!=10);

    //组下载请求包
    char buf[516]  = "";

    int size = sprintf(buf, "%c%c%s%c%s%c", 0, 1, name, 0, "octet", 0);

    if(sendto(cfd, buf, size, 0, (struct sockaddr*)&sin, sizeof(sin)) <0)
    {
        ERR_MSG("sendto");
        return -1;
    }
    printf("sendto success\n");

    //打开一个文件,以可写的方式

    socklen_t addrlen = sizeof(sin);
    ssize_t res = 0;
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收数据包
        if((res=recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &addrlen)) < 0)
        {
            ERR_MSG("recvfrom");
            return -1;
        }

        //判断是否是数据包 buf[1]==3  buf[1]==5

        if(3 == buf[1])
        {
            //处理重复收包的情况,通过块编号判断是否重复

            //存储数据到文件中
            //write(fd, buf+4, res-4)

            //回复ACK --->数据包和ACK包前4个字节,只有操作码不一致
            //由于操作码是大端字节序,所以有效操作码其实存在buf[1]的位置
            buf[1] = 4;
            if(sendto(cfd, buf, 4, 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
            {
                ERR_MSG("sendto");
                return -1;
            }


            //当数据小于512的时候要结束循环,即整个数据包要小于516
            if(res<512+2+2)
            {
                printf("下载完毕\n");
                break;
            }
        }
        else if(5 == buf[1])
        {

        }
    }

    //关闭文件
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值