必威体育Betway必威体育官网
当前位置:首页 > IT技术

基于UDP协议的服务器

时间:2019-09-03 00:14:26来源:IT技术作者:seo实验室小编阅读:58次「手机版」
 

udp协议

博客已弃用,当时存在一些小细节错误后期也不再修改了

欢迎来我的新博客

在撸我们的服务器之前,先好好给大家好好理一理套接字这块的一些知识和概念。


先问问你自己,什么是端口号?这个概念可不是一个模模糊糊的东西

端口号:标识特定主机上唯一一个进程的标识符

是不是和进程id(pid)很像,没错,确实就是很像,因为进程id是也用来标识一个主机上唯一的一个进程,我们通过某个进程的进程id,可以对该进程进行几乎所有操作,如进程控制,进程间通信等等。而端口号和它的区别就在于:进程id所有进程一定都会有,而端口号却不是所有进程都会有,端口号是需要在不同主机间进行通信时才会绑定给参与通信的某个进程的一个标识符,也就是说,端口号是在两台主机间时才会有的概念和标识,但是进程id是在这个进程被创建出后一直伴随到它死亡(某种程度上就和一个人的名字一样),这个进程id才能被新创建的进程使用。

个人感觉有个很形象的例子:在一个学校里,每个学生都拥有学号,并且是从你成为这个学校的学生那一刻起就有了,这个学号也能唯一的来标识你,对于学校的管理层来说,他们只要拿到你的学号,就可以得到你的所有信息,并且可以进行相关事宜的安排,这就像进程id。假设这个学校里有一批学生,他们是被学校任命去长期与其他学校学生进行交流的一批人,学校又给这一批人分配了一些编号,这些编号又能唯一标识这些人,这就像端口号。不知道你明白了没,反正我觉得很形象了(๑•̀ㅂ•́)و✧

关于端口号再扩展一点重要的东西

端口号的划分:

0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的,也就是你不能用的。

1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的,也就是你能用的。

这些也算是一个程序员的常识了,记不住也混个眼熟吧
ssh服务器, 使⽤用22端⼝
ftp服务器, 使⽤用21端⼝(更具体的话是20和21) 
telnet服务器, 使⽤用23端⼝ 
http服务器, 使⽤用80端⼝ 
https服务器, 使⽤用443 

需要注意的是在TCP/ip协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过netstat -n查看)。

关于端口号,有常见的两个问题

1. 一个进程是否可以绑定多个端口号?

2. 一个端口号是否可以被多个进程绑定?

再来说说啥是IP地址呢?是IP地址到底是个用来干什么的东西呢?为什么要有它?

IP地址:用来标识网络中,接入公网的唯一一台主机

这个时候,你肯定可以理解,一台主机的IP地址加上其上的一个端口号,可以在网络中唯一的标识特定主机上的一个特定进程!套接字(socket)是一个翻译过来的词,由于网络中的套接字技术本身也是一种抽象出来的概念,其理解没有一个绝对的说法,其最好的理解就是:IP地址+端口号


关于udp协议

说到UDP当然是先好好说说它的三大特点

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

我们知道,在通信技术不发达的时代,寄信是人类最常用的通信方式,UDP协议某些程度上和寄信挺像的

  • 无连接:写信的时候,你知道知道对方的邮政编码,对方知道寄信人,就能写信了,别的都不用管
  • 不可靠:写信这种通信方式是相当不可靠的(我们的UDP还是比写信可靠很多很多的),如果邮递员遇到恶劣天气无法前往就放弃送这封信了,或者邮递员在路上把你的信弄丢了,都会到导致信寄不到,也就是导致通信的失败,而邮递员也不会去告诉你,信没送到,毕竟那么多封信嘛,他根本就知道,这样,寄信人就傻傻的发,收信人就傻傻的收,有时候后果还挺严重啊
  • 关于面向数据报这一特点在寄信中不好类比,在我们的网络通信中是这样:无论应用层交给UDP多长的报文, UDP都原样发送, 既不会拆分,也不会合并(比如用UDP传输100个字节的数据: 如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100 个字节; 而不能循环调⽤用10次recvfrom, 每次接收10个字节)

特别注意:一个UDP能传输的数据最大长度是64K(由UDP数据报结构决定的), 然而64K在当今的互联网环境下, 这是一个非常小的数字。如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。UDP协议虽然具有接收缓冲区,但是其接收缓冲区不保证接收顺序,也就是未必和发送顺序一样,因此这个问题如何解决也是程序员在应用层要做的。

UDP协议至今都是传输层的两大协议之一就是因为它的应用仍然非常广泛,它虽然有一些上面说到的缺点(TCP都没有),但是也有自己不可取代的优点:首先,UDP比TCP要快,虽然TCP没有UDP的那些缺点,但是TCP的三次握手建立连接、四次挥手断开连接、确认、窗口、重传、拥塞控制等机制无疑会导致TCP的速度是比不上UDP的,其次,没有这些机制,UDP也更不容易被攻击,因为UDP不需要这么多机制,那么为了保证其安全需要注意的方面也会相对少一些,能被他们利用的漏洞也就更少,当然UDP也是无法避免被攻击的,只是相比TCP之下更不容易。

比如QQ中的大多数服务,都是用的UDP协议。

基于UDP的应用层协议

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

当然还有咱们自己写基于的基于UDP的相关程序某种程度上也是UDP的应用层协议


下面咱们开始撸一个很简单的echo(回显)服务器,这个服务器咱们要做到的是服务端接收客户端发来的内容并显示,并且把该内容发回客户端,客户端再显示一次

服务器要做的事情就是:接收内容,并显示,然后发回客户端

客户端要做的事情就:发送内容,再接收服务器的回显,再显示出来

那么服务器代码的思路就是这样:

创建套接字(socket)-->填充本地信息(sockaddr_in)-->把本地信息(套接字)与socket(这个我们在这区分一下就不叫它套接字了)一起绑定到操作系统内部,也就是把socket与IP地址加端口号绑定在一起(bind)-->接收(recvfrom)和回发(sendto)

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


int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        printf("Usage %s [ip] [port]\n",argv[0]);
        return 1;
    }

    int sock = socket(AF_INET,SOCK_DGRAM,0);//协议IPV4 udp传输协议
    if(sock < 0)
    {
        pERROR("socket");
        return 2;
    }

    printf("sock: %d\n",sock);

    //填充本地信息
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[2]));  //把主机序列的字符串端口号转换成整型  然后转换为二子皆网络端口号 因为网络端口号是两个字节所以用s
    local.sin_addr.s_addr = inet_addr(argv[1]);  //把点分十进制字符串IP地址转换为网络字节序的四字节整型IP地址
    
    if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)    //把用户栈上的local绑定到操作系统内部
    {
        perror("bind");
        return 3;
    }

    char buf[1024];
    while(1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        //收到的东西放到buf里
        ssize_t s = recvfrom(sock, buf, sizeof(buf)-1,0,(struct sockaddr*)&client,&len);//通过client带回的信息知道是谁连接了我,我后面要发送
        if(s > 0)
        {
            buf[s] = 0;                                                                  
            //打印出客户端的IP地址和网络端口号,并用这两个函数将其转换成我们想看到的样子,并打印出client发过来的内容
            printf("[%s:%d]: %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
            sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client));//把buf中的内容发回客户端                               
        }
    }

}

客户端代码的思路:

创建套接字(socket)-->填充本地信息(sockaddr_in)-->发送(sendto)和接收回显(recvfrom)

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

int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        printf("Usage %s [ip] [port]\n",argv[0]);
        return 1;
    }

    int sock = socket(AF_INET,SOCK_DGRAM,0);//协议IPV4 udp
    if(sock < 0)
    {
        perror("socket");
        return 2;
    }

    printf("sock: %d\n",sock);

    //填充本地信息
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(atoi(argv[2]));  //把主机序列的字符串端口号转换成整型  然后转换为二子皆网络端口号 因为网络端口号是两个字节所以用short
    server.sin_addr.s_addr = inet_addr(argv[1]);  //把点分十式十进制字符串IP地址转换为网络字节序的四字节整型IP地址
    
    //客户端的端口号不应该绑定,这样不合适!
    /*if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)    //把用户栈上的local绑定到操作系统内部
    {
        perror("bind");
        return 3;
    }*/

    char buf[1024];
    while(1)  //先读数据,再收数据
    {
        printf("Please Enter:");
        fflush(stdout);
        ssize_t s = read(0,buf,sizeof(buf)-1);  //从标准输入读
        if(s > 0)
        {
            buf[s-1] = 0;   //处理\n                                                               
            sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));//把buf中的内容发送到服务端
            //发送完后再接受服务器发回来的消息
            ssize_t _s = recvfrom(sock, buf, sizeof(buf)-1,0,NULL,NULL);//因为我们的客户端收到的数据一定从服务器发过来,不接受发送者也知道是谁发来的
            if(_s > 0)
            {
                buf[_s] = 0;                                                                  
                printf("server echo: %s\n",buf);
            }
        }
    }

}

makefile

.PHONY:all
all:udp_server udp_client

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

udp_client:udp_client.c
	gcc -o $@ $^ -static

.PHONY:clean
clean:
	rm -f udp_client udp_server

gcc默认动态链接,因此我们的客户端代码要加上-static采用静态链接保证了客户端的可移植性,既是一个好习惯也方便我们可以在两台主机测试,不依赖本地库文件

最后,用一台机器测试的用时候服务器和客户端的IP地址都用127.0.0.1(本地回环),端口号就都用8080(这个随意,只要是能用的就行)

我写的这个是个非常简单的基于UDP的服务器,但是UDP协议的本质就是这样了,由这个你也能拓展出很多东西,比如,你把客户端发过来的又转发给另一个客户端,然后两个客户端之间互相回显,这就可以一对一聊天啦,服务器给所有连接的客户端回显,就是聊天室。

代码点我

相关阅读

基于ArcGIS 二次开发 使用技巧总结

这两天刚忙完一个项目,趁着这几天任务轻松,抽空总结上一个项目所遇到的一些问题,都是很简单的基本操作,先列个大纲吧: 一、地图的基本

如何搭建(腾讯云服务器)WEB项目(一)

写在前面的话    写这系列的博客目的是把在搭建过程中遇到的问题进行梳理,作以记录。言归正传,直接开始过程一  申请腾讯云服务

服务器是干什么用的?服务器的作用是什么?

  对于很多刚接触IDC和服务器的企业和网民而言,面对服务器是干什么用的?这样的问题会显得无从作答。其实服务器也没有大家想象的

FTP服务器:如何创建FTP服务器(一)

在开始屏幕下打开“搜索”-进入控制面板 在控制面板窗口中找到“”程序”,点击打开 点击“启用或关闭windows功能” 在打开的

基于vs2012开发activex(MFC)控件

最近学习下ActiveX的开发,网上找了好多东西,现在把开发过程记录下来以备以后使用。1.新建工程2.一直点击下一步,直到出现一下界面,注

分享到:

栏目导航

推荐阅读

热门阅读