1. socket 介绍

  • 所谓 socket(套接字),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象(端到端通信)。一个套接字就是网络上进程通信的一端,给应用层进程提供了利用网络协议交换数据的机制。

    • 从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口
  • socket 可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的 API,也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连的进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 socket 中,该 socket 通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的 socket 中,使对方能够接收到这段信息。
    • socket 是由 IP地址 和 端口 结合的,提供向应用层进程传送数据包的机制。
  • socket 本身有“插座”的意思,在 Linux 环境下,用于表示进程间网络通信的特殊文件类型。本质为借助内核缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。
    • 与管道类似,Linux 将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。

      • 区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

套接字通信分两部分:

  • 服务器端:被动接受连接,一般不会主动发起连接
  • 客户端:主动向服务器发起连接

socket是一套通信的接口,Linux和Windows都有,但是有一些细微的差别。

2. 字节序

简介

  • 现代 CPU 的累加器一次都能装载(至少)4 字节(这里考虑 32 位机),即一个整数。那么这 4字节在内存中排列的顺序将影响它被累加器装载成的整数的值,这就是字节序问题。在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编码/译码从而导致通信失败。
  • 字节序,即字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。
  • 字节序分为大端字节序(Big-Endian)和小端字节序( Little-Endian)。
    • 大端字节序是指一个整数的最高位字节(23 ~ 31 bit)存储在内存的低地址处,低位字节(0 ~ 7 bit)存储在内存的高地址处;
    • 小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。

字节序举例

  • 小端字节序
0x01020304 内存的方向 ----->
内存的低位 -----> 内存的高位
04 03 02 01 0x1122334412345678

  • 大端字节序
0x01020304
内存的方向 ----->
内存的低位 -----> 内存的高位
01 02 03 04
0x 12 34 56 78 11 22 33 44

/*字节序:字节在内存中存储的顺序小端字节序:数据的高位字节存储在内存的高位地址,低位字节存储在内存的低位地址大端字节序:数据的低位字节存储在内存的高位地址,高位字节存储在内存的低位地址
*/
// 通过代码检测当前主机的字节序
#include <stdio.h>
int main(){union {short value; // 2Byteschar bytes[sizeof(short)]; // char[2]} test;test.value = 0x0102;if(test.bytes[0] == 1 && test.bytes[1] == 2) printf("大端字节序\n");else if(test.bytes[0] == 2 && test.bytes[1] == 1)  printf("小端字节序, %d, %d, %d\n", test.value, test.bytes[0], test.bytes[1]);else printf("未知\n");return 0;
}

字节序转换函数

  • 当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然会错误解释。

    解决问题的方法是:发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)

  • 网络字节序是 TCP/IP 中规定好的一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释网络字节序采用大端排序方式

  • BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数: htons、htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。

h  - host 主机,主机字节序
to - 转换成什么
n  - network 网络字节序
s  - short unsigned short
l  - long  unsigned int
// 网络通信时,需要将主机字节序转换成网络字节序
// 另外一端获取到数据以后根据情况将网络字节序转换成主机字节序。
#include <arpa/inet.h>
// 转换端口(16位 2字节)
uint16_t htons(uint16_t hostshort); // 主机字节序 -> 网络字节序
uint16_t ntohs(uint16_t netshort);  // 网络字节序 -> 主机字节序
// 转换IP(32位 4字节)
uint32_t htonl(uint32_t hostlong);  // 主机字节序 -> 网络字节序
uint32_t ntohl(uint32_t netlong);   // 网络字节序 -> 主机字节序
#include <stdio.h>
#include <arpa/inet.h>int main()
{// htons 转换端口// 网络通信时 不会涉及负数传输 一般使用unsignedunsigned short a = 0x0102;printf("a: %x\n", a);unsigned short b = htons(a);printf("b: %x\n", b);printf("---------------------\n");// htonl 转换IPchar buf[4] = {192, 168, 1, 100};int num = *(int *) buf;int sum = htonl(num);unsigned char* p = (char *)&sum;printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3));printf("---------------------\n");// ntohsunsigned short a1 = 0x0201;printf("a1: %x\n", a1);unsigned short b1 = ntohs(a1);printf("b1: %x\n", b1);printf("---------------------\n");// ntohlunsigned char buf1[4] = {1, 1, 168, 192};int num1 = *(int*)buf1;int sum1 = ntohl(num1);unsigned char* p1 = (unsigned char*) &sum1;printf("%d, %d, %d, %d\n", *p1, *(p1+1), *(p1+2), *(p1+3)); return 0;
}

3. socket 地址

  • socket地址:一个结构体,封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。

    • 客户端 -> 服务器(IP, Port)

通用 socket 地址

socket 网络编程接口中表示 socket 地址的是结构体 sockaddr,其定义如下:

#include <bits/socket.h>
struct sockaddr { sa_family_t sa_family;   // 2字节char       sa_data[14];
};
typedef unsigned short int sa_family_t;
  • **sa_family 成员:地址族类型(sa_family_t)的变量。**地址族类型通常与协议族类型对应。常见的协议族(protocol family,也称 domain)和对应的地址族如下所示:
协议族 地址族 描述
PF_UNIX AF_UNIX UNIX本地域协议族
PF_INET AF_INET TCP/IPv4协议族
PF_INET6 AF_INET6 TCP/IPv6协议族

​ 宏 PF_ * 和 AF_ * 都定义在 bits/socket.h 头文件中,且后者与前者有完全相同的值,所以二者通常混用。

  • sa_data 成员:用于存放 socket 地址值。

    但是,不同的协议族的地址值具有不同的含义和长度,如下所示:

协议族 地址值含义和长度
PF_UNIX 文件的路径名,长度可达到108字节
PF_INET 16 bit 端口号和 32 bit IPv4 地址,共 6 字节
PF_INET6 16 bit 端口号,32 bit 流标识,128 bit IPv6 地址,32 bit 范围 ID,共 26 字节

由上表可知,14 字节的 sa_data 根本无法容纳多数协议族的地址值。因此,Linux 定义了下面这个新的通用的 socket 地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的。

#include<bits/socket.h>
struct sockaddr_storage{sa_family_t sa_family;unsigned long int __ss_align; // 用于内存对齐char __ss_padding[ 128 - sizeof(__ss_align)];
};
typedef unsigned short int sa_family_t;

专用 socket 地址

很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 struct sockaddr 结构体,为了向前兼容,现在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是 sockaddr_in 还是 sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

UNIX 本地域协议族使用如下专用的 socket 地址结构体:

#include <sys/un.h>
struct sockaddr_un { sa_family_t sin_family; char sun_path[108];
};

TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用的 socket 地址结构体,它们分别用于 IPv4 和 IPv6:

#include <netinet/in.h>
struct sockaddr_in
{ sa_family_t sin_family;   /* 地址族类型 __SOCKADDR_COMMON(sin_) */ in_port_t sin_port;         /* Port number. */ struct in_addr sin_addr;     /* Internet address. IPv4*/ /* Pad to size of `struct sockaddr'. */ unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)];
};
struct in_addr { in_addr_t s_addr;
};
struct sockaddr_in6 { sa_family_t sin6_family; in_port_t sin6_port; /* Transport layer port # */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* IPv6 scope-id */
};
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

注意:

// 计算机有多个网卡 无线网卡、以太网卡...
// IP地址也都不同
// sockaddr.sin_addr.s_addr = 0; 表示所有网卡都绑定 客户端连接任何IP都可以访问到主机

所有专用 socket 地址(以及 sockaddr_storage)类型的变量在实际使用时都需要转化为通用 socket 地址类型 sockaddr(强制转化即可),因为所有 socket 编程接口使用的地址参数类型都是 sockaddr。

4. IP地址转换(字符串IP->整数,主机/网络字节序转换)

  • 通常,人们习惯用可读性好的字符串来表示 IP 地址,比如用点分十进制字符串表示 IPv4 地址,以及用十六进制字符串表示 IPv6 地址。
  • 但编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录日志时则相反,我们要把整数表示的 IP 地址转化为可读的字符串。
  • 下面 3 个函数可用于用点分十进制字符串表示的 IPv4 地址用网络字节序整数表示的 IPv4 地址之间的转换:
// 比较旧的函数 并且只适用于IPv4
#include<arpa/inet.h>
in_addr_t inet_addr(const char* cp);
int inet_aton(const char* cp,struct in_addr* inp);
char* inet_ntoa(struct in_addr in);

下面这些更新的函数也能完成前面3 个函数同样的功能,且同时适用 IPv4 地址和 IPv6 地址:

#include <arpa/inet.h>
// p:点分十进制的 IP字符串, n:表示 network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst); 参数:af: 地址族: AF_INET AF_INET6 src: 需要转换的点分十进制的 IP字符串 dst: 转换后的结果保存在这个里面(传出参数)返回值: 1: 成功0: src 包含的不是目标地址族的IP字符串(不匹配)-1: af 不是合法的地址族,and errno is set to EAFNOSUPPORT.
// 将网络字节序的整数,转换成点分十进制的 IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 参数:af: 地址族: AF_INET AF_INET6 src: 要转换的 ip的整数的地址 dst: 转换成 IP地址字符串保存的地方 size: 指定第三个参数dst的大小(数组的大小)返回值:返回转换后的数据的地址(字符串),和 dst是一样的

示例:将点分十进制的IP字符串转换成网络字节序的整数,再转换回来

#include <arpa/inet.h>
#include <stdio.h>
int main()
{// 创建一个IP字符串,点分十进制的IP地址字符串char buf[] = "192.168.1.4";// 将点分十进制的IP字符串转换成网络字节序的整数unsigned int num;inet_pton(AF_INET, buf, &num);unsigned char *p = (unsigned char *) &num;printf("%d %d %d %d\n",*p, *(p+1), *(p+2), *(p+3));// 将网络字节序的整数转换成点分十进制的IP字符串char ip[16]; // 最大长度16个字节const char* str = inet_ntop(AF_INET, &num, ip, sizeof(ip));printf("%s\n", str);return 0;
}

【Linux系统编程】socket介绍相关推荐

  1. Linux系统编程-基本命令

    Linux系统编程-基本命令 目录 1.学习目标 2.Linux/Unix操作系统简介 2.1 Linux操作系统的目标(了解) 2.2 Linux操作系统的作用(了解) 2.3 Unix家族 (了解 ...

  2. linux系统发送信号的系统调用是,linux系统编程之信号:信号发送函数sigqueue和信号安装函数sigaction...

    信号发送函数sigqueue和信号安装函数sigaction sigaction函数用于改变进程接收到特定信号后的行为. sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然 ...

  3. Linux 系统编程总结 (未完待续)

    前言 我记得去年说过要讲一些干货分享给大家,Linux 系统编程是linux 驱动开发入门的条件 有人说为什么? mmap select read write open ioctl socket po ...

  4. linux线程并不真正并行,Linux系统编程学习札记(十二)线程1

    Linux系统编程学习笔记(十二)线程1 线程1: 线程和进程类似,但是线程之间能够共享更多的信息.一个进程中的所有线程可以共享进程文件描述符和内存. 有了多线程控制,我们可以把我们的程序设计成为在一 ...

  5. 嵌入式Linux文件提取,嵌入式 Linux系统编程(四)——文件属性

    嵌入式 Linux系统编程(四)--文件属性 一.文件属性概述 Linux 文件的属性主要包括:文件的节点.种类.权限模式.链接数量.所归属的用户和用户组.最近访问或修改的时间等内容.文件属性示例如下 ...

  6. 【Linux系统编程】特殊进程之守护进程

    00. 目录 文章目录 00. 目录 01. 守护进程概述 02. 守护进程查看方法 03. 编写守护进程的步骤 04. 守护进程代码 05. 附录 01. 守护进程概述 守护进程(Daemon Pr ...

  7. 【Linux系统编程】进程概述和进程号

    00. 目录 文章目录 00. 目录 01. 进程概述 02. 进程状态 03. 进程控制块 04. 进程号 05. 进程号相关函数 06. 案例实战 07. 附录 01. 进程概述 我们平时写的 C ...

  8. linux程序设计百度网盘,linux系统编程视频 百度网盘下载

    本帖最后由 雇佣兵333 于 2015-5-19 16:15 编辑 c教程目录: Linux开发快速入门培训 gcc快速入门 Makefile快速入门 GDB快速入门 Linux系统编程之文件篇 01 ...

  9. linux系统编程之进程(八):守护进程详解及创建,daemon()使用

    linux系统编程之进程(八):守护进程详解及创建,daemon()使用 一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等 ...

  10. Linux系统编程(二)孤儿进程和僵尸进程

    Linux系统编程(二) 一.exec函数族 1.exec函数 二.孤儿进程和僵尸进程 三.wait和waitpid 1.wait函数 2.waitpid函数 一.exec函数族 exec函数使用时, ...

最新文章

  1. [ExtJS5学习笔记]第五节 使用fontawesome给你的extjs5应用添加字体图标
  2. oracle负载均衡方案,Oracle负载均衡配置代码
  3. git使用(一)----git安装
  4. php 字符符转整数
  5. mac 使用远程连接
  6. python LIST学习
  7. C++ 你想要的C++面经都在这(附答案 | 持续更新)
  8. Go学习笔记一:基础知识
  9. 数据库原理—数据库管理系统的功能和特点(四)
  10. java 字符串加密解密_Java加密解密字符串
  11. LeetCode 34. Search for a Range
  12. 最齐全的地面贴图素材,速来收藏
  13. 关于 联想昭阳 E43A 无线开关设置的 正确解决办法
  14. Spring boot集成海康威视门禁设备
  15. json-lib将xml转json报错java.lang.NoClassDefFoundError: nu/xom/ParentNode
  16. 在word里面加水印的方法和技巧教程!
  17. 01Linux之计算机硬件软件介绍
  18. 2020考研上海交通大学823计算机通信网真题回忆
  19. 1g等于多少mb计算机网络,1g能存多少首歌曲,1GB等于多少MB
  20. 计算机固态地址是什么,SSD固态硬盘安装到笔记本电脑什么位置比较合适

热门文章

  1. 延迟审核该怎么办?是申诉还是回复?与苹果沟通了1个半月最终解决!吓人!
  2. C++ 详解创建链表过程
  3. 机器学习:聚类算法的模型评估指标:轮廓系数
  4. 如何禁用chosen插件
  5. java基于微信小程序的药品购物商城系统 自助购药小程序uinapp 计算机毕业设计
  6. unity5 (三) 粒子系统
  7. 技术选型解决方案之腾讯云TDSQL数据库实战
  8. 错误:The driver has not received any packets from the server.
  9. 使用Clang作为库 —— Clang AST
  10. 精诚软件|CRM系统是怎么解决这些问题的?