文章目录

  • 一:回顾
  • 二:彻底了解套接字和struct socket结构
    • (1)一切皆文件-文件描述符-套接字描述符
    • (2)struct socket结构
      • A:struct socket结构体作用
      • B:struct socket结构体详解
  • 三:socket接口(UDP)和sockaddr结构
    • (1)socket常用API接口1(UDP)
    • (2)sockaddr结构
      • A:struct sockaddr结构
      • B:struct sockaddr_in
  • 四:UDP通信示例
    • (1)UDP通信
    • (1)sendto和recvfrom接口
    • (2)代码
    • (3)效果

一:回顾

前文讲过,套接字分为流式套接字(SOCKET_STREAM)和数据报套接字(SOCKET_DGRAM),他们所采用的协议分别为TCPUDP,相应的对应的Socket编程就是TCP套接字编程UDP套接字编程

相比于UDP而言,TCP保证了数据的可靠传输,所以它比UDP就复杂一点,从下面的流程图中也可以看出来

  • 流式套接字

  • 数据包套接字

二:彻底了解套接字和struct socket结构

(1)一切皆文件-文件描述符-套接字描述符

下图是Linux内核中关于socket的数据结构还有后续我们再说编程时的一些API接口。

你可能注意到了一个非常熟悉的地方,struct file* file,这不就是文件吗?是的没错,如果再深入理解一点,其实套接字就是使用文件描述符和其它程序进行通讯的一种方式。我们知道,Linux系统在执行任何I/O的时候,都在和文件描述符打交道,而在Linux下,我们一直反复强调“一切皆文件的思想”,之前说过的屏幕都可以作为文件,那么现在接触的网卡也当然可以做文件

所以以后再进行网络通讯时,你会利用socket系统调用,它将返回套接字文件描述符,然后你会利用它再通过相应(如下)接口进行通信操作。
既然它是文件描述符,那就意味着你仍然可以使用read()和write()来进行通信,但是“术业有专攻”,网络的事情还是尽量使用它们对应的接口来操作。

(2)struct socket结构

A:struct socket结构体作用

用户使用socket系统调用编写程序时,通过套接字描述符完成相关操作

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

它对应的就是我们在上面说到的struct socket结构体

那么内核中为什么要有struct socket这样的结构体呢,以及它有什么作用呢?可以看下面这张图

所以内核中的进程可以通过该结构体来访问Linux内核中的传输层,网络层和数据链路层,也就是说struct socket是内核中的进程与内核中的网络系统的桥梁

B:struct socket结构体详解

这是一个基本的BSD socket,我们调用socket系统调用创建的各种不同类型的socket,开始创建的都是它,到后面**,各种不同类型的socket在它的基础上进行 各种扩展。struct socket是在虚拟文件系统上被创建出来的,可以把它看成一个文件,**是可以被安全地扩展的。下面是其完整定义:

struct socket {  socket_state            state;  unsigned long           flags;  const struct proto_ops *ops;  struct fasync_struct    *fasync_list;  struct file             *file;  struct sock             *sk;  wait_queue_head_t       wait;  short                   type;
};  

1: state用于表示socket所处的状态,是一个枚举变量,其类型定义如下:

该成员只对TCP socket有用,因为只有tcp是面向连接的协议,udp跟raw不需要维护socket状态。

typedef enum {  SS_FREE = 0,            //该socket还未分配  SS_UNCONNECTED,         //未连向任何socket  SS_CONNECTING,          //正在连接过程中  SS_CONNECTED,           //已连向一个socket  SS_DISCONNECTING        //正在断开连接的过程中
}socket_state;

2:ops是协议相关的一组操作集,结构体struct proto_ops的定义如下:

struct proto_ops {  int     family;  struct module   *owner;  int (*release)(struct socket *sock);  int (*bind)(struct socket *sock, struct sockaddr *myaddr, int sockaddr_len);  int (*connect)(struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags);  int (*socketpair)(struct socket *sock1, struct socket *sock2);  int (*accept)(struct socket *sock,struct socket *newsock, int flags);  int (*getname)(struct socket *sock, struct sockaddr *addr,int *sockaddr_len, int peer);  unsigned int (*poll)(struct file *file, struct socket *sock,  struct poll_table_struct *wait);  int (*ioctl)(struct socket *sock, unsigned int cmd, unsigned long arg);  int (*listen)(struct socket *sock, int len);  int (*shutdown)(struct socket *sock, int flags);  int (*setsockopt)(struct socket *sock, int level,  int optname, char __user *optval, int optlen);  int (*getsockopt)(struct socket *sock, int level,  int optname, char __user *optval, int __user *optlen);  int (*sendmsg)(struct kiocb *iocb, struct socket *sock,  struct msghdr *m, size_t total_len);  int (*recvmsg)(struct kiocb *iocb, struct socket *sock,  struct msghdr *m, size_t total_len, int flags);  int (*mmap)(struct file *file, struct socket *sock,struct vm_area_struct * vma);  ssize_t (*sendpage)(struct socket *sock, struct page *page,  int offset, size_t size, int flags);  };

其中协议栈总共定义了了三个strcut proto_ops类型的变量,分别myinet_stream_ops, myinet_dgram_ops, myinet_sockraw_ops,分别对应流式套接字,数据报套接字和原生套接字

3:type是socket的类型,对应的取值如下:

enum sock_type {  SOCK_DGRAM = 1,  //数据报套接字SOCK_STREAM = 2,  //流式套接字SOCK_RAW    = 3,  //原生套接字SOCK_RDM    = 4,  SOCK_SEQPACKET = 5,  SOCK_DCCP   = 6,  SOCK_PACKET = 10,
};

4:sk是网络层对于socket的表示,用户需要进行传参,让网络层采用TCP还是UDP

三:socket接口(UDP)和sockaddr结构

(1)socket常用API接口1(UDP)

所以我们要完成通讯,就要利用sockt结构体提供给我们的一些接口来实现。由于UDP只负责传送,所以相较于TCP而言它的接口少一点,所以这里只列出UDP中使用到的,但需要注意的是下面的接口对TCP也是通用的,只不过TCP相较于UDP的接口要多一点,还要扩展一点。

本节的模型就是一个服务端接受客户端发送的消息,然后回应客户端

1:创建 一个socket 文件描述符

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

他们三个参数的含义及选用如下

对于它的返回值其实前面我们已经说过了,本质是一个文件描述符

2:绑定地址信息

前文说过,IP地址+端口号唯一表示了全网的一个进程。服务端既然想要提供服务,那么必须要让客户端知道怎么找到自己。所以对于服务端我们需要填入ip地址和端口号,以便客户端可以找到自己。 当然客户端一般是不要绑定的,当数据返回给客户端时,具体要用哪一个端口,是操作系统决定的,如果我们人为去绑定,可能导致端口号的冲突

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

第一个参数很好理解,第二个参数和第三个参数就是我们下面要说到的sockaddr结构

(2)sockaddr结构

所以在绑定时,我们需要将ip和端口号一起封装在某个结构体中,然后传参到bind接口里面。它共有三种类型的结构体

你可能发现了,bind接口的形参给的是struct sockaddr*的指针,但是为什么这里有三种类型的结构呢。其实这样的设计主要是为了用更少的操作完成更多的事情。

在传参时,这三种结构体的前16位是不相同的,它就是通过这个来区分的,所以只要保证在对齐的情况下,就能用struct sockaddr*来接受不同的结构,这有点像C++中的切片操作

A:struct sockaddr结构

struct sockaddr为许多类型的套接字存储套接字地址信息,其结构如下

struct sockaddr {unsigned short sa_family; /* 地址家族, AF_xxx */char sa_data[14]; /*14字节协议地址*/
};

这个结构体是一个通用的地址信息结构,它并不是某一个具体的地址信息结构。所以我们不选择它,而选择struct sockaddr_int这种结构

B:struct sockaddr_in

该结构体如下

struct sockaddr_in {short int sin_family; /* 通信类型 */unsigned short int sin_port; /* 端口 */struct in_addr sin_addr; /* Internet 地址 */unsigned char sin_zero[8]; /* 与sockaddr结构的长度相同*/};

用这样结构可以很轻松的处理套接字地址的基本元素。

现在让我们回到bind接口,看一下它是如何传参的:当我们使用ipv4版本的洗衣,绑定地址信息的时候,需要填充struct sockadd_in结构体来保存服务器的ip和端口号

四:UDP通信示例

(1)UDP通信

UDP是面向非连接的协议,它不与对方建立连接,而是直接把数据报发给对方。UDP无需建立类如三次握手的连接,使得通信效率很高。因此UDP适用于一次传输数据量很少、对可靠性要求不高的或对实时性要求高的应用场景。

UDP服务端流程如下

  1. 使用函数socket(),生成套接字文件描述符
  2. 通过struct sockaddr_in 结构设置服务器地址和监听端口
  3. 使用bind() 函数绑定监听端口,将套接字文件描述符和地址类型变量(struct sockaddr_in )进行绑定;
  4. 接收客户端的数据,使用recvfrom() 函数接收客户端的网络数据;
  5. 向客户端发送数据,使用sendto() 函数向服务器主机发送数据;
  6. 关闭套接字,使用close() 函数释放资源;

UDP客户端流程如下

  1. 使用函数socket(),生成套接字文件描述符
  2. 通过struct sockaddr_in 结构设置服务器地址和监听端口
  3. 向服务器发送数据,sendto() ;
  4. 接收服务器的数据,recvfrom() ;
  5. 关闭套接字,close() ;

需要注意以下几点

  1. 服务器和客户端地址理应是不一样的,这里为了测试使用本地环回地址

  2. ip地址实际是点分十进制,每个部分算作1个字节,但是我们输入时往往是字符串,所以下面的接口可以将字符串转换为正确的IP地址,并且是网络字节序

  3. 如果需要将四字节序列转为点分十进制,则用char *inet_ntoa (struct in_addr);

  4. 本地环回:本地环回地址为127.0.0.1,这是一个测试IP。表示数据会完整的走一遍协议,但是是自己发自己收

  5. 对于服务端一般不指定某个ip,因为有可能会有很多个ip,如果指定了ip,服务端只能接受特定ip的数据。所以我们一般把ip设置为一个宏,也即INADDR_ANY表示绑定任意IP

(1)sendto和recvfrom接口

1:UDP发送函数

#include <sys/types.h>
#include <sys/socket.h>
int sendto(
int sockfd,//套接字描述符
const void* buf,//要发什么东西
size_t len,//期望发多长
,int flags,//阻塞还是非阻塞,一般设置为0
const struct sockaddr* dest_addr,//指向服务器的struct_in结构体(注意强转)
socklen_t addrlen//指的是上面结构体的长度
)

2:UDP接受函数

#include <sys/tyeps.h>
#include <sys/socket.h>
ssize_t recvfrom(
int sockfd,//套接字描述符
void* buf,//读取到放在哪?
size_t len,//期望读取多长
int flags.//没有数据读的时候挂起,默认设置为0表示阻塞等待
struct sockaddr* src_addr,
socklen_t* addrlen//保存那个客户端发给你的(这两个如果你不关心是谁发给你的,设置为null即可。)

(2)代码

udpServer.h

#include <iostream>
#include <cstdio>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;class udpServer
{private:int _port;//端口号int _sock;//套接字描述符public:udpServer(int port=8080):_port(port){}void initServer()//初始化服务器{_sock=socket(AF_INET,SOCK_DGRAM,0);//cout<<_sock<<endl;struct sockaddr_in local;//创建sockaddr_in结构//填充local.sin_family=AF_INET;//IPV4协议local.sin_port=htons(_port);//主机字节序转为网路字节序local.sin_addr.s_addr=INADDR_ANY;//绑定任意IP//绑定if(bind(_sock,(struct sockaddr*)&local,sizeof(local)) < 0){cerr << "绑定失败" <<endl;exit(1);}}void startServer(){char msg[64];for(;;)//服务器永不停机{msg[0]='\0';//清空缓冲区struct sockaddr_in end_point;//客户端的信息socklen_t len=sizeof(end_point);//输出和输入型参数,recvfrom和sendto都要用ssize_t ret=recvfrom(_sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);//接受,并把客户端的信息保存在结构体当中if(ret > 0){char bu[16];sprintf(bu,"%d",ntohs(end_point.sin_port));//把网络字节序转为主机字节序,端口号string cli=inet_ntoa(end_point.sin_addr);//把客户端的ip转为点分十进制cli+=":";cli+=bu;msg[ret]='\0';cout<<"服务端接受到消息:来自->"<<cli<<" "<< msg << endl;string respond="服务端回消息";sendto(_sock,respond.c_str(),respond.size(),0,(struct sockaddr*)&end_point,len);//服务器应答}}}~udpServer(){close(_sock);}
};

udpServer.cpp

#include "udpServer.h"int main(int argc,char* argv[])
{if(argc!=2)//判断是否传入端口号{cout<<"端口号未传入"<<endl;exit(1);}udpServer* ss=new udpServer(atoi(argv[1]));ss->initServer();ss->startServer();
}

udpClient.h

#include <iostream>
#include <string>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;class udpClient
{private:string _ip;//int _port;//客户端要保存服务器的IP和端口int _sock;//套接字描述符public:udpClient(string ip="127.0.0.1",int port=8080):_ip(ip),_port(port){//连接服务器,本地环回测试}void initClient()//初始化服务器{_sock=socket(AF_INET,SOCK_DGRAM,0);////客户端不需要绑定}void startClient(){string msg;//接受用户输入//发送给服务器struct sockaddr_in peer;peer.sin_family=AF_INET;peer.sin_port=htons(_port);peer.sin_addr.s_addr=inet_addr(_ip.c_str());for(;;){cout<<"【请输入:】";cin>>msg;if(msg=="quit")break;//如果用户输入退出,下线sendto(_sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));//客户端给服务端发送消息//服务器接受消息会返回信息char echo[128];ssize_t ret=recvfrom(_sock,echo,sizeof(echo)-1,0,nullptr,nullptr);//不关心服务器的地址if(ret > 0){echo[ret]='\0';cout<<"客户端受到回应"<< echo<< endl;}}}~udpClient(){close(_sock);}};

udpClient.cpp

#include "udpClient.h"int main(int argc,char* argv[])
{if(argc!=3){cout<<"服务器地址没有传入"<<endl;exit(1);}udpClient uu(argv[1],atoi(argv[2]));uu.initClient();//初始化客户端uu.startClient();//启动客户端
}

(3)效果

使用netstat nlup可以查看网络进程信息


1:本地环回测试

2:局域网IP

3:公网IP

2-3:套接字(Socket)编程之UDP通信,sockaddr,sockaddr_in,recvfrom,sendto相关推荐

  1. [深入浅出WP8.1(Runtime)]Socket编程之UDP协议

    13.3 Socket编程之UDP协议 UDP协议和TCP协议都是Socket编程的协议,但是与TCP协议不同,UDP协议并不提供超时重传,出错重传等功能,也就是说其是不可靠的协议.UDP适用于一次只 ...

  2. Linux下socket编程之UDP简单实现

    本文实现一个简单的UDP小例子,来说明Linux下socket编程之UDP的简单实现.本文主要包括三个部分:服务器端的实现,客服端的实现和通信测试.实现的功能:客服端发送一条消息给服务器端,服务器端把 ...

  3. 5.1 计算机网络之传输层(传输层提供的服务及功能概述、端口、套接字--Socket、无连接UDP和面向连接TCP服务)

    文章目录 1.传输层提供的服务及功能概述 2.传输层的寻址与端口 (1)端口的作用 (2)端口号的分类 (3)套接字 3.无连接UDP和面向连接TCP服务 1.传输层提供的服务及功能概述 传输层的功能 ...

  4. 网络编程之UDP通信

    一.UDP简介 UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一 ...

  5. Linux C socket 编程之UDP

    发送方: /*  * File:   main.c  * Author: tianshuai  *  * Created on 2011年11月29日, 下午10:34  *  * 主要实现:发送20 ...

  6. Linux下Socket编程之UDP原理

    一.设计UDP Server类 人们通常用电话连线来说明TCP协议,而UDP协议,则常常用邮递来做比喻.与TCP有连接的信息传输方式不同,UDP协议被认为是对底层IP协议简单的扩展:协议并不保证每个数 ...

  7. 1.6 网络编程之 UDP通信

    ************************************************** * 本文由小鸟飞飞整理发表 <samboy@sohu.com> * * 首发网站:蓝丽 ...

  8. 黑马程序员:Socket编程之(UDP vs TCP)

    ---------------------- ASP.Net+Unity开发. .Net培训.期待与您交流! ---------------------- UDP和TCP都是传输协议,设备之间遵循通讯 ...

  9. [python学习] 专题七.网络编程之套接字Socket、TCP和UDP通信实例

    很早以前研究过C#和C++的网络通信,参考我的文章:                  C#网络编程之Tcp实现客户端和服务器聊天                 C#网络编程之套接字编程基础知识   ...

最新文章

  1. python测试之道进阶,Pytest-Mock 进阶用法详解
  2. 阿里巴巴400集python教程_阿里巴巴推荐的400集Python视频合集免费学起来,学完万物皆可爬...
  3. 绝地求生哪个服务器延迟,绝地求生:腾讯公布国服服务器,超性能环境绝对稳定远离延迟!...
  4. jca使用_使用JCA的密码学–提供者中的服务
  5. boot定时任务开启和关闭 spring_spring-boot 多线程并发定时任务的解决方案
  6. HDU 3861 The King’s Problem 强连通分量 最小路径覆盖
  7. 安装linux没有raid驱动程序,LINUX 无法在 RAID 上安装的问题
  8. 值类型和引用类型小解
  9. C#上位机工作感想2(2020.4.15-2021.7.24)
  10. LaText Error:Environment aligned undefined.
  11. LAMP环境和wordpress站点搭建
  12. 2022烷基化工艺操作证考试题库及模拟考试
  13. unity 简单的吃豆豆项目
  14. 微信小程序实现导航功能
  15. 三网融合可借鉴欧盟视听新媒体内容规制
  16. Android 之路18---Java基础12
  17. Nds-IR780 近红外荧光探针IR780纳米粒子
  18. 《虚拟化与云计算技术》实训
  19. AXI接口协议学习总结
  20. Java课程实验报告实验六——异常处理

热门文章

  1. 程序员工资虽高,但也很苦逼!
  2. html是什么1002无标题,Jsf页面为普通/无标题的文本html
  3. 乌班图linux怎么连手机热点,使用Ubuntu12.04创建无线WiFi热点供手机上网
  4. vue打包放到Java项目里_如何把vuejs打包出来的文件整合到springboot里
  5. 无代码绘制基因表达箱线图
  6. 中医科学院院士团队解析丹参纯合基因组和新基因簇在丹参酮合成中的作用
  7. PAT学习资料汇总(PAT甲级、PAT顶级、PAT考试经验)
  8. 1.10 编程基础之简单排序 06 整数奇偶排序 python
  9. 浏览器填写数据,跳转页面返回数据不消失
  10. hertz接触理论_角接触轴承的组配和预紧技术及影响