基于UDP的服务器

UDP

OSI七层模型(传输层)


UDP协议端格式


这里写图片描述
校验和采用:CRC


UDP特点


无连接:知道对端的IP和端⼝号就直接进⾏传输, 不需要建⽴连接
不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段⽆法发到对⽅, UDP协议层也不会给应⽤层返回任何错误信息
面向数据报:不能够灵活的控制读写数据的次数和数量

面向数据报:应⽤层交给UDP多⻓的报⽂, UDP原样发送, 既不会拆分, 也不会合并
例:如果发送端调⽤⼀次sendto, 发送100个字节, 那么接收端也必须调⽤对应的⼀次recvfrom, 接收100个字节; ⽽不能循环调⽤10次recvfrom, 每次接收10个字节;


UDP传输的过程
无连接,全双工(UDP的socket既能读,也能写)


关于UDP的缓冲区

UDP没有真正意义上的 发送缓冲区. 调⽤sendto会直接交给内核, 由内核将数据传给网络层协议进⾏后续的传输动作;
UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序⼀致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;


注意事项:
UDP协议⾸部中有⼀个16位的最⼤⻓度. 也就是说⼀个UDP能传输的数据最⼤⻓度是64K(包含UDP⾸部). 然⽽64K在当今的互联网环境下, 是⼀个⾮常⼩的数字.
如果我们需要传输的数据超过64K, 就需要在应⽤层⼿动的分包, 多次发送, 并在接收端⼿动拼装;


基于UDP的应用层协议

NFS: 网络⽂件系统
TFTP: 简单⽂件传输协议
DHCP: 动态主机配置协议
BOOTP: 启动协议(⽤于⽆盘设备启动)
DNS: 域名解析协议


以下是一个UDP回显服务器编写过程


//linux下运行


/******************** server.c *********************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>//socket_in
#include <arpa/inet.h>//inet_addr

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printf("Usage  ./server [ip]  [port]\n");
        return 1;
    }
    int sock = socket(AF_INET,SOCK_DGRAM,0);//创建文件描述符
    //socket 第一个参数 指定协议类型 IPV4
    //SCOK_DGRAM 表示创建的socket要使用面向数据报的方式传输数据
    //第三个参数  一般填0
    if(sock < 0)
    {
        perror("socket error\n");
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET; //版本号

    addr.sin_addr.s_addr = inet_addr(argv[1]);//IP地址
    //inet_addr 把一个点分十进制的字符串IP地址转换为数字IP地址 刚好是一个网络字节序

    addr.sin_port = htons(atoi(argv[2])); 
    //atoi argv[2](字符串)转化为数字 然后转化为网络序
    //先发出的数据是低地址,后发出的数据是高地址  tcp/ip 大端.
    //htons表示将16位的短整数从主机字节序转换为网络字节序

    int ret = bind(sock,(sockaddr*)&addr,sizeof(addr));//绑定端口号
    //第一个参数 名字     第二个参数 地址
    //第三个参数 长度
    //第二个参数和第三个参数描述addr这个结构体

    if(ret < 0)
    {
        perror("bind\n");
        return 1;
    }

    while(1)
    {
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        char buf[1024] = {};
        ssize_t read_size = recvfrom(sock,buf,sizeof(buf) - 1,0,
                (sockaddr*)&peer,&len);//读数据
        //UDP 面向数据报 故只能用recvfrom
        //第一个参数  文件描述符  sock (关联网卡)
        //第二个参数  读到的内容放入到缓冲区
        //第三个参数  缓冲区大小
        //第四个参数  flag 默认0
        //第五个参数  对端的IP地址和端口号 存放地方
        //第六个参数  缓冲区的最大长度  (保存对端IP地址和端口号)
        if(read_size < 0)
        {
            perror("recvfrom\n");
            continue;
        }
        buf[read_size] = '\0';//不写没有任何问题 初始化为0 前面我们预留了一位'0'
        printf("[%s:%d] %s\n",inet_ntoa(peer.sin_addr),
               ntohs(peer.sin_port),buf);
        //回显服务器
        //inet_ntoa 32位的数字IP 转化为点分十进制的字符串IP
        //ntons 网络序转主机序
        sendto(sock,buf,strlen(buf),0,
                (sockaddr*)&peer,sizeof(peer));// UDP 面向数据报
        //第一个参数  文件描述符 
        //第二个参数  缓冲区位置
        //第三个参数  发送的大小
        //4 flag  0
        //5 对端IP地址的起始位置
        //6 大小 
    }
    close(sock); //执行不到 保证

    return 0;
}

/******************** client.c **********************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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

typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in; 


int main(int argc,char* argv[])
{
    //通过命令行参数,指定客户端向哪个服务器发送请求
    // ./client [ip] [port]
   if(argc != 3)
   {
       printf("Usage ./cilent [ip] [port]\n");
       return 1;
   }
   int sock =socket(AF_INET,SOCK_DGRAM,0);
   if(sock < 0)
   {
       perror("socket error\n");
       return 1;
   }

   sockaddr_in server_addr;
   server_addr.sin_family = AF_INET;
   server_addr.sin_addr.s_addr = inet_addr(argv[1]);
   server_addr.sin_port = htons(atoi(argv[2]));




while(1)
{
    printf(">begin>:");
    fflush(stdout);

    char buf[1024] = {0};

    ssize_t read_size = read(0,buf,sizeof(buf)-1);
    //0 stdin 标准输入读数据
    //1 stdout
    //2 stderr
    if(read_size < 0)
    {
        perror("read error\n");
        return 1;
    }
    if(read_size == 0)//读完啦 读到EOF
    {
        printf("read done!\n");
        return 0;
    }
    buf[read_size] = '\0';

    //2.把数据发给服务器
    sendto(sock,buf,strlen(buf),0,(sockaddr*)&server_addr,
            sizeof(server_addr));

    //3.尝试从服务器读取数据,此时recvfrom不需要知道对端的IP和端口号
    char buf_output[1024] = {0};
    read_size = recvfrom(sock,buf_output,
            sizeof(buf_output)-1,0,NULL,NULL);

    //4.写到标准输入上

    printf("server resp: %s",buf_output);


}//while

    close(sock);
    return 0;
}

/******************** makefile ***********************/
.PHONY:all
all:server client

server:server.c
    gcc $^ -o $@

client:client.c
    gcc $^ -o $@ 

.PHONY:clean
clean:
    rm -f server client

OSI七层模型:
https://blog.csdn.net/Romantic_C/article/details/81665591
网络套接字:
https://blog.csdn.net/Romantic_C/article/details/81704425
UDP服务器:
https://blog.csdn.net/Romantic_C/article/details/81705468
TCP服务器:
https://blog.csdn.net/Romantic_C/article/details/81707907
应用层:
https://blog.csdn.net/Romantic_C/article/details/81714161
传输层:
https://blog.csdn.net/Romantic_C/article/details/81747317
网络层:
https://blog.csdn.net/Romantic_C/article/details/81751255
数据链路层:
https://blog.csdn.net/Romantic_C/article/details/81777844

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值