【Linux】网络编程套接字(一)—— UDP编程

1. 预备知识

1.1 源IP地址和目的IP地址

源IP地址:指的就是发送数据包的那个电脑的IP地址。
目的IP地址:就是想要发送到的那个电脑的IP地址。

1.2 认识端口号

端口号(port)是传输层协议的内容。

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用。

1.3 理解 “端口号” 和 “进程ID”

系统编程的pid 表示唯一一个进程;此处我们的端口号也是唯一表示一个进程。
一个进程可以绑定多个端口号
一个端口号不能被多个进程绑定

1.4 源端口号和目的端口号

传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述 “数据是谁发的,要发给谁”。

1.5 认识UDP协议

我们先对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;后面再详细讨论。

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

1.6 认识TCP协议

此处我们也是对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;后面再详细讨论。

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

1.7 网络字节序

内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分。

那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可。
    在这里插入图片描述

为使网络程序具有可移植性, 使同样的C代码在大端和小端计算机上编译后都能正常运行, 可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);  // 主机字节序转网络字节序,转化4字节
uint16_t htons(uint16_t hostshort);  // 主机字节序转网络字节序,转化2字节

uint32_t ntohl(uint32_t netlong);  // 网络字节序转主机字节序,转化4字节
uint16_t ntohs(uint16_t netshort);  // 网络字节序转主机字节序,转化2字节
  • h表示host, n表示network, l表示32位长整数, s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序, 例如将IP地址转换后准备发送。
  • 如果主机是小端字节序, 这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序, 这些函数不做转换, 将参数原封不动地返回。

2. socket编程接口

2.1 socket 常见API

2.1.1 创建

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
参数:
    domain:地址域 —> 网络层使用什么协议
        AF_INET: ipv4版本的ip协议
        AF_INET6: ipv6版本的ip协议
        AF_UNIX: 域套接字
    type:套接字的类型
        SOCK_DGRAM: 用户数据报套接字 —> 默认协议是UDP协议
        SOCK_STREAM: 流式套接字 —> 默认的协议是TCP协议
    protocol:协议
        SOCK_DGRAM: IPPROTO_UDP(17)
        SOCK_STREAM: IPPROTO_TCP(6)
        也可以传递0,表示使用套接字的默认协议
返回值:套接字描述符,本质上还是一个文件描述符
  • socket()打开一个网络通讯端口, 如果成功的话, 就像open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用read/write在网络上收发数据;
  • 如果socket()调用出错则返回-1;
  • 对于IPv4, family参数指定为AF_INET;
  • 对于TCP协议, type参数指定为SOCK_STREAM, 表示面向流的传输协议;
  • protocol参数的介绍从略, 指定为0即可。

2.1.2 绑定

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addr_len);
参数:
    sockfd:套接字描述符
    addr:地址信息
    addr_len:传入结构体的真实字节数量
  • 服务器程序所监听的网络地址和端口号通常是固定不变的, 客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;
  • 服务器需要调用bind绑定一个固定的网络地址和端口号;
  • bind()成功返回0, 失败返回-1;
  • bind()的作用是将参数sockfd和addr绑定在一起;
  • struct sockaddr *是一个通用指针类型, addr参数实际上可以接受多种协议的sockaddr结构体, 而它们的长度各不相同, 所以需要参数addr_len指定结构体的长度。

注意绑定ip的时候

  1. 可以直接绑定网卡对应的ip地址;
  2. 也可以绑定0.0.0.0表示当前机器当中的任意网卡的ip地址;
  3. 绑定127.0.0.1表示绑定本地回环地址,只访问本地网络协议栈(访问自己,测试地址)。

2.1.3 关闭

// 关闭套接字
close(int sockfd);
参数:
    sockfd:套接字描述符

2.2 sockaddr结构

socket API是一层抽象的网络编程接口, 适用于各种底层网络协议, 如IPv4、IPv6。然而, 各种网络协议的地址格式并不相同。
在这里插入图片描述

2.2.1 sockaddr 结构

//通用的结构体:
struct sockaddr 
{
   
    sa_family_t  sa_family;  //地址类型, AF_xxx, 2字节
    char  sa_data[14];  // 协议地址, 14字节
};

2.2.2 sockaddr_in 结构

// AF_INET的结构体
struct sockaddr_in 
{
   
    sa_family_t  sin_family;  //地址类型, 2字节
    in_port_t  sin_port;  // 端口号, 2字节
    struct in_addr  sin_addr;  // IP地址, 4字节
    unsigned char  sin_zero[8];  // 8字节填充, 目的是与addr对齐
};

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in。
这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。

2.2.3 in_addr 结构

typedef uint32_t in_addr_t;
struct in_addr
{
   
    in_addr_t s_addr;
};

in_addr用来表示一个IPv4的IP地址,其实就是一个32位的整数。

2.2.4 小结

  • IPv4和IPv6的地址格式定义在netinet/in.h中, IPv4地址用sockaddr_in结构体表示, 包括16位地址类型, 16位端口号和32位IP地址。

  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样, 只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体, 就可以根据地址类型字段确定结构体中的内容。

  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

2.3 地址转换函数

基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址,但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换。

// 点分十进制转网络二进制
#include <netinet/in.h>
in_addr_t inet_addr(const char *cp);

inet addr()函数的作用是:将Internet主机地址从IPv4数字和点符号转换为按网络字节顺序的二进制数据。如果输入无效,则返回INADDR_NONE(通常为-1)。

2.4 例子

#include <stdio.h>    
#include <unistd.h>    
#include <sys/socket.h>  // 套接字   
#include <netinet/in.h>  // IP地址——> 网络字节序     
#include <arpa/inet.h>  // 主机 ——> 网络字节序                                                                                    
#include <iostream>    

int main()    
{
       
    int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);    
    if(sockfd < 0)    
    {
       
        perror(
  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
内容简介回到顶部↑ 本书是专为在UNIX平台下用C语言编制程序的人写的。是以POSIX为标准,主要以C语言为基础,详细介绍了UNIX平台下编写各种应用程序的范例和方法。全书分四个部分,共十五章。本书范例丰富,且具有代表性,如Socket编程、客户/服务端编程、多线程开发、CGI编程、X Windows下的Motif编程等。读者直接或只需稍作修改就可以将它们应用到自己的应用程序开发中。这些范例的源代码可以从配套光盘的电子书中直接拷贝使用。 目录回到顶部↑ 第一部分 基本的系统调用 第1章 文件子系统 1.1 文件子系统的基本概念 1.2 基本的文件输入和输出 1.3 高级的文件操作 第2章 终端操作 2.1 终端的基本概念 2.2 终端输入和输出 2.3 ioctl系统调用 第3章 进程及进程间通信 3.1 进程的基本概念 3.2 进程的一般操作 3.3 进程的特殊操作 3.4 进程间使用管道通信 第4章 信号 4.1 信号的基本概念 4.2 信号机制 4.3 有关信号的系统调用 第5章 部分其他调用 5.1 系统调用 .5.2 相关函数 第二部分 网络编程 第6章 Socket编程基础 6.1 TCP/IP基础知识 6.2 Socket一般描述 6.3 Socket中的主要调用 6.4 Socket的原始方式 第7章 客户/服务器编程 7.1 客户端程序设计 7.2 服务器端程序设计 7.3 服务端程序结构 7.4 多协议(TCP、UDP)服务端 7.5 客户端的并发程序设计 7.6 使用telnet协议的客户端例子 第8章 线程 8.1 有关线程的基本概念 8.2 线程的创建和终止 8.3 线程控制调用 8.4 线程之间的互斥 8.5 线程之间的同步 8.6 线程特定数据区的函数调用 8.7 一个使用线程的客户端并发的例子 8.8 有关线程的函数列表 第9章 CGl编程 9.1 CGI程序的基本概念 9.2 CGI基本编程 9.3 使用脚本语言编写CGl 9.4 Perl语言简介 9.5 一个简单的CGI例子 第三部分 X Window应用程序开发 第10章 X Window和Motif基础 10.1 简介 10.2 XWindow基本概念 10.3 启动Motif窗口管理器 10.4 设置Motif特性 10.5 Widget 第11章 Motif编程 11.1 基本编程概念 11.2 Widget资源 11.3 Motif编程基础 11.4 程序框架 11.5 "HelloWorld!"示例 11.6 管理器 11.7 按钮 11.8 X事件 11.9 其他Widget简介 11.10 菜单 11.11 对话框 第12章 Widget与X事件汇总 12.1 Widget 12.2 X事件 第四部分 常用的编程工具 第13章 编译器及调试工具 13.1 编译器用法入门 13.2 调试器使用入门 13.3 关于库的简介 第14章 Make工具及makefile规则 14.1 概述 14.2 make和makefile的关系 14.3 makefile规则 14.4 伪指令 14.5 make命令行参数 第15章 版本控制 15.1 版本控制概念 15.2 源代码控制系统SCCS 15.3 RCS使用方法 15.4 并发版本控制CVS
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值