linux udp socket

发布时间 2023-08-30 11:30:41作者: 秋来叶黄

服务端源码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define BUFF_SIZE 1024
int main()
{
    int sock = 0;
    int recvlen = 0;
    // 接收数据缓冲区
    char buff[BUFF_SIZE] = {0};
    struct sockaddr_in addr;
    // 初始化地址结构体
    memset(&addr, 0, sizeof(addr));
    // 设置地址协议簇为AF_INET,也就是ipv4
    addr.sin_family = AF_INET;
    // 设置监听端口
    addr.sin_port = htons(12345);
    socklen_t addrlen;

    // 创建socket,AF_INET表示ipv4,SOCK_DGRAM表示udp
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        perror("socket error");
        exit(-1);
    }

    // 把sock绑定到指定的端口上
    if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("bind error");
        exit(-1);
    }

    struct sockaddr_in client_addr;
    while (1)
    {
        memset(buff, 0, BUFF_SIZE);
        // 接收数据
        // sock是需要接收的socket
        // buff用来保存接收的数据
        // 1024表示buff有多大,最多只能接收这么多
        // client_addr用来保存发送数据段的地址信息
        // addrlen表示地址信息的长度,因为ipv4和ipv6地址长度不一致,所以需要指明有多长
        // recvlen表示接收了多少字节的数据
        recvlen = recvfrom(sock, buff, BUFF_SIZE, 0, (struct sockaddr *) &client_addr, &addrlen);
        if (recvlen > 0)
        {
            printf("%s\n", buff);
            // 把数据发送出去
            // 发送还是通过sock
            // buff是发送的数据
            // recvlen是发送的数据长度
            // client_addr是需要发送的ip地址和端口信息,这里用上面接收到的地址,就相当于再发送回去
            // addrlen用来描述client_addr的大小
            sendto(sock, buff, recvlen, 0, (struct sockaddr *) &client_addr, addrlen);
        }
    }
}

客户端源码

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/udp.h>
#define BUFF_SIZE 1024
int main(int argc, char *argv[])
{
    int sock_r;
    struct sockaddr_in svr_addr;
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[BUFF_SIZE] = {};

    // 创建socket
    if ((sock_r = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket error");
        exit(-1);
    }

    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(12345);
    svr_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    while (1)
    {
        memset(buf, 0, BUFF_SIZE);
        printf("input: ");
        fgets(buf, BUFF_SIZE, stdin);
        // 向svr_addr指定的地址发送数据
        sendto(sock_r, buf, BUFF_SIZE, 0, (struct sockaddr *) &svr_addr, addrlen);

        // 从sock_r接收数据
        ret = recvfrom(sock_r, buf, BUFF_SIZE, 0, (struct sockaddr *) &svr_addr, &addrlen);
        printf("%s\n", buf);
    }

    close(sock_r);
    return 0;
}

我们可以看到服务端使用了bind函数,而客户端确可以直接发送数据,这是为什么呢?因为socket创建好之后,系统也不知道你需要监听哪个端口。服务端是被动接收数据,必须bind后,系统才知道这个socket监听的哪个端口,不然recvfrom就不知道从哪里获取数据。

而客户端是先发送数据,发送数据时系统会随机分配一个端口给客户端的socket使用,也就是底层帮我们做了绑定。

send和sendto

https://man7.org/linux/man-pages/man2/send.2.html

       #include <sys/socket.h>

       ssize_t send(int sockfd, const void buf[.len], size_t len, int flags);
       ssize_t sendto(int sockfd, const void buf[.len], size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
       ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

linux发送数据有多个api,除了上面的这些,还有一个write。在官方文档中介绍的很清楚,send是用在connect之后,因为send没有指定发送的地址,必须先使用connect为socket确定发送端地址信息。

由于sendto指定了地址信息,如果使用connect后,再使用sendto,并且dest_addr不为NULL,地址信息与connect的不匹配,就会报错,因为系统不知道你到底要发送到哪个地址信息。

write大部分用作文件操作,不过在linux下一切皆文件,所以也可以操作socket,与send功能相同,只是少了flags参数。

sendmsg会发送一个msghdr结构体,可以对发送的socket做更多控制。

客户端源码

使用send

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/ip.h>
#include <linux/udp.h>
#define BUFF_SIZE 1024
int main(int argc, char *argv[])
{
    int sock_r;
    struct sockaddr_in svr_addr;
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[BUFF_SIZE] = {};

    // 创建socket
    if ((sock_r = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket error");
        exit(-1);
    }

    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(12345);
    svr_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 增加了connect的操作,把svr_addr描述的地址信息与sock_r绑定
    connect(sock_r, (struct sockaddr*)&svr_addr, sizeof(svr_addr));
    while (1)
    {
        memset(buf, 0, BUFF_SIZE);
        printf("input: ");
        fgets(buf, BUFF_SIZE, stdin);
        // 向svr_addr指定的地址发送数据
        //sendto(sock_r, buf, BUFF_SIZE, 0, (struct sockaddr *) &svr_addr, addrlen);
        // 直接使用send发送数据,不需指定地址信息
        send(sock_r, buf, BUFF_SIZE, 0);

        // 从sock_r接收数据
        ret = recvfrom(sock_r, buf, BUFF_SIZE, 0, (struct sockaddr *) &svr_addr, &addrlen);
        printf("%s\n", buf);
    }

    close(sock_r);
    return 0;
}