【Linux】socket基础API

   日期:2024-12-26    作者:cyrh2011 移动:http://oml01z.riyuangf.com/mobile/quote/38767.html

目录

1. 创建socket(TCP/UDP,客户端+服务器

1.1 第一个参数——domain

1.2 第二个参数——type

1.3 第三个参数——protocol

2. 绑定socket地址(TCP/UDP,服务器

2.1 字节序及转换函数

2.2 IP地址及转换函数

2.3 MAC地址

2.4 端口号

2.5 通用socket地址

2.6 专用socket地址

2.7 INADDR_ANY

2.8 为什么客户端不需要手动bind,服务器需要手动bind

3. 监听socket(TCP,服务器

4. 接受连接(TCP,服务器

5. 发起连接(TCP,客户端

6. 关闭连接(TCP/UDP,客户端+服务器

7. 数据读写

7.1 TCP数据读写

7.2 UDP数据读写

8. 基于UDP的回声程序

9. 基于TCP的回声程序


 

协议族(protocol family,也称domain)是多个相关协议的集合。地址族类型通常与协议族类型对应。

协议族描述地址族描述PF_INETlPv4协议族AF_INETlPv4地址族PF_INET6lPv6协议族AF_INET6lPv6地址族

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

socket类型指的是socket的数据传输方式。

socket类型描述SOCK_STREAM字节流式socketSOCK_DGRAM数据报式socket

对TCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表示传输层使用UDP协议。

传输层协议主要有两个:TCP协议和UDP协议。TCP协议相对于UDP协议的特点是:面向连接、字节流和可靠传输。

使用TCP协议通信的双方必须先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP连接是全双工的,即双方的数据读写可以通过一个连接进行。完成数据交换之后,通信双方都必须断开连接以释放系统资源。

TCP协议的这种连接是一对一的,所以基于广播和多播(目标是多个主机地址)的应用程序不能使用TCP服务。而无连接协议UDP则非常适合于广播和多播。

字节流服务和数据报服务的区别对应到实际编程中,则体现为通信双方是否必须执行相同次数的读、写操作(当然,这只是表现形式)。当发送端应用程序连续执行多次写操作时,TCP模块先将这些数据放入TCP发送缓冲区中。当TCP模块真正开始发送数据时,发送缓冲区中这些等待发送的数据可能被封装成一个或多个TCP报文段发出。因此,TCP模块发送出的TCP报文段的个数和应用程序执行的写操作次数之间没有固定的数量关系。

当接收端收到一个或多个TCP报文段后,TCP模块将它们携带的应用程序数据按照TCP报文段的序号依次放入TCP接收缓冲区中,并通知应用程序读取数据。接收端应用程序可以一次性将TCP接收缓冲区中的数据全部读出,也可以分多次读取,这取决于用户指定的应用程序读缓冲区的大小。因此,应用程序执行的读操作次数和TCP模块接收到的TCP报文段个数之间也没有固定的数量关系。

综上所述,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,这就是字节流的概念:应用程序对数据的发送和接收是没有边界限制的。UDP则不然。发送端应用程序每执行一次写操作,UDP模块就将其封装成一个UDP数据报并发送之。接收端必须及时针对每一个UDP数据报执行读操作(通过recvfrom系统调用,否则就会丢包(这经常发生在较慢的服务器上)。并且,如果用户没有指定足够的应用程序缓冲区来读取UDP数据,则UDP数据将被截断。

TCP传输是可靠的。首先,TCP协议采用发送应答机制,即发送端发送的每个TCP报文段都必须得到接收方的应答,才认为这个TCP报文段传输成功。其次,TCP协议采用超时重传机制,发送端在发送出一个TCP报文段之后启动定时器,如果在定时时间内未收到应答,它将重发该报文段。最后,因为TCP报文段最终是以IP数据报发送的,而IP数据报到达接收端可能乱序、重复,所以TCP协议还会对接收到的TCP报文段重排、整理,再交付给应用层。

UDP协议则和IP协议一样,提供不可靠服务。它们都需要上层协议来处理数据确认和超时重传。

protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议。不过这个值通常都是唯一的(前两个参数已经完全决定了它的值)。几乎在所有情况下,我们都应该把它设置为0,表示使用默认协议。

 

创建socket时,我们给它指定了地址族,但是并未指定使用该地址族中的哪个具体socket地址。将一个socket与socket地址绑定称为给socket命名。在服务器程序中,我们通常要命名socket,因为只有命名后客户端才能知道该如何连接它。客户端则通常不需要命名socket,而是采用匿名方式,即使用操作系统自动分配的socket地址。

“命名socket”,等价于“给socket绑定socket地址”,等价于“给socket分配IP地址和端口号”。

 

字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。

字节序描述大端字节序(Big Endian)低位字节存储在高地址处小端字节序(Little Endian)低位字节存储在低地址处

如0x12345678

大端模式:12 34 56 78

             低地址<--->高地址

小端模式:78 56 34 12

             低地址<--->高地址

为了防止数据在两台不同字节序的主机之间直接传递时解析错误,在通过网络传输数据时约定统一方式,这种约定称为网络字节序(Network Byte Order,统一为大端字节序。

字节序转换函数

 

IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

IP地址描述IPv4(Internet Protocol version 4)4字节地址族IPv6(Internet Protocol version 6)16字节地址族

IPv4与IPv6的差别主要是表示IP地址所用的字节数,目前通用的地址族为IPv4。IPv6是为了应对2010年前后IP地址耗尽的问题而提出的标准,即便如此,现在还是主要使用IPv4,IPv6的普及将需要更长时间。

IPv4标准的4字节IP地址分为网络ID和主机ID,且分为A、B、C、D、E等类型。

同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。

E类IP地址不区分网络ID和主机ID,为将来使用保留。

只需通过IP地址的第一个字节即可判断网络地址占用的字节数,因为我们根据IP地址的边界区分网络地址,如下所示

  • A类地址的首字节范围:0~127
  • B类地址的首字节范围:128~191
  • C类地址的首字节范围:192~223
  • D类地址的首字节范围:224~239
  • E类地址的首字节范围:240~255

还有如下这种表述方式

  • A类地址的首位以0开始
  • B类地址的前2位以10开始
  • C类地址的前3位以110开始
  • D类地址的前4位以1110开始
  • E类地址的前5位以11110开始

正因如此,通过套接字收发数据时,数据传到网络后即可轻松找到正确的主机。

通常,人们习惯用可读性好的字符串来表示IP地址,比如用点分十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。但编程中我们需要先把它们转化为整数(二进制数)方能使用。而记录日志时则相反,我们要把整数表示的IP地址转化为可读的字符串。下面3个函数可用于用点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换

 

inet_ntoa函数调用时需小心,返回值类型为char指针。返回字符串地址意味着字符串已保存到内存空间,但该函数未向程序员要求分配内存,而是在内部申请了内存并保存了字符串。也就是说,调用完该函数后,应立即将字符串信息复制到其他内存空间。因为,若再次调用inet_ntoa函数,则有可能覆盖之前保存的字符串信息。总之,再次调用inet_ntoa函数前返回的字符串地址值是有效的。若需要长期保存,则应将字符串复制到其他内存空间。

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

 
 

网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件,又称为网络适配器或网络接口卡NIC。其拥有MAC地址,属于OSI模型的第2层,它使得用户可以通过电缆或无线相互连接。每一个网卡都有一个被称为MAC地址的独一无二的48位串行号。网卡的主要功能:1. 数据的封装与解封装;2. 链路管理;3. 数据编码与译码。

MAC地址(Media Access Control Address,直译为媒体存取控制位址,也称为局域网地址、以太网地址、物理地址或硬件地址,它是一个用来确认网络设备位置的位址,由网络设备制造商生产时烧录在网卡中。在OSI模型中,第三层网络层负责IP地址,第二层数据链路层则负责MAC地址。MAC地址用于在网络中唯一标识一个网卡,一台设备若有一或多个网卡,则每个网卡都需要并会有一个唯一的MAC地址。

MAC地址的长度为48 位(6个字节,通常表示为12个16进制数,如:00-16-EA-AE-3C-40,就是一个MAC地址,其中前3个字节,16进制数00-16-EA代表网络硬件制造商的编号,它由IEEE(电气与电子工程师协会)分配,而后3个字节,16进制数AE-3C-40代表该制造商所制造的某个网络产品(如网卡)的系列号。只要不更改自己的MAC地址,MAC地址在世界是唯一的。形象地说,MAC地址就如同身份证上的身份证号码,具有唯一性。

端口号就是在同一操作系统内为区分不同套接字而设置的,因此无法将1个端口号分配给不同套接字。另外,端口号由16位构成,可分配的端口号范围是0~65535。但0~1023是知名端口(Well-known PORT,一般分配给特定应用程序,所以应当分配此范围之外的值。另外,虽然端口号不能重复,但TCP套接字和UDP套接字不会共用端口号,所以允许重复。例如:如果某TCP套接字使用9190号端口,则其他TCP套接字就无法使用该端口号,但UDP套接字可以使用。

总之,数据传输目标地址同时包含IP地址和端口号,只有这样,数据才会被传输到最终的目的应用程序(应用程序套接字)。

 

此结构体成员sa_data保存的地址信息中需包含IP地址和端口号,剩余部分应填充0,这也是bind函数要求的。而这对于包含地址信息来讲非常麻烦,继而就有了新的结构体sockaddr_in。

表示IPv4地址的结构体

 

其中,stuct in_addr定义如下

 

sin_zero[8]无特殊含义。只是为使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员。必需填充为0,否则无法得到想要的结果。

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

每次创建服务器端socket都要输入IP地址会有些繁琐,此时可如下初始化地址信息。

 

若采用这种方式,则可自动获取运行服务器端的计算机IP地址,不必亲自输入。而且,若同一计算机中已分配多个IP地址多宿主(Multi-homed计算机,一般路由器属于这一类,则只要端口号一致,就可以从不同IP地址接收数据。因此,服务器端中优先考虑这种方式。而客户端中除非带有一部分服务器端功能,否则不会采用。

客户端socket也需要绑定socket地址,但是不需要手动绑定,是操作系统自动绑定的。客户端的端口号是操作系统随机分配的,防止客户端出现启动冲突。

服务器为什么需要手动bind

  • 服务器的端口号是众所周知且不能随意改变的
  • 同一家公司的端口号需要统一规范化

socket被命名之后,还不能马上接受客户连接,我们需要使用如下系统调用来创建一个监听队列(连接请求队列)以存放待处理的客户连接

 

下面的系统调用从listen监听队列中接受一个连接

 

accept函数受理连接请求等待队列中待处理的客户端连接请求。函数调用成功时,accept函数内部将产生用于数据I/O的套接字,并返回其文件描述符。需要强调的是,套接字是自动创建的,并自动与发起连接请求的客户端建立连接。下图展示了accept函数调用过程。

如果说服务器通过listen调用来被动接受连接,那么客户端需要通过如下系统调用来主动与服务器建立连接

 

关闭一个连接实际上就是关闭该连接对应的socket,这可以通过如下关闭普通文件描述符的系统调用来完成

 

close系统调用并非总是立即关闭一个连接,而是将sockfd的引用计数减1。只有当sockfd的引用计数为0时,才真正关闭连接。多进程程序中,一次fork系统调用默认将使父进程中打开的socket的引用计数加1,因此我们必须在父进程和子进程中都对该socket执行close调用才能将连接关闭。

如果无论如何都要立即终止连接(而不是将socket的引用计数减1,可以使用如下的shutdown系统调用(相对于close来说,它是专门为网络编程设计的

 

shutdown能够分别关闭socket上的读或写,或者都关闭。而close在关闭连接时只能将socket上的读和写同时关闭。

对文件的读写操作read和write同样适用于socket。但是socket编程接口提供了几个专门用于socket数据读写的系统调用,它们增加了对数据读写的控制。其中用于TCP流数据读写的系统调用是

 
 

socket编程接口中用于UDP数据报读写的系统调用是

 
 

我们可以把socket封装起来,也可以不封装。

这里我们展示把服务器的socket封装,客户端的socket就不封装了。

udp_server.hpp

 

udp_server.cc

 

udp_client.cc

 

Makefile

 
 

这里我们展示把服务器的socket封装,客户端的socket就不封装了。

tcp_server.hpp

 

tcp_server.cc

 

tcp_client.cc

 

Makefile


特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


举报收藏 0评论 0
0相关评论
相关最新动态
推荐最新动态
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号