Socket编程实践(1) 基本概念

1. 什么是socket

socket可以看成是用户进程与内核网络协议栈的编程接口。TCP/IP协议的底层部分已经被内核实现了,而应用层是用户需要实现的,这部分程序工作在用户空间。用户空间的程序需要通过套接字来访问内核网络协议栈。

套接口是全双工的通信,它不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。

套接字还可以异构系统间进行通信,异构系统指的是在硬件或软件上有所差别的系统,例如安卓系统的手机与windows系统的PC机上都可以实现QQ通信,套接字可以实现在这两个设备上的通信。

2. IPV4套接口地址结构

套接口既然能够连接两个端系统,那它就需要一个地址来标记该端系统,例如两个电话需要电话号码来标记才可以进行拨号。这抽象成套接口的地址结构。IPV4套接口地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在头文件< netinet/in/h >中。

struct sockaddr_in{

    uint8_t sin_len;

    sa_family_t sin_family;

    in_port_t sin_port;

    struct in_addr sin_addr;

    char sin_zero[8];

};

说明:

  1. sin_len:整个sockaddr_in结构体的长度,在4.3BSD-Reno版本之前的第一个成员是sin_family。
  2. sin_family:指定带地址家族,在这里必须设置为AF_INET。socket在设计时不仅可以用于TCP/IP协议,它还可以用于其他协议,例如unix域协议,地址家族用于指定该套接字用于哪种协议。AF_INET表示用于IPV4协议。
  3. sin_port:端口号,16位的无符号整数,能够表示到65535。2个字节。
  4. sin_addr: IPV4的地址。4个字节的整数。
  5. sin_zero:暂不使用,一般将其设置为0。

其中,struct in_addr仅仅是一个32位的无符号整数,可以在终端下输入man 7 ip进行查看:

接下来看一下通用的地址结构。上面说过,socket可以用于不同的协议上,通用的地址结构可以用于任何协议的socket编程。

struct sockaddr{

    uint8_t sin_len;

    sa_family sin_family;

    char sa_data[14];

};

说明:

  1. sin_len:整个sockaddr结构大小
  2. sin_family:指定该地址家族
  3. sa_data:由sin_family决定它的形式

可以看到,在通用地址结构中sa_data是14个字节,而在IPV4的地址结构中,sin_port、sin_addr、sin_zero三个变量加起来也等于14个字节。也即是说,这两种结构是兼容的。

3. 网络字节序

字节序可以分为大端字节序与小端字节序:

  • 大端字节序(Big Endian) :最高有效位存储于最低内存地址处,最低有效位存储于最高地址内存处。
  • 小端字节序(Little Endian):刚好与大端字节序倒过来,最高有效位存于最高内存地址处,最低有效位存储于最低内存地址处。

这样说起来挺抽象,通过一幅图来说明:

上面说过,socket可以用于异构系统之间的通信。而不同的系统采用的字节序可能是不同的,有的系统采用大端字节序,例如Motorola 6800;有的采用小端字节序,如X86。因此,在进行字节传输时,应该同一一个字节序,称为网络字节序。网络字节序采用大端字节序。如果主机A为小端字节序的系统,那么在传输时需要先将小端字节序转换成网络字节序。这需要一些字节序的转换函数。

我们可以编写程序来测试自己的主机是什么字节序:

#include<stdio.h>

int main(void)

{

        unsigned int x = 0x12345678;

        unsigned char *p = (unsigned char*)&x;

        printf("%0x,%0x,%0x,%0x\n",p[0],p[1],p[2],p[3]);

        return 0;

} 

在我的电脑上输出结果为:78,56,34,12. 因此我的主机为小端字节序。

4. 字节序转换函数

如果主机的字节序与网络字节序不同,那么需要进行字节序的转换。下面是一些字节序转换函数:

   # include < arpa/inet.h >

   uint32_t htonl(uint32_t hostlong);

   uint16_t htons(uint16_t hostshort);

   uint32_t ntohl(uint32_t netlong);

   uint16_t ntohs(uint16_t netshort);

说明:h代表host;n代表network;s代表short;l代表long

描述:

  • htonl()函数将无符号整数hostlong从主机字节序转换成网络字节序。
  • htons()函数将无符号短整型hostshort从主机字节序转换成网络字节序。
  • ntohl()函数功能与 htonl()函数相反
  • ntohs()函数功能与htons()函数相反

我们可以进行验证,刚才已经通过程序测试出我的主机是小端字节序,接下来使用函数 htonl()将整数0x12345678转换成网络字节序。

#include<stdio.h>

#include <arpa/inet.h>

int main(void)

{

        unsigned int x = 0x12345678;

        unsigned char *p = (unsigned char*)&x;

        printf("转换前:%0x,%0x,%0x,%0x\n",p[0],p[1],p[2],p[3]);

        unsigned int y = htonl(x);

        p = (unsigned char *) &y;

        printf("转换后:%0x,%0x,%0x,%0x\n",p[0],p[1],p[2],p[3]);

        return 0;

}         

结果输出:

转换前:78,56,34,12

转换后:12,34,56,78

5. 地址转换函数

对于IP地址,我们通常采用点分十进制的形式进行直观的认识,而程序更多的时候是处理32位的地址,因此需要有函数在点分十进制与32位地址这两种形式间进行转换。

   # include < sys/socket.h>

   # include < netinet/in.h>

   # include < arpa/inet.h>



   int inet_aton(const char *cp, struct in_addr *inp);

   in_addr_t inet_addr(const char *cp);

   char *inet_ntoa(struct in_addr in);

描述:

  • inet_addr()函数:表示将点分十进制的IP地址转换成32位的ip地址(整数)。
  • inet_ntoa()函数:将32位ip地址(网络字节序)转换成点分十进制的ip之地。

例程:

#include<stdio.h>

#include<arpa/inet.h>

int main()

{

        unsigned long addr = inet_addr("192.168.0.100");//将点分十进制转换为32bit地址

        printf("addr = %u\n",htonl(addr)); 



        struct in_addr ipaddr;

        ipaddr.s_addr = addr;

        printf("ipaddr = %s\n",inet_ntoa(ipaddr)); //网络字节序地址转换为点分十>进制

        return 0;

}

输出:

addr = 3232235620

ipaddr = 192.168.0.100

6. 套接字类型

套接字类型主要有三种:

  1. 流方套接字(SOCK_STREAM):它对应TCP协议,它提供面向连接的、可靠的数据传输服务,数据无差错、无重复的发送,且按发送顺序接收。
  2. 数据报套接字(SOCK_DGREAM):提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。
  3. 原始套接字(SOCK_RAW):它提供一种能力,让我们直接跨越传输层,直接对IP层进行数据封装,通过该套接字,我们可以直接将数据封装成IP层能够认识的协议格式。

文章连接:http://www.cnblogs.com/QG-whz/p/5426634.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

Google protocol buffer简介

Google Protocol Buffer Google Protocol Buffer又简称Protobuf,它是一种很高效的结构化数据存储格式,一般用于结...

2226
来自专栏王亚昌的专栏

strace命令解析

strace常用于跟踪和分析进程执行时中系统调用和耗时以及占用cpu的比例,常用的格式如下:

761
来自专栏大内老A

通过内存分析工具来证明字符串驻留机制

在这之前我写过一些文章来介绍关于字符串内存分配和驻留的文章,涉及到的观点主要有:字符串的驻留机制避免了对具有相同字符序列的字符串对象的重复创建;被驻留的字符串是...

18710
来自专栏牛客网

美团榛果民宿面经

1474
来自专栏漏斗社区

Burp Suite API学习思路(二)

在上篇工具| Burp Suite API学习思路文章中,斗哥介绍了BurpSuite扩展开发的一些准备工作与利用IHttpListener接口监听模块接收返回...

842
来自专栏Golang语言社区

一起用golang之Go程序的套路

系统性地介绍golang基础的资料实在太多了,这里不再一一赘述。本文的思路是从另一个角度来由浅入深地探究下Go程序的套路。毕竟纸上得来终觉浅,所以,能动手就不要...

1532
来自专栏Fundebug

JavaScript异步编程史:回调函数到Promise到Async/Await

摘要: 异步编程时JavaScript以及Node.js的一大亮点,其中有什么心酸的黑历史呢?

1595
来自专栏我是攻城师

Apache Pig和Solr问题笔记(一)

3386
来自专栏阮一峰的网络日志

JavaScript 内存泄漏教程

一、什么是内存泄漏? 程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。 对于持续运行的服务进程(daemon),必须及...

3056
来自专栏python学习之旅

Python+Selenium笔记(十):元素等待机制

(一) 前言 突然的资源受限或网络延迟,可能导致找不到目标元素,这时测试报告会显示测试失败。这时需要一种延时机制,来使脚本的运行速度与程序的响应速度相匹配,W...

4265

扫码关注云+社区