socket(套接字)实现udp通信
udp通信
- 储备知识
- 网络字节序
- udp使用的接口
- sockaddr结构
- 简单的udp通信
- 优化服务器
储备知识
源ip地址和目的ip地址
我们先来看个例子:
如果当女儿国国王问你上一站从何而来,下一站去往何处?唐僧就会说我上一站从XXX来下一站到XXX。唐僧总是有2套说辞。源ip地址就像是唐僧的上一站,目的ip就是下一站的地址。
源ip地址:就是发送数据包的那个电脑的IP地址。
目的ip地址: 就是想要发送到的那个电脑的IP地址。
端口号
那我们有了ip地址就能通信了吗?例如QQ发消息,我们有了ip地址能够把信息发给对方的机器上,但是我们还需要有一个其他的标识来区分出这个数据交给哪个程序来进行解析。
下面来简单认识一下端口号:
- 端口号是是一个2字节16位的整数
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理
- 一个端口号只能被一个进程占用
ip来标识主机,端口号标识进程,ip+端口号就可以标识全网的唯一进程
,我们就可以知道数据要交给哪个程序解析了。一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定。
源端口号和目的端口号
和上面的源ip地址、目的ip地址一样的,传输层协议的数据段中有2个端口号,源端口号:数据是谁发的,目的端口号:要发给谁
udp协议:先简单认识一下,后面有详细的讲解
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
网络字节序
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
下面有一批接口可以使用:
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htonl(uint16_t hostshort);
uint32_t htonl(uint32_t netlong);
uint16_t htonl(uint16_t netshort);
详细解释:
- h表示host,n表示network,l表示32位长整数,s表示16位短整数
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
udp使用的接口
先来认识udp的一套接口:
1.创建socket
int socket(int domain, int type, int protocol);
参数说明:
domain:协议域又称协议家族,协议族决定了socket的地址类型,我们使用ipv4进行通信,使用AF_INET
type:套接字类别,有流式套接字和数据报套接字,upd使用的是SOCK_DGRAM
protocol:协议指定与套接字一起使用的特定协议。默认使用0即可。
返回值:
成功则返回socket文件描述符,错误返回-1.
为什么返回文件描述符?
Linux中说一切皆文件,为了表示和区分已经打开的文件,Linux 会给每个文件分配一个 ID,这个 ID 就是一个整数,被称为文件描述符(File Descriptor)。例如:
通常用 0 来表示标准输入文件(stdin),它对应的硬件设备就是键盘;
通常用 1 来表示标准输出文件(stdout),它对应的硬件设备就是显示器。
Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。
所以网络连接也是一个文件,也要有文件描述符
,所以它的返回值是文件描述符。
2.bind(绑定函数)
函数原型:
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
参数说明:
socket:需要绑定的socket
addr:存放了服务端用于通信的地址和端口。
addrlen:表示addr结构体的大小。
返回值:成功返回0,失败返回-1
3.recvfrom(接收)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr*src_addr, socklen_t *addrlen);
参数说明:
- sockfd:接收的socket
- buf:接收的数据放在哪
- len:接收多大的数据
- flags:需不需要阻塞,默认填0
- src_addr:发送数据的客户端地址信息的结构体
- addrlen:指向结构体长度值
注意后两个参数是输出参数,其中addrlen既是输入又是输出参数,即值-结果参数,需要在调用时,指明src_addr的长度。另外,如果不关心数据发送端的地址,可以将后两者均设置为NULL。
返回值:如果正确接收返回接收到的字节数,失败返回-1.
4.sendto(发送数据)
函数原型:
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:指明buf的长度
- flag:一般设置为0
- dest_addr:表示目的机的地址和端口号信息
- addrlen:常被赋值为sizeof(struct sockaddr)
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同。上面的接口都是使用sockaddr结构,先来看一下它的结构:
它们的接口使用的都是struct sockaddr,是为了使用一套接口就可以完成通信。但是我们在使用的时候使用的是第2个,因为我们实现通信就要传ip地址和端口号,哪个8字节填充是为了内存对齐,不用管它。那怎么区别它们呢?
只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好
处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。
sockaddr_in结构
下面的填充的不用管。
in_addr结构
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。
简单的udp通信
知道了上面的接口,下面我们使用这些接口写一个简单的udp通信。
服务器端:
我们让服务器一直读客户端发来的信息,并给服务器返回。整体的逻辑:先创建套接字,绑定,接收客户端信息,给客户端返回信息。
udpServer.hpp:
1 #pragma once 2 #include<iostream> 3 #include<cstdio> 4 #include<string> 5 #include<unistd.h>6 #include<sys/socket.h>7 #include<stdlib.h> 8 #include<sys/types.h> 9 #include<netinet/in.h>10 #include<arpa/inet.h> 11 12 class udpServer 13 { 14 private: 15 std::string ip;16 int port; 17 int sock; 18 public: 19 udpServer(std::string _ip="127.0.0.1",int _port = 8088)20 :ip(_ip) 21 ,port(_port) 22 {} 23 void initServer()24 { 25 sock = socket(AF_INET,SOCK_DGRAM,0);26 std::cout<<"sock:"<<sock<<std::endl;27 struct sockaddr_in local; 28 local.sin_family = AF_INET; 29 local.sin_port = htons(port);//主机序列转成网络序列30 local.sin_addr.s_addr = inet_addr(ip.c_str()); 31 32 //开始绑定 33 //绑定失败就直接退出 34 if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)35 { 36 std::cerr<<"bind error"<<std::endl; 37 exit(1);38 39 }40 }41 void start()42 {43 char msg[64];44 for(;;)45 {46 msg[0] = '\0';47 //从远端接收48 struct sockaddr_in end_point;49 socklen_t len = sizeof(end_point);50 ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);51 if(s > 0 )52 {53 msg[s] = '\0';54 std::cout<<"client:"<<msg<<std::endl;55 std::string echo_string = msg;56 echo_string += "[srever echo]";57 sendto(sock,echo_string.c_str(),echo_string.size(),0,58 (struct sockaddr*)&end_point,len);59 }60 }61 }62 ~udpServer()63 {64 close(sock);65 }66 };
udpServer.cc
1 #include"udpServer.hpp"2 3 int main()4 {5 udpServer *us = new udpServer();6 us->initServer();7 us->start();8 delete us; 9 return 0;10 }
客户端发消息给服务器端,服务器端返回echo server
udpClient,hpp
1 #pragma once 2 #include<iostream>3 #include<cstdio>4 #include<unistd.h>5 #include<sys/socket.h>6 #include<stdlib.h>7 #include<sys/types.h>8 #include<arpa/inet.h>9 #include<string>10 #include<netinet/in.h>11 class udpClient12 {13 private:14 std::string ip;15 int port;16 int sock;17 public:18 udpClient(std::string _ip="127.0.0.1",int _port = 8088)19 :ip(_ip)20 ,port(_port)21 {}22 void initClient()23 {24 sock = socket(AF_INET,SOCK_DGRAM,0);25 std::cout<<"sock:"<<sock<<std::endl;26 27 }28 void start()29 {30 31 std::string msg;32 struct sockaddr_in peer;33 peer.sin_family = AF_INET;34 peer.sin_port = htons(port);35 peer.sin_addr.s_addr = inet_addr(ip.c_str());36 for(;;)37 {38 std::cout<<"please enter:";39 std::cin>>msg;40 if(msg == "quit")41 {42 break;43 }44 sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));45 char echo[128];46 ssize_t s = recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);47 if(s > 0)48 {49 echo[s] = 0;50 std::cout<<"server:"<<echo<<std::endl;51 }52 }53 }54 ~udpClient()55 {56 close(sock);57 }58 };
udpClient.cc
1 #include"udpClient.hpp"2 3 int main()4 {5 udpClient uc;6 uc.initClient();7 uc.start(); 8 return 0;9 }
下面开始通信
我们可以看到通信成功了,服务器给我们返回echo server。
优化服务器
12 class udpServer13 {14 private:15 // std::string ip;16 int port;17 int sock;18 public:19 udpServer(int _port = 8088)20 //:ip(_ip)21 :port(_port)22 {}23 void initServer()24 {25 sock = socket(AF_INET,SOCK_DGRAM,0);26 std::cout<<"sock:"<<sock<<std::endl;27 struct sockaddr_in local;28 local.sin_family = AF_INET;29 local.sin_port = htons(port);//主机序列转成网络序列30 //local.sin_addr.s_addr = inet_addr(ip.c_str());31 local.sin_addr.s_addr = INADDR_ANY;
将sin.addr设为INADDR_ANY;
运行服务器:
用命令:netstat -nlup
查看udp协议相关的统计数据,一般用于检验本机各端口的网络连接情况
转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。所以出现INADDR_ANY,你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。
客户端和服务器端都加上命令行参数
服务器端:
1 #include"udpServer.hpp"2 3 void Usage(std::string proc)4 {5 std::cout<<"Usage: "<<proc<<"port"<<std::endl;6 }7 int main(int argc,char* argv[])8 {9 if(argc != 2)10 {11 Usage(argv[0]);12 exit(1);13 }14 udpServer *us = new udpServer(atoi(argv[1])); 15 us->initServer();16 us->start();17 delete us;18 return 0;19 }
客户端:
1 #include"udpServer.hpp"2 3 void Usage(std::string proc)4 {5 std::cout<<"Usage: "<<proc<<"port"<<std::endl;6 }7 int main(int argc,char* argv[])8 {9 if(argc != 2)10 {11 Usage(argv[0]);12 exit(1);13 }14 udpServer *us = new udpServer(atoi(argv[1])); 15 us->initServer();16 us->start();17 delete us;18 return 0;19 }
效果演示:
可以绑定127.0.0.1本地环回网,和ifconfig查看的ip都可以进行通信。
小结:
客户端不需要绑定
。为什么呢?
- 客户端自己bind容易冲突,如果有多个客户端进行bind,我们自己不知道哪个端口号被bind,肯定会有客户端启动失败。
- 客户端需要唯一性,但是不需要明确是哪个端口号,所以客户端由操作系统自己选择绑定,因为系统知道哪个端口被bind,哪个端口没被bind。
本地环回:
通常用来进行网络通信代码的本地测试,一般跑通,本地环境以及代码基本没有问题。
也可以远程通信,代码链接,点击直达。
有客户端代码就可以和博主远程通信了。
socket(套接字)实现udp通信相关推荐
- Socket 套接字之UDP通信
前言: UDP 是一个面向无连接的,不安全的,报式传输层协议,udp 的通信过程默认也是阻塞的. UDP通信不需要建立连接 ,因此不需要进行 connect () 操作 UDP通信过程中,每次都需要指 ...
- TCP与UDP协议,socket套接字编程,通信相关操作
文章目录 TCP与UDP协议 TCP协议 ==三次握手== ==四次挥手== UDP协议 TCP与UDP的区别 应用层 socket套接字 代码优化 循环通信 半连接池 粘包问题 TCP与UDP协议 ...
- 【网络编程】Socket套接字;UDP数据报套接字编程;TCP流套接字编程
文章目录 1. 什么是网络编程 2. 网络编程中的基本概念 3. Socket套接字 4 UDP数据报套接字编程 4.1 客户端服务器交互流程 4.2 UDP版本的回显服务 4.3 英译汉服务 5. ...
- TCP与UDP协议、socket套接字编程、通信相关操作(cs架构软件)、TCP黏包问题及解决思路
OSI七层协议 传输层 1.PORT协议:前面讲过 2.TCP协议与UDP协议:规定了数据传输所遵循的规则(数据传输能够遵循的协议有很多,TCP和UDP是较为常见的两个) TCP协议 基于TCP传输数 ...
- Python网络编程——socket套接字实现UDP/TCP信息传输
socket套接字 socket(简称 套接字) ,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来 ...
- 计算机网络(二) | 网络编程基础、Socket套接字、UDP和TCP套接字编程
目录 一.网络编程基础 1.1 为什么需要网络编程 1.2 什么是网络编程 1.3 网络编程中的基本概念 二.Socket套接字 2.1 概念 2.2 分类 2.3 Java数据报套接字通信模型 2. ...
- 套接字、UDP通信、TCP通信、TCP/IP协议簇
一.套接字(socket) 1.英语单词socket:n.插座:穴:v.插入插座 2.套接字就是源IP地址和目的IP地址.源端口号和目的端口号的组合,是通过传输层进行通信的.IP指定电脑,端口指定某一 ...
- 套接字、UDP通信、TCP通信、TCP\IP协议簇
一.套接字(socket) 1.英语单词socket:n.插座:穴:v.插入插座 2.套接字就是源IP地址和目的IP地址.源端口号和目的端口号的组合,是通过传输层进行通信的.IP指定电脑,端口指定某一 ...
- 网络编程-Socket套接字(TCP、UDP、广播和组播通信)
socket套接字 socket是一个编程接口(网络编程接口) 作用是用来实现网络上不同的主机的应用进程之间进行双向通信 套接字是一种特殊的文件描述符 也就意味着我们使用套接字实现网络通信的时候可以用 ...
最新文章
- 双屏全屏跳回到主屏_双屏双倍乐趣?华硕灵耀X2 Duo笔记本评测
- WinXP下At,Schtask和Eventtriggers命令实例(一)
- 在大型软件中用Word做报表: 书签的应用
- stm32IAP代码升级小结
- 《JavaWeb从入门到改行》注册时向指定邮箱发送邮件激活
- MySQL数据库面试题
- Kafka JMX监控报错 Failed to get broker metrics for BrokerIdentity(128,192.168.2.128,9999,true,false,Map
- win7旗舰版系统关闭自动更新功能教程
- 文档预览 OfficeWebViewer:在浏览器中查看Office文档
- HttpHandler和HttpModule 心得介绍
- ios唤起键盘后,页面不收回导致元素错位的问题.(譬如固定在底部的自定义键盘等)...
- pylint警告: An attribute defined in json.encoder line 158 hides this methodpylint(method-hidden)
- Word AddIn编译出现LINK2001 _main
- centos7下载及安装步骤
- shibor与沪深300指数的相关性图示
- Common Language Extension(CLE) 介绍
- C++ fstream 在文件末尾写入问题
- 【经济学_04】晨读:保险的实质 特效药 医疗膨胀 保险的发明
- 微知识|人工智能 第一期
- IDEA配置关联Git
热门文章
- Mysql的Derived派生表查询优化
- 从巴菲特10亿美元买苹果股 看“硬件公司”的低谷期
- Kerberos原理--经典对话
- ❤️【GaussDB精品课第6期】数据库和应用迁移UGO服务❤️
- 「PAT乙级真题解析」Basic Level 1089 狼人杀-简单版 (问题分析+完整步骤+伪代码描述+提交通过代码)
- Java 多线程之闭锁-CountDownLatch
- Windows 上安装 PostgreSQL详细图文教程
- linux下载软件说依赖关系不足,在依赖关系可以满足的情况下也会提示“依赖关系不满足”...
- 第2章-3 阶梯电价
- unity全栈开发是什么意思_前端所谓的全栈和大前端有什么区别?