UDP下的Socket编程

本文介绍UDP的特点及Socket编程基础知识,包括常用结构体与函数说明,并通过客户端和服务端实例展示数据交互过程。

UDP下Socket编程

1、UDP的基础知识

UDP的特点:

(1) UDP是无连接的,即发送数据之前不需要建立连接。
(2) UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制。
(3) UDP 是面向报文的。UDP 没有拥塞控制,很适合多媒体通信的要求。
(4) UDP 支持一对一、一对多、多对一和多对多的交互通信。
(5) UDP 的首部开销小,只有 8 个字节。

UDP报文的组成

在这里插入图片描述

UDP通信的流程比较简单,因此要搭建这么一个常用的UDP通信框架比较简单,如下图:

在这里插入图片描述

由以上框图可以看出,客户端要发起一次请求,仅仅需要两个步骤(socket和sendto),而服务器端也仅仅需要三个步骤即可接收到来自客户端的消息(socket、bind、recvfrom)。

2、 SOCKET编程下两个关键的结构体

sockaddr:

struct sockaddr{
    unsigned short sa_family; //通信协议类型族AF_xx
      char sa_data[14];  //14字节协议地址,包含该socket的IP地址和端口号
 };

sockaddr_in:

  struct sockaddr_in{
      short int sin_family; //通信协议类型族
      unsigned short int sin_port; //端口号
      struct in_addr sin_addr; //IP地址
      unsigned char si_zero[8];  //填充0以保持与sockaddr结构的长度相同
  };

两个结构体的区别与联系:

Socketaddr与socketaddr_in具有相同的长度,相同定义了地址族等参数,其实它们在一定情况下是通用的,使用Socketaddr兼容性好,使用socketaddr_in方则便数据引用。通常我们在赋值的时候用sockaddr_in,作为函数参数的时候用socket_addr.

3、UDP下的SOCKET编程涉及到的函数

socket()函数:创建一个socket

#include <sys/types.h>          
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

**参数domain:**设置网络通信的域,socket根据这个参数选择信息协议的族,如下列举所示:

Name Purpose

AF_UNIX, AF_LOCAL Local communication

AF_INET IPv4 Internet protocols //用于IPV4

AF_INET6 IPv6 Internet protocols //用于IPV6

AF_IPX IPX - Novell protocols

AF_NETLINK Kernel user interface device

AF_X25 ITU-T X.25 / ISO-8208 protocol

AF_AX25 Amateur radio AX.25 protocol

AF_ATMPVC Access to raw ATM PVCs

AF_APPLETALK AppleTalk

AF_PACKET Low level packet interface

AF_ALG Interface to kernel crypto API

对于该参数我们仅需熟记AF_INET和AF_INET6即可

参数type(只列出最重要的三个):

SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. //用于TCP

SOCK_DGRAM Supports datagrams (connectionless, unreliable messages ). //用于UDP

SOCK_RAW Provides raw network protocol access. //RAW类型,用于提供原始网络访问

参数protocol:置0即可

**函数返回值:**成功返回一个非负的文件描述符,失败则返回-1

**bind()函数:**该函数就说把创建的socket,对其进行赋值/绑定ip地址喝端口号

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);

**第一个参数sockfd:**正在监听端口的套接口文件描述符,通过socket获得

**第二个参数my_addr:**需要绑定的IP和端口

**第三个参数addrlen:**my_addr的结构体的大小

**返回值:**成功:0,失败:-1

sendto()函数:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

**第一个参数sockfd:**正在监听端口的套接口文件描述符,通过socket获得

**第二个参数buf:**发送缓冲区,往往是使用者定义的数组,该数组装有要发送的数据

**第三个参数len:**发送缓冲区的大小,单位是字节

**第四个参数flags:**填0即可

第五个参数dest_addr:指向接收数据的主机地址信息的结构体,也就是该参数指定数据要发送到哪个主机哪个进程

**第六个参数addrlen:**表示第五个参数所指向内容的长度

**返回值:**成功返回发送成功的数据的长度,失败返回-1

recvfrom()函数:

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr c*src_addr, socklen_t *addrlen);

**第一个参数sockfd:**正在监听端口的套接口文件描述符,通过socket获得

**第二个参数buf:**接收缓冲区,往往是使用者定义的数组,该数组装有接收到的数据

**第三个参数len:**接收缓冲区的大小,单位是字节

**第四个参数flags:**填0即可

**第五个参数src_addr:**指向发送数据的主机地址信息的结构体,也就是我们可以从该参数获取到数据是谁发出的

**第六个参数addrlen:**表示第五个参数所指向内容的长度

**返回值:**成功:返回接收成功的数据长度,失败: -1

4、实现UDP下客户端与服务端发送指定数据

client.c:指定服务端的ip地址、发送数据的时间(秒),每秒发送512字节

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<unistd.h>
#include <stdlib.h>
#include<arpa/inet.h>
#define SERVER_PORT 8888
#define BUFF_LEN 512



void udp_msg_sender(int fd, struct sockaddr* dst,int t)
{
    int count=0;
    int total=0;
    socklen_t len;//定义结构体长度
    struct sockaddr_in src;
    while(count<t)
    {
        char buf[BUFF_LEN] = "TEST UDP MSG!\n";
        len = sizeof(*dst);
        printf("client:%s\n",buf);  //打印自己发送的信息
        sendto(fd, buf, BUFF_LEN, 0, dst, len);//第1个参数套节字文件描述符,第2个参数,发送缓存区,第3个发送缓存区的大小,第4个填0即可,第5个指向服务器端的主机地址信息的结构体,第6个参数表示第5个参数所指向内容的长度
        memset(buf, 0, BUFF_LEN);//初始化缓存区
        recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&src, &len);  //接收来自server的信息
        printf("server:%s\n",buf);
        sleep(1);  //一秒发送一次消息
	count++;

    }
    
}

/*
    client:
            socket-->sendto-->revcfrom-->close
*/

int main(int argc, char* argv[])
{
    int time= atoi(argv[1]);

    int client_fd;
    int total=time*512;
    struct sockaddr_in ser_addr;//服务器端的结构体

    client_fd = socket(AF_INET, SOCK_DGRAM, 0);//建立客户端的套节字
    if(client_fd < 0)
    {
        printf("create socket fail!\n");
        return -1;
    }

    memset(&ser_addr, 0, sizeof(ser_addr));
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_addr.s_addr = inet_addr(argv[2]);//将一个点分十进制的IP转换成一个长整数型数
//    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //在本机上可以使用该行,采用cs模式将改行注释掉,注意网络序转换
    ser_addr.sin_port = htons(SERVER_PORT);  //注意网络序转换

    udp_msg_sender(client_fd, (struct sockaddr*)&ser_addr,time);
    printf("%d s共发送%dbyte数据",time,total);
    close(client_fd);

    return 0;
}

server.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include<unistd.h>
#define SERVER_PORT 8888
#define BUFF_LEN 1024

void handle_udp_msg(int fd)
{
    char buf[BUFF_LEN];  //接收缓冲区,1024字节
    socklen_t len;//结构体长度
    int count;
    struct sockaddr_in clent_addr;  //clent_addr用于记录发送方的地址信息
    while(1)
    {
        memset(buf, 0, BUFF_LEN);//将buf 所指向的BUFF_LEN的内存单元用一个“整数” 0替换,即对buf初始化
        len = sizeof(clent_addr);
	/*recvform()用来接收远程主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间, BUFF_LEN为可接收数据的最大长度. 参数flags 一般设0, 其他数值定义请参考recv(). (struct sockaddr*)&clent_addr 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). len为sockaddr 的结构长度.成功则返回接收到的字符数, 失败则返回-1 */

        count = recvfrom(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, &len);  //recvfrom是拥塞函数,没有数据就一直拥塞
        if(count == -1)
        {
            printf("recieve data fail!\n");
            return;
        }
        printf("client:%s\n",buf);  //打印client发过来的信息
        memset(buf, 0, BUFF_LEN);//初始化buff缓存区
        sprintf(buf, "I have recieved %d bytes data!\n", count);  //回复client的消息放到buf
        printf("server:%s\n",buf);  //打印自己发送的信息给client的消息
//sendto()用来将数据由指定的socket 传给对方主机,返回传送的字节数
        sendto(fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, len);  //发送信息给client,注意使用了clent_addr结构体指针

    }
}


/*
    server:
            socket-->bind-->recvfrom-->sendto-->close
*/

int main(int argc, char* argv[1])
{ 
    int server_fd, ret;
    struct sockaddr_in ser_addr;//描述服务端的套节字结构体

    server_fd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET:IPV4;SOCK_DGRAM:UDP
    if(server_fd < 0)
    {
        printf("create socket fail!\n");
        return -1;
    }

    memset(&ser_addr, 0, sizeof(ser_addr));//初始化服务端套节字结构体
    /*
     * 服务端结构体成员变量(ip、端口)赋值
     * htonl():将主机的无符号长整形数转换成网络字节顺序
     * htons():   将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian)
     */
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); //IP地址,需要进行网络序转换,INADDR_ANY:本地地址
    ser_addr.sin_port = htons(SERVER_PORT);  //端口号,需要网络序转换

    ret = bind(server_fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));//将套节字与赋值后的套接字进行绑定
    if(ret < 0)
    {
        printf("socket bind fail!\n");
        return -1;
    }

    handle_udp_msg(server_fd);   //处理接收到的数据

    close(server_fd);
    return 0;
}

查看服务端的ip地址:

在这里插入图片描述

开启服务端:

在这里插入图片描述

客户端进行连接并发送消息:

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值