「Linux网络编程-01-01」 socket编程篇(一) socket编程基础

目录

一、预备知识 

1.IP地址

2.端口号

3.网络通信

4.TCP协议简介

5.UDP协议简介

6.网络字节序


二、socket

1.什么是socket(套接字)?

2.为什么要有套接字?

3.套接字的主要类型

4.套接字的域和地址族

拓】网络套接字


三、socket API

1.socket API是什么?  

2.为什么要有Socket API?

3.Socket编程常见API

3.1 socket()

3.2 bind()

3.3 listen ()

3.4 accept()

3.5 connect ()


四、sockaddr

1. 是什么?

2. 为什么?

3. sockaddr的分类

4.Linux中sockaddr的声明

4.1struct  sockaddr(在头文件:中)

 4.2 struct sockaddr_in (在头文件:中)

 4.3 struct sockaddr_un (在头文件:中)

5.创建并填充struct sockaddr_in

6.使用sockaddr传参


一、预备知识 

1.IP地址

1.1 是什么?

        IP地址是在IP协议中, 用来标识网络中不同主机的地址。


1.2 IPv4 & IPv6

  • 对于IPv4来说, IP地址是一个4字节, 32位的整数。通常使用 "点分十进制" 的字符串表示IPv4地址, 例如 123.145.67.89 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255;
  • 对于IPv6来说, IP地址长度为16字节128位,是IPv4地址长度的4倍。于是IPv4点分十进制格式不再适用,采用十六进制表示。具体表示方式请看:IPv6- 百度百科

1.3源IP和目的IP

  • 在IP数据报(在网络层向数据链路层传递数据时封装)的首部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址。
  • 源IP地址:发送消息的主机地址。
  • 目的IP地址:接收消息的主机地址。

2.端口号

2.1 是什么?

         端口号是一个2字节16位的整数,用来标识一个进程。

2.2 pid 表示唯一一个进程; 端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?

        
  • "端口号" 和 "进程pid"没有任何关系。
    端口号用于网络通信,而进程pid用于进程管理,网络通信和进程管理是两个毫不相干的模块。之所以不用pid来代替端口号的功能是为了功能解耦,减少系统的耦合度。
  • 不是所有的进程需要端口号,但是所有的进程都需要PID。
  • 一个进程可以有多个端口号; 但一个端口号只能被一个进程占用。

2.3 理解源端口号和目的端口号

  • 传输层协议(TCPUDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号。 就是在描述 "数据是谁发的, 要发给谁"。
  • 源端口号:发送消息的进程的端口号。
  • 目的端口号:接收消息的进程的端口号。

3.网络通信

3.1 是什么?

        网络通信的本质就是进程间通信。


3.2 为什么?

        发送数据的主机由进程发出数据,接收数据的主机也要靠进程处理数据。简化后其实就是一个进程发出数据,另一个进程处理数据。所以说网络通信的本质就是进程间通信。


3.3 网络通信时如何保证IP地址+端口号能找到指定进程?

  • 客户端进程给服务端进程发信息
            服务端进程一般都是一经启动,避免关闭,所以服务端进程的端口号不会随意改变。所以客户端进程能根据下载软件时得到的IP地址+端口号找到服务端进程。     
  • 服务端进程给客户端进程发信息
            软件被用户打开后,客户端进程和端口号被创建,此时客户端进程要先向服务端发信息来获取数据,所以服务端进程就得到了客户端进程的IP地址+端口号,往后服务端进程能根据客户端进程的IP地址+端口号找到客户端进程。(所以我们打开软件后,通常会加载一会)

3.4 OS如何根据端口号找到指定的进程?

        底层采用哈希的方式建立了端口号和进程PID或PCB之间的映射关系,当底层拿到端口号时就可以在哈希表中根据端口号找到对应的进程。


3.5 网络通信是双方的

        当一台主机发送数据给另一台主机时,发送方除了要发送数据外还要把自己的 IP地址和端口号 发送给接收方,所以接收方能给发送方回数据。所以说通信是双方的。


4.TCP协议简介

  • TCP(Transmission Control Protocol 传输控制协议),TCP协议是一种有连接、可靠、面向字节流的传输层通信协议
  • TCP协议是面向连接的,如果两台主机之间想要进行数据传输,那么必须要先建立连接,当连接建立成功后才能进行数据传输。
  • TCP协议是可靠的,因为TCP协议注重丢包率, TCP协议花费大量开销解决数据在传输过程中出现的丢包、乱序等问题,保证不丢包。文件传输、电子邮件、网站访问一般用的就是TCP协议。

5.UDP协议简介

  • UDP(User Datagram Protocol 用户数据报协议),UDP协议是一种无连接、不可靠、面向数据报的传输层通信协议。
  • 使用UDP协议进行通信时无需建立连接,如果两台主机之间想要进行数据传输,那么直接将数据发送给对端主机就行了。
  • UDP协议是不可靠的,因为UDP协议注重效率,没有处理在传输数据过程中小概率出现的丢包、乱序等情况。直播一般用的就是UDP协议。

6.网络字节序

6.0 大端字节序和小端字节序

  • 大端字节序:是将数据的低位字节放到高地址处,高位字节放到低地址处。
    地址字节:“大弟高”)
  • 小端字节序,是将数据的低位字节放到低地址处,高位字节放到高地址处。
    地址字节:“小弟弟”)

6.1 是什么?

        
        即网络通信中共同遵守的字节序,规定为 大端字节序(低字节,高地址)

6.2 为什么?

        不同计算机的 字节序不同,如果在网络通信时不加以规定,会出现发送方以大端模式发送数据,接收方以小端模式读取数据的情况。

6.3 怎么定义?

        TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据。
如果当前发送主机是小端, 就需要先将数据转成大端再发送,否则直接发送。

6.4 网络字节序与主机字节序之间的转换函数

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

#include <arpa/inet.h>
 
uint32_t htonl(uint32_t hostlong);
//将主机字节序(h)转换为(to)网络字节序(n)要转化的数据是长整数(l)。

uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  • 这些函数名很好记:h表示host,n表示network,l表示32位长整数,s表示16位短整数。所以htonl表示:将主机字节序(h)转换为(to)网络字节序(n)要转化的数据是长整数(l)。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
  • 如果主机是大端字节序,这些 函数不做转换将参数原封不动地返回。 

二、socket

1.什么是socket(套接字)

        IP地址+端口号就是socket(套接字) ,用来标定某主机上的某进程。

2.为什么要有套接字?

        有了套接字,不同主机的进程才能在网络中找到彼此,才能进行网络通信(进程间通信的前提是能互相看到彼此),所以说套接字是网络通信的基石。其次有了套接字,开发者才能开发出通用的网络通信接口,用户也能拿着套接字去使用这些接口。

3.套接字的主要类型

  • 流套接字(SOCK_STREAM):用于读取TCP协议的数据。
  • 数据报套接字(SOCK_DGRAM):用于读取UDP协议的数据。
  • 原始套接字(SOCK_RAW):可以从应用层直接绕开传输层,直接去访问底层协议,所以原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接字。

        从套接字的类型就可以看出网络通信遵守的传输层协议(TCP、UDP…)

4.套接字的域和地址族

要使用套接字进行通信的有两个前提:1. 套接字有相同的类型  2.  套接字处于相同的域

4.1 什么是套接字域?

        域就是套接字的工作范围。用来指定套接字通信中使用的网络介质。


4.2 常见的套接字域

        AF_INET / PF_INET        ---------       表示套接字工作在互联网地址类型为IPv4

        AF_INET6 / PF_INET6    ---------       表示套接字工作在互联网地址类型为IPv6

        AF_UNIX / AF_LOCAL     ---------       表示套接字工作在Unix系统内。

        前缀AF_表示地址族(address family),PF_表示协议族(protocol family)


4.3 什么是地址族?

        相同类型套接字地址的集合,用来表示套接字地址类型。

        AF_INET表示是IPv4地址族,即表示套接字地址类型为IPv4 。

拓】网络套接字

  1. 流套接字(SOCK_STREAM)流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,是因为TCP协议。流套接字只能读取TCP协议的数据
  2. 数据报套接字(SOCK_DGRAM)数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP( User DatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。数据报套接字只能读取UDP协议的数据。

拓】功能强大的套接字

        套接字是非常强大的,虽然文章只介绍了套接字用于网络通信,但其实套接字可以用于几乎任何类型的进程间通信:本地通信、各种类型的网络通信等。


三、socket API

1.socket API是什么?  

        是提供给程序员(应用层)做网络开发所用的接口,用来实现不同主机中进程的通信。

        Socket API(套接字编程接口)实际是 传输层 提供给 应用层 的编程接口,用来实现不同主机中进程的通信:传输层网络层的基础上提供进程到进程问的逻辑通道,而应用层的进程则利用传输层向另一台主机的某一进程通信。Socket就是应用层与传输层之间的桥梁,使用Socket编程可以开发客户端和服务端应用程序,从而通过网络实现在全球范围内通信。


2.为什么要有Socket API?

        socket(套接字)只是网络通信的前提,只有开发出一套通用的网络接口才能实现不同主机中进程的通信。Linux下的这套接口就是Socket API(套接字编程接口)。


3.Socket编程常见API

3.1 socket()

//创建socket_fd(套接字 文件描述符),用于TCP/UDP网络程序中的客户端 + 服务器

3.2 bind()

//让 socket_fd和sockaddr_in绑定 用于 TCP/UDP 网络程序中的 服务器。

3.3 listen ()

//开始监听socket,用于 TCP 网络程序中的 服务器

3.4 accept()

//接收请求,用于 TCP 网络程序中的 服务器

3.5 connect ()

//建立连接,用于 TCP 网络程序中的 服务器

四、sockaddr

1. 是什么?

        struct sockaddr(套接字地址结构体)是Linux用来保存套接字和套接字地址类型(地址族)的结构体。


2. 为什么?

        使用Socket API要需要传入套接字,Linux选择用struct sockaddr保存套接字和套接字地址类型(地址族)。在Linux下使用Socket API要传入struct  sockaddr*。


3. sockaddr的分类

        Linux中只设置了一套Socket API(套接字编程接口),但是有不同类型的套接字(用于IPv4的套接字、用于IPv6的、用于本地通信的) ,所以使用struct sockaddr、struct sockaddr_in 、struct  sockaddr_un来区分不同类型的套接字:


sockaddr、sockaddr_in 和 sockaddr_un结构体头部的16个比特位(2字节)都是一样的,这16位是用来区分套接字的地址类型(地址族),根据套接字的地址类型(地址族)分别定义为常数AF_INET、AF_INET6……


Socket API都用struct  sockaddr *类型传参,将 sockaddr_in 或 sockaddr_un强制类型转换为sockaddr,才能被Socket API识别。对于传进来的参数 sockaddr ,函数通过前两个字节,进行判断是网络通信还是本地通信,知道这个结果后再强制类型转换回  sockaddr_in 和 sockaddr_un结构体。这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket等各种地址类型(地址族)的sockaddr结构体指针做为参数。


4.Linux中sockaddr的声明

4.1struct  sockaddr(在头文件:<sys/socket.h>中


 4.2 struct sockaddr_in (在头文件:<netinet/in.h>中)


 4.3 struct sockaddr_un (在头文件:<sys/un.h>中)


5.创建并填充struct sockaddr_in

4.0 导入结构体声明所在的头文件。

#include <netinet/in.h>

struct sockaddr_in ,声明在头文件:<netinet/in.h>中


4.1 定义struct sockaddr_in变量。

struct sockaddr_in local;
  • struct sockaddr_in:用来定义Ipv4和Ipv6的套接字结构体,用于网络通信。
  • struct sockaddr_un :用来定义UNIX_Domain_Socket(Unix域套接字:用于同一台主机上进程间通信)。用于本地通信。

4.2 初始化结构体

bzero(&local, sizeof(local)); 

使用bzero() 或 memset()初始化结构体的内存空间为0 。


4.3 设置套接字地址类型 (即地址族)

local.sin_family = AF_INET;


4.4 设置端口号 (保存端口号的变量为:port,是一个2字节16位的整数)

local.sin_port = htons(port);

端口号要被对方获取,也是网络数据的一部分,所以要考虑大小端问题,使用htons函数将主机字节序转为网络字节序。


4.5 设置IP地址 (保存IP地址的变量为:ip,是一个点分十进制字符串)

local.sin_addr.s_addr = inet_addr(ip);

 inet_addr()的作用:

        1.将点分十进制字符串风格的IP地址 -> 4字节整数

        2.ip地址也要考虑大小端:将4字节整数 -> 网络序列


6.使用sockaddr传参

        Socket API只接受struct  sockaddr *类型传参,所以将 sockaddr_in、 sockaddr_un强制类型转换为sockaddr,才能被Socket API识别。
        对于传进来的参数 sockaddr ,函数通过前两个字节,进行判断是网络通信还是本地通信,知道这个结果后再强制类型转换回  sockaddr_in 和 sockaddr_un结构体。这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

猜你喜欢

转载自blog.csdn.net/look_outs/article/details/132167770