套接字(socket)编程简介


现在的网络编程几乎都是用的socket。

我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?

这些都得靠socket!那什么是socket?下面介绍一下socket的相关概念和一些基本函数。

套接字概念

Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。

既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。

在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。

套接字通信原理如下图所示:

在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。

TCP/IP协议最早在BSD UNIX上实现,为TCP/IP协议设计的应用层编程接口称为socket API。本文的主要内容是socket API,主要介绍TCP协议的函数接口,最后介绍UDP协议和UNIX Domain Socket的函数接口。

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为**套接字(Socket )**的接口,区分不同应用程序进程间的网络通信和连接。

socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用以下模式来操作

打开open –> 读写write/read –> 关闭close”

Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),这些函数我们在后面进行介绍。

生成套接字,主要有3个参数:通信的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号

Socket 原意是“插座”。通过将这3个参数结合起来,与一个“插座”Socket 绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

TCP/IP协议族包括运输层、网络层、链路层,而socket所在位置如图,Socket是应用层与TCP/IP协议族通信的中间软件抽象层

sockaddr数据结构

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

sockaddr数据结构:

struct sockaddr {sa_family_t sa_family;   //地址结构类型char sa_data[14];     //地址数据, 14 字节的协议地址,sa_data则包含该socket的IP地址和端口号
};/*说明:
在实际编程中,一般定义struct sockaddr_in  addr,
然后给各个成员赋值,传参数强制转换为struct sockaddr, 例如 (struct sockaddr *) &addr*/

IPv4: struct sockaddr_in (internet), 16个字节

struct sockaddr_in {__kernel_sa_family_t sin_family;      //地址结构类型,AF_INET__be16 sin_port;               //端口号struct in_addr sin_addr;          //IP地址/* Pad to size of `struct sockaddr'. */unsigned char sin_zero[sizeof (struct sockaddr) -sizeof (sa_family_t) -sizeof (in_port_t) -sizeof (struct in_addr)];
};//其中ip地址封装了32位的地址信息--对应点分十进制
struct in_addr {          __be32 s_addr;
};

IPv6: struct sockaddr_in6, 28个字节

struct sockaddr_in6 {unsigned short int sin6_family;     //地址结构类型,AF_INET6__be16 sin6_port;          //端口号__be32 sin6_flowinfo;        //流量信息struct in6_addr sin6_addr;     //IP地址__u32 sin6_scope_id;        //scope_id
};struct in6_addr {union {__u8 u6_addr8[16];__be16 u6_addr16[8];__be32 u6_addr32[4];} in6_u;   #define s6_addr      in6_u.u6_addr8#define s6_addr16         in6_u.u6_addr16#define s6_addr32        in6_u.u6_addr32
};

struct sockaddr_un, 110字节

#define UNIX_PATH_MAX 108struct sockaddr_un {__kernel_sa_family_t sun_family;  /* AF_UNIX */char sun_path[UNIX_PATH_MAX];  /* pathname */
};

Pv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。UNIX Domain Socket的地址格式定义在sys/un.h中,用sock-addr_un结构体表示。各种socket地址结构体的开头都是相同的,前16位表示整个结构体的长度(并不是所有UNIX的实现都有长度字段,如Linux就没有),后16位表示地址类型。IPv4、IPv6和Unix Domain Socket的地址类型分别定义为常数AF_INET、AF_INET6、AF_UNIX。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。因此,socket API可以接受各种类型的sockaddr结构体指针做参数,例如bind、accept、connect等函数,这些函数的参数应该设计成void *类型以便接受各种类型的指针,**但是sock API的实现早于ANSI C标准化,那时还没有void 类型,因此这些函数的参数都用struct sockaddr 类型表示,在传递参数之前要强制类型转换一下,例如:

struct sockaddr_in servaddr;bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));    /* initialize servaddr */

网络字节序与主机字节序

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的Big-Endian和Little-Endian的定义如下:

a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络字节序:网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以,在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。

字节顺序转换函数

头文件:#include <arpa/inet.h>

·htonl():把32位值从主机字节序转换成网络字节序
·htons():把16位值从主机字节序转换成网络字节序
·ntohl():把32位值从网络字节序转换成主机字节序
·ntohs():把16位值从网络字节序转换成主机字节序

1. uint32_t htonl(uint32_t hostint32);

功能:

将 32 位主机字节序数据转换成网络字节序数据

参数:

hostint32:需要转换的 32 位主机字节序数据,uint32_t 为 32 为无符号整型

返回值:

成功:返回网络字节序的值

2. uint16_t htons(uint16_t hostint16);

功能:

将 16 位主机字节序数据转换成网络字节序数据

参数:

hostint16:需要转换的 16 位主机字节序数据,uint16_t,unsigned short int

返回值:

成功:返回网络字节序的值

3. uint32_t ntohl(uint32_t netint32);

功能:

将 32 位网络字节序数据转换成主机字节序数据

参数:

netint32:待转换的 32 位网络字节序数据,uint32_t,unsigned int

返回值:

成功:返回主机字节序的值

4. uint16_t ntohs(uint16_t netint16);

功能:

将 16 位网络字节序数据转换成主机字节序数据

参数:

netint16:待转换的 16 位网络字节序数据,uint16_t,unsigned short int

返回值:

成功:返回主机字节序的

IP地址转换

头文件:

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

1. int inet_pton(int family, const char *strptr, void *addrptr);

功能:

将点分十进制数串转换成 32 位无符号整数

参数:

family:协议族( AF_INET、AF_INET6、PF_PACKET 等 ),常用 AF_INET

strptr:点分十进制数串

addrptr:32 位无符号整数的地址

返回值:

成功返回 1 、 失败返回其它

2. const char *inet_ntop( int family, const void *addrptr, char *strptr, size_t len );

功能:

将 32 位无符号整数转换成点分十进制数串

参数:

family:协议族( AF_INET、AF_INET6、PF_PACKET 等 ),常用 AF_INET

addrptr:32 位无符号整数

strptr:点分十进制数串

len:strptr 缓存区长度

len 的宏定义

#define INET_ADDRSTRLEN 16 // for ipv4

#define INET6_ADDRSTRLEN 46 // for ipv6

返回值:

成功:则返回字符串的首地址

失败:返回 NULL

3. in_addr_t inet_addr(const char * cp)

inet_addr函数转换网络主机地址(如192.168.1.10)为网络字节序二进制值,如果参数char *cp无效,函数返回-1(INADDR_NONE),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理

4. char *inet_ntoa(struct in_addr in)

inet_ntoa 函数转换网络字节排序的地址为标准的ASCII以点分开的地址,该函数返回指向点分开的字符串地址的指针,该字符串的空间为静态分配的,这意味着在第二次调用该函数时,上一次调用将会被重写(复盖)

应用举例:

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> int a = 0x01020304;
short int b = 0x0102;
printf("htonl(0x%08x) = 0x%08x\n", a, htonl(a));
printf("htons(0x%04x) = 0x%04x\n", b, htons(b));char ip_str[]="172.20.223.75";
unsigned int ip_uint = 0;
unsigned charchar *ip_p = NULL;
inet_pton(AF_INET,ip_str,&ip_uint);
printf("in_uint = %d\n",ip_uint); unsigned char ip[] = {172,20,223,75};
char ip_str[16] = "NULL";
inet_ntop(AF_INET,(unsigned intint *)ip,ip_str,16);
printf("ip_str = %s\n",ip_str); strcut sockaddr_in  add;
add.sin_addr.s_addr  = inet_addr("*.*.*.*"); //构建网络地址。
printf("ip is %s\n",inet_ntoa(add.sin_addr)); char *add1,add2;
src.sin_addr.s_addr = inet_addr("192.168.1.123");
add1 =inet_ntoa(src.sin_addr);
src.sin_addr.s_addr = inet_addr("192.168.1.124");
add2 = inet_ntoa(src.sin_addr);

总结:

struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式。这两个结构体一样大,都是16个字节,而且都有family属性,不同的是:

sockaddr用其余14个字节来表示sa_data,而sockaddr_in把14个字节拆分成sin_port, sin_addr和sin_zero

分别表示端口、ip地址。sin_zero用来填充字节使sockaddr_in和sockaddr保持一样大小。

sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:

程序员不应操作sockaddr,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中,sockaddr是给操作系统用的

程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便

字的地址形式。这两个结构体一样大,都是16个字节,而且都有family属性,不同的是:

sockaddr用其余14个字节来表示sa_data,而sockaddr_in把14个字节拆分成sin_port, sin_addr和sin_zero

分别表示端口、ip地址。sin_zero用来填充字节使sockaddr_in和sockaddr保持一样大小。

sockaddr和sockaddr_in包含的数据都是一样的,但他们在使用上有区别:

程序员不应操作sockaddr,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中,sockaddr是给操作系统用的

程序员应使用sockaddr_in来表示地址,sockaddr_in区分了地址和端口,使用更方便

套接字(socket)编程简介相关推荐

  1. Java套接字Socket编程--TCP参数

    在Java的Socket中,主要包含了以下可设置的TCP参数. 属性 说明 默认值 SO_TIMEOUT 对ServerSocket来说表示等待连接的最长空等待时间; 对Socket来说表示读数据最长 ...

  2. 套接字Socket编程

    Socket,原意插座.插口.写软件程序时,可以想象成一根网线,一头插在客户端,一头插在服务端,然后进行通信.所以通信前,双方都要建立一个Socket. Socket编程进行的是端到端的通信,意识不到 ...

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

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

  4. 网络编程:套接字socket函数与绑定信息bind函数

    套接字socket函数与绑定信息bind函数 套接字 绑定信息(绑定IP和端口) socket函数 bind函数 struct sockaddr结构体的组成: struct sockaddr_in结构 ...

  5. Java网络编程入门,包含网络相关概念、InetAddress类、套接字Socket、网络上传和下载文件等

    Java学习-11-韩顺平老师 Java-网络编程入门 目录: 01-网络相关概念 02-InetAddress类 03-套接字Socket 04-网络上传和下载文件 05-UDP网络编程 网络编程相 ...

  6. java实现套接字网络编程_Java网络编程(一)Socket套接字

    一.基础知识 1.TCP:传输控制协议. 2.UDP:用户数据报协议. 二.IP地址封装 1.InetAddress类的常用方法 getLocalHost() 返回本地主机的InetAddress对象 ...

  7. 网络编程(网络基础、套接字Socket、数据报Datagram及其常用方法)

    章节内容 套接字Socket 数据报Datagram 章节目标 了解网络通信中的IP,端口和协议 掌握套接字的使用 熟悉数据报的使用 一.网络基础 1.软件结构 C/S结构 C => Clien ...

  8. Linux下套接字详解(二)----套接字Socket

    在前面我们讲了TCP/IP.TCP和UDP的一些基本知识,但是协议只有一套,而我们系统多个TCP连接或多个应用程序进程必须通过同一个 TCP协议端口传输数据.为了区别不同的应用程序进程和连接,许多计算 ...

  9. 安卓学习笔记40:基于套接字网络编程

    文章目录 零.学习目标 一.Socket概述 (一)两种传输模式 (二)基于Socket网络编程 三.案例演示 - C/S架构聊天室 (一)运行效果 (二)涉及知识点 (三)实现步骤 1.创建聊天服务 ...

  10. 套接字socket 的地址族和类型、工作原理、创建过程

    注:本分类下文章大多整理自<深入分析linux内核源代码>一书,另有参考其他一些资料如<linux内核完全剖析>.<linux c 编程一站式学习>等,只是为了更好 ...

最新文章

  1. 收藏 | 深度学习中神经网络的可视化解释!
  2. vim-addon-manager install youcompleteme
  3. php 获取用户的IP、地址、来源
  4. 中文只占一个字符_一文搞懂字符和字节的含义
  5. 【Python图像特征的音乐序列生成】解析ABC格式的文件(修改版)
  6. ora-01591:锁被未分布式事物处理/Distrib tran
  7. 《ASP.NET Core 真机拆解》 送书活动结果公布
  8. MySQL find_in_set()函数
  9. iOS应用软件沙盒sandbox相关知识(整理)
  10. compareto方法_Java ArrayList 的不同排序方法
  11. C语言排序方法-----冒泡排序法
  12. vue如何让自定义函数挂到全局
  13. 简单介绍Javascript匿名函数和面向对象编程
  14. delphi之鼠标模拟
  15. ldpcMATLAB/ldpc的译码,matlab程序/LDPC编码的matlab实现/源码
  16. php网页源码库存管理系统进销存mysql数据库web结构html布局
  17. javaSE基础全覆盖
  18. 四十八 停电与打牌(中) 我在软件园的那些日子里
  19. linux 排除多个目录搜索文件,关于linux:使用find命令但排除两个目录中的文件
  20. TSP 问题的几种经典建模方式

热门文章

  1. 如何在谷歌地球上绘制路线
  2. EP4拮抗剂、PGE2、受体亚型 是什么?
  3. maven插件maven-complier-plugin
  4. 阿里开源 Easy-Excel合并单元格数据
  5. VMware虚拟机迁移至PVE
  6. 爱恨交加 大数据走在精准营销的边缘
  7. LED和LCD有什么区别?
  8. 盘点六大程序员接单网站,务必收藏!
  9. 桥接模式的应用之三层架构中的业务逻辑层(BLL)与数据访问层(DAL)的解耦
  10. 工作流程引擎:流程引擎对比