Unix网络编程 之 socket基础

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介:

基本结构

(这部分的地址均为网络地址<网络字节序>)


1、struct sockaddr:通用套接字地址结构

    此结构用于存储通用套接字地址。

   数据结构定义:

typedef unsigned short  sa_family_t;
struct sockaddr {
    sa_family_t sa_family;  /* address family, AF_xxx	*/
    char sa_data[14];  /* 14 bytes of protocol address	*/
};
   sa_family:实际使用地址——根据不同的协议族,采用不同的地址类,最常用的三种:

   1)本地地址族:AF_UNIX或AF_LOCAL;

#include <sys/un.h>

struct sockaddr_un {
    sa_family_t  sun_family;         /*AF_UNIX*/
    char        sun_path[108];      /*地址数据*/
};

   2)网络地址族:AF_INET;

#include <netinet/in.h>

struct sockaddr_in {
    sa_family_t    sin_family;       /* AF_INET */
    uint16_t       sin_port;         /*端口号*/
    struct in_addr   sin_addr;        /*Internet地址*/
    unsigned char   sin_zero[8]      /*占位字节*/
};

   3)红外地址类:AF_IRDA;

#include <sys/types.h>

struct sockaddr_irda {
    sa_family_t sir_family;  /* AF_IRDA */
    u_int8_t sir_lsap_sel;     /* LSAP selector */
    u_int32_t sir_addr;         /* Device address */
    char sir_name[25];  /*:IrDA:TinyTP ,OBEX,etc.*/
};

  sa_data[]:包含远程主机的地址、端口号和套接字的数目,这些信息包含于字符串中。

   而在实际应用中,我们经常使用AF_INET。为了处理struct sockaddr,Unix建立了另外一个相似结构struct sockaddr_in。其结构如下所示。

struct sockaddr_in {
    sa_family_t    sin_family;       /* AF_INET */
    uint16_t       sin_port;         /*端口号*/
    struct in_addr   sin_addr;        /*Internet地址*/
    unsigned char   sin_zero[8]      /*占位字节*/
};
   此结构提供了简洁的方法用于访问socket address(struct sockaddr)结构中的每一个元素。sin_zero[8]是为了使两个结构在内存中具有相同的尺寸,使用sockaddr_in时要把sin_zero[]全部设置为零值(使用bzero()函数或memset()函数)。此结构可以认为是IPv4套接字地址结构。

2、struct in_addr:因特网地址结构

   该结构用于表示Internet Address(因特网地址)。

/*Internet address*/
typedef uint32_t in_addr_t;
struct in_addr
{
    in_addr_t s_addr;
};
    在这里,我们由s_addr的类型定义可知,此处的因特网地址长度为32bit,也就是说此处的地址为IPv4地址。同时,当使用struct sockaddr_in类型的ina变量时,ina.sin_addr.s_addr即为32bit的IP地址。当然,此处,我们应该注意这里的IP地址是网络字节序。   

3、IPv6套接字地址结构

    在上文,我们介绍了IPv4套接字地址结构,那么,对于IPv6套接字,其地址结构又如何呢?

   IPv6套接字地址结构在<netinet/in.h>头文件中定义:

struct in6_addr{
    uint8_t s6_addr[16];   /*128bits IPv6 address network byte ordered*/
 };

struct sockaddr_in6{
    sa_family_t sin6_family;    /*AF_INET6 */
    in_port_t sin6_port;  /*transport layer port network byte ordered */
    uint32_t sin6_flowinfo;  /*flow information, undefined*/
    struct in6_addr sin6_addr; /*IPv6 address network byte ordered */
    uint32_t sin6_scope_id;  /*set of interfaces for a scope*/
};
   对于IPv6套接字地址结构,我们要注意这样几点:

   *IPv6的地址族(sa_family)是AF_INET6,而IPv4的地址族是AF_INET;

   *结构中字段的先后顺序做过编排,使得如果sockaddr_in6结构本身是64位对齐的,那么128位的sin6_addr字段也是64位对齐的。

   *sin6_flowinfo字段分为两个字段:低序20位流标(flow table) + 高序12位保留。

   *对于具备范围的地址(scoped address),sin6_scope_id字段标识其范围(scope),最常见的是链路局部地址(link-local address)的接口索引(interface index)。

4、struct sockaddr_storage:新的通用套接字地址结构

struct sockaddr_storage{
    uint8_t    ss_len;    //length of the struct
    sa_family_t ss_family;   //address family: AF_xxx value
};
   在上文,我们可以知道struct sockaddr为通用套接字地址结构,但是由于因特网地址结构的限制,该结构仅支持IPv4地址。而新的通用套接字地址结构struct sockaddr_storage克服了现有struct sockaddr的一些缺点,足以容纳系统所支持的任何任何套接字地址结构。

   sockaddr_storage类型提供的通用套接字地址结构相比sockaddr存在以下两点差别:

(1)、如果系统支持的任何套接字地址结构有对齐需要,那么sockaddr_storage能够满足最苛刻的对齐要求;

(2)、sockaddr_storage足够大,能够容纳系统支持的任何套接字地址结构。

5、套接字地址结构的比较:

 


基本转换函数


1、值-结果参数

   当往一个套接字函数传递一个套接字地址结构时,该结构总是以引用形式来传递,也就是说传递的是指向该结构的一个指针。该结构的长度也作为一个参数来传递,不过其传递方式取决于该结构的传递方向:是从进程到内核,还是从内核到进程。

(1)、从进程到内核传递套接字地址结构的函数有3个:bind、connect和sendto。这些函数一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小。例如:

struct sockaddr_in serv;

connect(sockfd, (struct sockaddr *) &serv, sizeof(serv));
   既然指针和指针所指内容的大小都传递给了内核,内核就知道到底需要从进程复制多少数据。下图展示了这个情形。

 

(2)、从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname和getpeername。这四个函数的其中两个参数是指向某个套接字地址结构的指针和指向表示该结构大小的整数变量的指针。例如:

struct sockaddr_un cli; /*Unix domain*/

socklen_t len;
len = sizeof(cli);
getpeername(unixfd, (struct sockaddr *)&cli, &len);
   把套接字地址结构大小这一参数从一个整数改为指向某个整数变量的指针,其原因在于:

   当函数被调用时,结构大小是一个值(value),它告诉内核该结构的大小,这样内核在写该结构时不至于越界;当函数返回时,结构大小又是一个结果(result),它告诉进程内核在该结构中究竟存储了多少信息。这种类型的参数称为“值-结果(value-result)”参数。下图展示了这个情形。

 

   关于值-结果参数,在后面介绍套接字调用函数时会进一步解析。

2、字节排序函数

   IP地址的三种表示格式:

1)ASCII(点分十进制字符串)

2)网络地址(网络字节序,Network Byte Order)

3)主机地址(主机字节序,Host Byte Order)

   那么,网络字节序和主机字节序有何不同?

   内存在存储数据时有两种处理方法:一种是将低序字节存储在起始地址,此称为小端(little-endian)字节序;另一种是将高序字节存储在起始地址,此称为大端(big-endian)字节序。采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。

   对于2个字节的数据存储,大端存储与小端存储的区别如下图:

 

   在套接字地址表示方面,网络地址(网络字节序)一般采用大端字节序,而主机地址(主机字节序)主要取决于系统。

   那么主机地址和网络地址(或者说主机字节序和网络字节序)各自主要应用于什么场合?

   主机地址主要用于主机处理时,因为计算机更加擅长处理小端字节序(对采用小端存储的主机);而在网络协议中,发送协议栈和接受协议栈必须就多字节字段的各个字节的传送顺序达成一致,为了便于人类思维过程,一般采用大端字节序,即网络字节序。

   主机字节序和网络字节序之间相互转化的过程即为字节排序的过程。这两种字节序之间的转换使用以下4个函数:

#include <netinet/in.h>

/*均返回网络字节序的值*/
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);

/*均返回主机字节序的值*/
uint16_t ntonhs(uint16_t net16bitvalue);
uint32_t ntonhl(uint32_t net32bitvalue);
  在这些函数的名字中,h代表host,n代表network,s代表short,l代表long。如今,我们把s视为16位的值(例如TCP/UDP端口号),把l视为32位的值(例如IPv4地址)。

   这些函数在处理时,会根据主机系统到底是小端存储还是大端存储来相应地调整函数的处理过程,如果主机系统支持小端存储,则对应实现字节反转过程,反之,则这些函数为空宏。

   在什么时候应该使用这些函数呢?当我们存在内核与进程之间的套接字地址结构访问时就必须使用相应的字节转换函数来实现地址的正常传递。

3、字节操纵函数

   操纵多字节字段的函数有两组(Berkeley&ANSIC),它们既不对数据作解释,也不假设数据是以空字符结束的C字符串。

   名字以b(表示字节)开头的第一组函数起源于4.2BSD,现今支持套接字函数的系统仍然提供它们。名字以mem(表示内存)开头的第二组函数起源于ANSIC标准,支持ANSIC函数库的所有系统都提供它们。

Berkeley:

#include <string.h>

void bero(void *dest, size_t, nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);
/*相等返回0,否则返回非0值*/
/*此处使用const限定词,表示所限制的指针所指的内容不会被函数更改。换句话说,函数只是读而不修改由const指针所指的内存单元。*/

ANSIC:

#include <string.h>

void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);
/*相等返回0,否则返回非0值,或大于0,或小于0*/
  4、地址转换函数

   地址转换函数实现ASCII字符串(点分十进制)与网络字节序的二进制(存放在套接字地址结构中的值)之间的网际地址的转换。

   地址转换函数有两组,分别是:

(1)、inet_aton、inet_addr和inet_ntoa函数

   完成点分十进制(如”192.168.1.1”)与它长度为32位的网络字节序二进制间转换IPv4地址。

#include <arpa/inet.h>

int inet_aton(const char *strptr, struct in_addr *addrptr);
/*
此函数将strptr所指的C字符串转换成一个32位的网络字节序二进制,并通过指针addrptr来存储。若成功,返回1,否则返回0。
 */

in_addr_t inet_addr(const char *strptr);
/*
此函数与inet_aton函数执行相同的操作,返回32位的网络字节序二进制值,否则返回INADDR_NONE。注意,此函数不能处理255.255.255.255点分十进制数串。
 */

char *inet_ntoa(struct in_addr inaddr);
/*
此函数将一个32位的网络字节序二进制IPv4地址转换成相应的点分十进制数串。
由该函数的返回值所指向的字符串驻留在静态内存中。另外。该函数是以一个结构而不是以指向该结构的一个指针作为参数。
 */

   注:inet_addr()函数已被废弃,建议采用inet_aton()函数。

(2)、inet_pton和inet_ntop函数

    这两个函数式随IPv6出现的新函数,对于IPv4和IPv6地址均适用。函数名中的p代表表达(presentation)和数值(numeric)。地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构中的二进制值。

#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr);
/*
此函数转换由strptr指针所指的字符串,并通过addrptr指针存放二进制结果。若成功,则返回值为1,否则返回0。
 */

const char *inet_ntop(int family, const void *addrptr, size_t len);
/*
此函数执行从数值格式(addrptr)到表达格式(strptr)的转换。
strptr参数不能是空指针,调用者必须为目标存储单元分配内存并指定大小。调用成功时,返回此指针。
 */
  对于这两个函数,family字段可以是AF_INET或AF_INET6。如果以不被支持的地址族作为family参数,这两个函数会返回错误,并将errno置为EAFNOSUPPORT。

   另外,在inet_ntop()函数中,len参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区。为了有助于指定此大小,在<netinet/in.h>头文件中有如下定义:

#define INET_ADDRSTRLEN 16 /*IPv4 dotted-decimal*/
#define INET6_ADDRSTRLEN 46 /*IPv6 hex-string*/
   如果len太小,不足以容纳表达格式结果,那么返回空指针,并置errno为ENOSPC。
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
目录
相关文章
|
3月前
|
开发者 Python
Python Socket编程:不只是基础,更有进阶秘籍,让你的网络应用飞起来!
在数字时代,网络应用成为连接世界的桥梁。Python凭借简洁的语法和丰富的库支持,成为开发高效网络应用的首选。本文通过实时聊天室案例,介绍Python Socket编程的基础与进阶技巧。基础篇涵盖服务器和客户端的建立与数据交换;进阶篇则探讨多线程与异步IO优化方案,助力提升应用性能。通过本案例,你将掌握Socket编程的核心技能,推动网络应用飞得更高、更远。
73 1
|
1月前
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
36 1
|
2月前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
33 1
|
2月前
|
消息中间件 监控 网络协议
Python中的Socket魔法:如何利用socket模块构建强大的网络通信
本文介绍了Python的`socket`模块,讲解了其基本概念、语法和使用方法。通过简单的TCP服务器和客户端示例,展示了如何创建、绑定、监听、接受连接及发送/接收数据。进一步探讨了多用户聊天室的实现,并介绍了非阻塞IO和多路复用技术以提高并发处理能力。最后,讨论了`socket`模块在现代网络编程中的应用及其与其他通信方式的关系。
275 3
|
2月前
|
网络协议 Linux 应用服务中间件
Socket通信之网络协议基本原理
【10月更文挑战第10天】网络协议定义了机器间通信的标准格式,确保信息准确无损地传输。主要分为两种模型:OSI七层模型与TCP/IP模型。
|
3月前
|
网络协议 Python
网络世界的建筑师:Python Socket编程基础与进阶,构建你的网络帝国!
在数字宇宙中,网络如同复杂脉络连接每个角落,Python Socket编程则是开启这一世界的钥匙。本文将引导你从基础概念入手,逐步掌握Socket编程,并通过实战示例构建TCP/UDP服务器与客户端。你将学会使用Python的socket模块进行网络通信,了解TCP与UDP的区别,并运用多线程与异步IO提升服务器性能。跟随本文指引,成为网络世界的建筑师,构建自己的网络帝国。
40 2
|
3月前
|
网络协议 Python
告别网络编程迷雾!Python Socket编程基础与实战,让你秒变网络达人!
在网络编程的世界里,Socket编程是连接数据与服务的关键桥梁。对于初学者,这往往是最棘手的部分。本文将用Python带你轻松入门Socket编程,从创建TCP服务器与客户端的基础搭建,到处理并发连接的实战技巧,逐步揭开网络编程的神秘面纱。通过具体的代码示例,我们将掌握Socket的基本概念与操作,让你成为网络编程的高手。无论是简单的数据传输还是复杂的并发处理,Python都能助你一臂之力。希望这篇文章成为你网络编程旅程的良好开端。
66 3
|
3月前
|
网络协议 开发者 Python
网络编程小白秒变大咖!Python Socket基础与进阶教程,轻松上手无压力!
在网络技术飞速发展的今天,掌握网络编程已成为开发者的重要技能。本文以Python为工具,带你从Socket编程基础逐步深入至进阶领域。首先介绍Socket的概念及TCP/UDP协议,接着演示如何用Python创建、绑定、监听Socket,实现数据收发;最后通过构建简单的聊天服务器,巩固所学知识。让初学者也能迅速上手,成为网络编程高手。
83 1
|
3月前
|
网络协议 安全 网络安全
震惊!Python Socket竟能如此玩转网络通信,基础到进阶全攻略!
【9月更文挑战第12天】在网络通信中,Socket编程是连接不同应用与服务的基石。本文通过问答形式,从基础到进阶全面解析Python Socket编程。涵盖Socket的重要性、创建TCP服务器与客户端、处理并发连接及进阶话题如非阻塞Socket、IO多路复用等,帮助读者深入了解并掌握网络通信的核心技术。
138 6
|
3月前
|
消息中间件 网络协议 网络安全
解锁Python Socket新姿势,进阶篇带你玩转高级网络通信技巧!
【9月更文挑战第13天】在掌握了Python Socket编程基础后,你是否想进一步提升技能?本指南将深入探讨Socket编程精髓,包括从阻塞到非阻塞I/O以提高并发性能,使用`select`进行非阻塞操作示例;通过SSL/TLS加密通信保障数据安全,附带创建SSL服务器的代码实例;以及介绍高级网络协议与框架,如HTTP、WebSocket和ZeroMQ,帮助你简化复杂应用开发。通过学习这些高级技巧,你将在网络编程领域更进一步。
50 3