Linux下UDP编程

一.概念介绍

1.socket 是什么?

socket(套接字)本质上是一个抽象的概念,它是一组用于网络通信的 API,提供了一种统一的接口,使得应用程序可以通过网络进行通信。在不同的操作系统中,socket 的实现方式可能不同,但它们都遵循相同的规范和协议,可以实现跨平台的网络通信。

2.socket实现通信的原理是基于网络协议栈。

协议栈是一个由多个层次协议组成的网络协议体系结构,它负责对数据进行封装和解封装,并确保数据能够在网络上正确传输。

当应用程序通过 socket 发送数据时,操作系统会将数据传递给协议栈的上层协议,该协议会对数据进行封装并添加一些必要的信息,例如目标 IP 地址和端口号等。然后将封装后的数据传递给下一层协议,直到数据最终被封装成一个网络包并通过网络发送到目标主机。

当目标主机收到网络包后,协议栈会对数据进行解封装,并将数据传递给操作系统中的套接字。如果该套接字是一个监听套接字,操作系统会创建一个新的套接字来处理连接请求,并将新的套接字加入到协议栈中。如果该套接字是一个已连接套接字,操作系统会将数据传递给应用程序处理。

3.socket 是一个系统接口函数,由操作系统提供,用于实现网络编程的功能。通过 socket 函数,应用程序可以创建套接字、绑定地址、监听连接、发送和接收数据等操作,从而实现网络通信。

二.API介绍

1.创建套接字的函数是socket()

int socket(int domain, int type, int protocol);

/*

  • 其中 "int domain"参数表示套接字要使用的协议簇,协议簇的在"linux/socket.h"里有详细定义,常用的协议簇:

AF_UNIX(本机通信)

AF_INET(TCP/IP -- IPv4)

AF_INET6(TCP/IP -- IPv6)

  • 其中 "type"参数指的是套接字类型,常用的类型有:

SOCK_STREAM(TCP流)

SOCK_DGRAM(UDP数据报)

SOCK_RAW(原始套接字)

  • 最后一个 "protocol"一般设置为"0",也就是当确定套接字使用的协议簇和类型时,这个参数的值就为0,但是有时候创建原始套接字时,并不知道要使用的协议簇和类型,也就是domain参数未知情况下,这时protocol这个参数就起作用了,它可以确定协议的种类。

socket是一个函数,那么它也有返回值,当套接字创建成功时,返回套接字,失败返回"-1",错误代码则写入"errno"中。

*/

// 实例

#include <sys/types.h>

#include <sys/socket.h>

#include <linux/socket.h>

int sock_fd_tcp;

int sock_fd_udp;

sock_fd_tcp = socket(AF_INET, SOCK_STREAM, 0); // 创建tcp通讯的套接字

sock_fd_udp = socket(AF_INET, SOCK_DGRAM, 0); // 创建udp通讯的套接字

if(sock_fd_tcp < 0) {

perror("TCP SOCKET ERROR!\n");

exit(-1);

}

if(sock_fd_udp < 0) {

perror("UDP SOCKET ERROR!\n");

exit(-1);

}

2.地址与端口设置的结构体 sockaddr_in

#include <netinet/in.h>

struct sockaddr_in{

unsigned short sin_family;

unsigned short int sin_port;

struct in_addr sin_addr;

unsigned char sin_zero[8];

};

struct in_addr{

unsigned long s_addr; // 这是一个无符号32位整数,用于存储IPv4地址

};

/*

sin_family表示地址类型,对于基于TCP/IP传输协议的通信,该值只能是AF_INET;

sin_prot表示端口号,例如:21 或者 80 或者 27015,总之在0 ~ 65535之间;

sin_addr表示32位的IP地址,例如:192.168.1.5 或 202.96.134.133;

sin_zero表示填充字节,一般情况下该值为0;

Socket数据的赋值实例:

*/

// 实例

struct sockaddr_in addr;

memset(&addr, 0, sizeof(addr)); // 将结构体清零 主要是sin_zero表示填充字节为零

addr.sin_family = AF_INET; //(TCP/IP -- IPv4)

addr.sin_port = htons(port_out); // 绑定端口号 htons将一个无符号短整型数值转换为网络字节序

addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定IP htonl就是把ip字节顺序转化为网络字节顺序

// INADDR_ANY 泛指机器所有的IP因为有些电脑不只有一个网卡;INADDR_ANY 是一个宏,表示"任意地址",通常用于服务器在绑定套接字时指定其愿意监听的地址。通常被赋值为 0.0.0.0 的网络字节序表示。

3.把名字和套接字相关联 bind()

int bind( int sockfd, const struct sockaddr * addr, socklen_t * addrlen);

/*

当用socket()函数创建套接字以后,套接字在名称空间(网络地址族)中存在,但没有任何地址给它赋值。bind()把用addr指定的地址赋值给用文件描述符代表的套接字sockfd。addrlen指定了以addr所指向的地址结构体的字节长度。一般来说,该操作称为"给套接字命名"。

通常,在一个SOCK_STREAM套接字接收连接之前,必须通过bind()函数用本地地址为套接字命名。

*/

// 实例

#include <sys/types.h>

#include <sys/socket.h>

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

struct sockaddr_in addr;

socklen_t addr_len = sizeof(addr);

if (bind(sockfd, (struct sockaddr*)&addr, addr_len) == -1){

printf("Failed to bind socket on port %d\n", port_out);

close(sockfd);

return false;

}

4.接收消息的函数recvfrom()

int recvfrom(int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, socklen_t * addrlen);

/*

recvfrom: 用于接收数据

  • sockfd:用于接收UDP数据的套接字;

  • buf:保存接收数据的缓冲区地址;

  • len:可接收的最大字节数(不能超过buf缓冲区的大小);

  • flags:可选项参数,若没有可传递0;

  • src_addr:存有发送端地址信息的sockaddr结构体变量的地址;

  • addrlen:保存参数 src_addr的结构体变量长度的变量地址值。

*/

5.发送消息的函数sendto()

int sendto(int sockfd, const void * buf, size_t len, int flags, const struct sockaddr * dest_addr, socklen_t addrlen);

/*

sendto:用于发送数据

  • sockfd:用于传输UDP数据的套接字;

  • buf:保存待传输数据的缓冲区地址;

  • len:带传输数据的长度(以字节计);

  • flags:可选项参数,若没有可传递0;

  • dest_addr:存有目标地址信息的 sockaddr 结构体变量的地址;

  • addrlen:传递给参数 dest_addr的地址值结构体变量的长度。

*/

注意:UDP套接字不会保持连接状态,每次传输数据都要添加目标地址信息,这相当于在邮寄包裹前填写收件人地址。

三.代码示例

1.udp server

#include<sys/select.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<arpa/inet.h>

#include<netinet/in.h>

#include <cstdlib>

#include <cstdio>

#include <cstring>

#include <iostream>

int main(){

//同一台电脑测试,需要两个端口

int port_in = 12321;

int port_out = 12322;

int sockfd;

// 创建socket

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if(-1==sockfd){

return false;

puts("Failed to create socket");

}

// 设置地址与端口

struct sockaddr_in addr;

socklen_t addr_len = sizeof(addr);

memset(&addr, 0, sizeof(addr));

addr.sin_family = AF_INET; // Use IPV4

addr.sin_port = htons(port_out); //

addr.sin_addr.s_addr = htonl(INADDR_ANY);

// Time out

struct timeval tv;

tv.tv_sec = 0;

tv.tv_usec = 200000; // 200 ms

setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));

// Bind 端口,用来接受之前设定的地址与端口发来的信息,作为接受一方必须bind端口,并且端口号与发送方一致

if (bind(sockfd, (struct sockaddr*)&addr, addr_len) == -1){

printf("Failed to bind socket on port %d\n", port_out);

close(sockfd);

return false;

}

char buffer[128];

memset(buffer, 0, 128);

int counter = 0;

while(1){

struct sockaddr_in src;

socklen_t src_len = sizeof(src);

memset(&src, 0, sizeof(src));

// 阻塞住接受消息

int sz = recvfrom(sockfd, buffer, 128, 0, (sockaddr*)&src, &src_len);

if (sz > 0){

buffer[sz] = 0;

printf("Get Message %d: %s\n", counter++, buffer);

}

else{

puts("timeout");

}

}

close(sockfd);

return 0;

}

2.udp client

#include<sys/select.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<arpa/inet.h>

#include<netinet/in.h>

#include <cstdlib>

#include <cstdio>

#include <cstring>

#include <iostream>

int main(){

int port_in = 12321;

int port_out = 12322;

int sockfd;

// 创建socket

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

if(-1==sockfd){

return false;

puts("Failed to create socket");

}

// 设置地址与端口

struct sockaddr_in addr;

socklen_t addr_len=sizeof(addr);

memset(&addr, 0, sizeof(addr));

addr.sin_family = AF_INET; // Use IPV4

addr.sin_port = htons(port_in); //

addr.sin_addr.s_addr = htonl(INADDR_ANY);

// Time out

struct timeval tv;

tv.tv_sec = 0;

tv.tv_usec = 200000; // 200 ms

setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(struct timeval));

// 绑定获取数据的端口,作为发送方,不绑定也行

if (bind(sockfd, (struct sockaddr*)&addr, addr_len) == -1){

printf("Failed to bind socket on port %d\n", port_in);

close(sockfd);

return false;

}

int counter = 0;

while(1){

addr.sin_family = AF_INET;

addr.sin_port = htons(port_out);

addr.sin_addr.s_addr = htonl(INADDR_ANY);

sendto(sockfd, "hello world", 11, 0, (sockaddr*)&addr, addr_len);

printf("Sended %d\n", ++counter);

sleep(1);

}

close(sockfd);

return 0;

}

3.测试结果

相关推荐
Biomamba生信基地5 分钟前
R语言基础| 功效分析
开发语言·python·r语言·医药
手可摘星河7 分钟前
php中 cli和cgi的区别
开发语言·php
vvw&12 分钟前
如何在 Ubuntu 22.04 上安装 Graylog 开源日志管理平台
linux·运维·服务器·ubuntu·开源·github·graylog
大哥_ZH16 分钟前
Linux umami在国产麒麟系统安装网站统计工具(只能上国内网站的系统)
linux·服务器
CT随30 分钟前
Redis内存碎片详解
java·开发语言
o(╥﹏╥)32 分钟前
在 Ubuntu 上安装 VS Code
linux·运维·vscode·ubuntu·vs
anlog40 分钟前
C#在自定义事件里传递数据
开发语言·c#·自定义事件
奶香臭豆腐1 小时前
C++ —— 模板类具体化
开发语言·c++·学习
不爱学英文的码字机器1 小时前
[Linux] Shell 命令及运行原理
linux·运维·服务器
晚夜微雨问海棠呀1 小时前
长沙景区数据分析项目实现
开发语言·python·信息可视化