07-基于UDP的服务器端/客户端

参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨

参考链接:https://www.jianshu.com/p/dac7b8bdb682

测试环境:Ubuntu 10.10

GCC版本:4.4.5

 

一、UDP套接字的特点

(1) UDP是一个非连接的协议,传输数据之前源端和终端不建立连接,当它想传送时就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。
(2) 由于传输数据不建立连接,因此也就不需要维护连接状态,包括收发状态等,因此一台服务机可同时向多个客户机传输相同的消息。
(3) UDP信息包的标题很短,只有8个字节,相对于TCP的20个字节信息包的额外开销很小。
(4) 吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制
(5)UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的链接状态表(这里面有许多参数)。
(6)UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付给IP层。既不拆分,也不合并,而是保留这些报文的边界,因此,应用程序需要选择合适的报文大小。

 

二、UDP内部工作原理

        与TCP不同,UDP不会进行流控制。请看下图:

分析:

         IP作用是让离开主机B的UDP数据包准确传递到主机A。UDP最重要的作用是根据端口号将传到主机的数据包交付给最终的UDP套接字。

 

三、UDP的一些使用场景

1. 实时传输视频、音频。

2. 收发数据量小但需要频繁连接时,UDP比TCP更高效。

 

四、实现基于UDP的服务器端/客户端

1.基于UDP的数据I/O函数

* 发送UDP数据

头文件:#include <sys/socket.h>
函数功能:传输数据
返回值:成功时返回传输的字节数,失败时返回-1
函数原型:ssize_t sendto(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* to,socklen_t addrlen);
函数参数:
sock——用于传输数据的UDP套接字文件描述符
buff——保存待传输数据的缓冲地址值
nbytes——待传输的数据长度,以字节为单位
flags——可选项参数,若没有则传递0
to——存在目标地址信息的sockaddr结构体变量的地址值
addrlen——传递给参数to的地址值结构体变量长度

* 接收UDP数据

头文件:#include <sys/socket.h>
函数功能:接收UDP数据
返回值:成功时返回接收的字节数,失败时返回-1
函数原型:ssize_t recvfrom(int sock, void* buff, size_t nbytes, int flags, struct sockaddr* from, socklen_t* addrlen);
函数参数:
sock——用于接收数据的UDP套接字文件描述符
buff——保存接收数据的缓冲地址值
nbytes——可接收的最大字节数,故无法超过参数buff所指的缓冲大小
flags——可选项参数,若没有则传入0
from——存有发送端地址信息的sockaddr结构体变量的地址值
addrlen——保存参数from的结构体变量长度的变量地址值

 

2.编写基于UDP的回声服务器端/客户端

服务器端:

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

#define BUF_SIZE 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int servSock = -1;
    char message[BUF_SIZE] = {0};
    int strLen = -1;
    socklen_t clntAddrSize;
    struct sockaddr_in servAddr, clientAddr;

    if(2 != argc)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_DGRAM, 0);
    if(-1 == servSock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error");

    while(1)
    {
        clntAddrSize = sizeof(clientAddr);
        strLen = recvfrom(servSock, message, BUF_SIZE, 0, (struct sockaddr*)&clientAddr, &clntAddrSize);
        sendto(servSock, message, strLen, 0, (struct sockaddr*)&clientAddr, clntAddrSize);
    }

    close(servSock);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端:

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

#define BUF_SIZE 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int sock = -1;
    char message[BUF_SIZE] = {0};
    int strLen = -1;
    socklen_t addrSize;

    struct sockaddr_in servAddr, fromAddr;

    if(3 != argc)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    while(1)
    {
        fputs("Input message(q to quit):", stdout);
        fgets(message, sizeof(message), stdin);

        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        sendto(sock, message, sizeof(message), 0, (struct sockaddr*)&servAddr, sizeof(servAddr));

        addrSize = sizeof(fromAddr);
        strLen = recvfrom(sock, message, sizeof(message), 0, (struct sockaddr*)&fromAddr, &addrSize);
        message[strLen] = 0;
        printf("Message from server:%s\n",message);
    }

    close(sock);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

操作:

服务器端:gcc main.c -o uechoServer.out,运行:

客户端:gcc main.c -o uechoClient.out,运行:

Input message(q to quit):hello world!
Message from server:hello world!

Input message(q to quit):Q

 

四、UDP客户端套接字的地址分配

        UDP程序中,首次调用sendto函数时给相应套接字自动分配IP和端口,此时分配的地址一直保留到程序结束为止。

 

五、存在数据边界的UDP套接字

1.UDP是具有数据边界的协议。输入函数的调用次数应和输出函数的调用次数完全一致

发送端:

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

#define BUF_SIZE 30

void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int sock = -1;
    char msg1[] = "Hi";
    char msg2[] = "I'm another UDP host!";
    char msg3[] = "Nice to meet you!";

    struct sockaddr_in yourAddr;
    socklen_t yourAddrSize;

    if(3 != argc)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");

    memset(&yourAddr, 0, sizeof(yourAddr));
    yourAddr.sin_family = AF_INET;
    yourAddr.sin_addr.s_addr = inet_addr(argv[1]);
    yourAddr.sin_port = htons(atoi(argv[2]));

    sendto(sock, msg1, sizeof(msg1), 0, (struct sockaddr*)&yourAddr, sizeof(yourAddr));
    sendto(sock, msg2, sizeof(msg2), 0, (struct sockaddr*)&yourAddr, sizeof(yourAddr));
    sendto(sock, msg3, sizeof(msg3), 0, (struct sockaddr*)&yourAddr, sizeof(yourAddr));

    close(sock);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

接收端:

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

#define BUF_SIZE 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int sock = -1;
    char message[BUF_SIZE] = {0};
    struct sockaddr_in myAddr, yourAddr;
    socklen_t addrSize;
    int strLen, i;

    if(2 != argc)
    {
        printf("Usage:%s <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");

    memset(&myAddr,0, sizeof(myAddr));
    myAddr.sin_family = AF_INET;
    myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(sock, (struct sockaddr*)&myAddr, sizeof(myAddr)))
        errorHandling("bind() error");

    for(i = 0; i < 3; i++)
    {
       sleep(5);
       addrSize = sizeof(yourAddr);
       strLen = recvfrom(sock, message, sizeof(message), 0, (struct sockaddr*)&yourAddr, &addrSize);
       printf("Message %s %d %d\n", message, strLen, i+1);
    }

    close(sock);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

操作:

1)发送端:编译gcc main.c -o boundHost1.out,运行:./boundHost1.out 127.0.0.1 9190

2)接收端:编译gcc main.c -o boundHost2.out,运行:./boundHost2.out 9190

./boundHost2.out 9190
Message Hi 3 1
Message I'm another UDP host! 22 2
Message Nice to meet you! 18 3

分析:

        快速发送3次数据,但是接收端延时5秒接收,所以接收端会间隔5秒打印数据。

注意:

* 如果代码修改为发送两次,接收三次,接收端则会处于等待数据。

* 如果代码修改为发送三次,接收两次,接收和发送端程序正常退出。

 

六、已连接(connected)UDP套接字与未连接(unconnected)UDP套接字

1)UDP套接字默认未连接套接字,每次调用sendto函数时可以发送数据。sendto函数大致分为3步:

1.向UDP套接字注册目标IP和端口。

2.传输数据。

3.删除UDP套接字中注册的目标地址信息。

2)已连接套接字:当时用UDP套接字,向IP不变的主机多次发送数据时,可以先建立连接,再发送数据。优势:提高数据传输性能(向UDP套接字注册目标IP、删除已注册的目标地址信息比较耗时)

 

七、创建已连接的套接字

1.使用connect函数向UDP套接字注册

        使用connect函数,向UDP套接字注册目标IP和端口信息。注册完毕后可以使用sendto、recvfrom函数收发数据,还可以使用write、read函数进行通信。

 

2.示例代码

服务器端:

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

#define BUF_SIZE 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int servSock = -1;
    char message[BUF_SIZE] = {0};
    int strLen = -1;
    socklen_t clntAddrSize;
    struct sockaddr_in servAddr, clientAddr;

    if(2 != argc)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_DGRAM, 0);
    if(-1 == servSock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error");

    while(1)
    {
        clntAddrSize = sizeof(clientAddr);
        strLen = recvfrom(servSock, message, BUF_SIZE, 0, (struct sockaddr*)&clientAddr, &clntAddrSize);
        sendto(servSock, message, strLen, 0, (struct sockaddr*)&clientAddr, clntAddrSize);
    }

    close(servSock);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端:

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

#define BUF_SIZE 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int sock = -1;
    char message[BUF_SIZE] = {0};
    int strLen = -1;
    socklen_t addrSize;
    struct sockaddr_in servAddr, fromAddr;

    if(3 != argc)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr));

    while(1)
    {
        fputs("Input message(q to quit):", stdout);
        fgets(message, sizeof(message), stdin);
        if(!strcmp(message, "q\n")||!strcmp(message, "Q\n"))
            break;

        write(sock, message, sizeof(message));
        strLen = read(sock, message, sizeof(message) - 1);

        message[strLen] = 0;
        printf("Message from server: %s\n", message);
    }

    close(sock);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

 操作:

1)服务器端:gcc main.c -o uechoServer.out,运行:./uechoServer.out 9190

2)客户端:gcc main.c -o uechoClient.out,运行:./uechoClient.out 127.0.0.1 9190

Input message(q to quit):123
Message from server: 123

Input message(q to quit):456
Message from server: 456

Input message(q to quit):q

分析:

       使用已连接UDP套接字后,可以使用write、read函数进行收发数据。        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值