网络通信编程

1 TCP/IP协议和OSI模型

OSI模型
应用层HTTP、SMTP、SNMP、FTP、Telnet、SIP、SSH、NFS、RTSP、XMPP、Whois、ENRP等
表示层数据格式定义、数据转换/加密、XDR、ASN.1、SMB、AFP、NCP等
会话层ASAP、SSH、RPC、NetBIOS、ASP、Winsock、BSD Sockets等
传输层TCP、UDP、TLS、RTP、SCTP、SPX、ATP、IL等
网络层IP、ICMP、IGMP、IPX、BGP、OSPF、RIP、IGRP、EIGRP、ARP、RARP、X.25等
数据链路层数据组成可发送、接收的帧、以太网、令牌环、HDLC、帧中继、ISDN、ATM、IEEE 802.11、FDDI、PPP等
物理层例如铜缆、网线、光缆、无线电等

 

 

 

 

TCP/IP协议
应用层Telnet、FTP、HTTP(超文本传输协议)、DNS(域名解析)、SMTP(邮件传输)、MQTT等,是用来读取来自传输层的数据或者将数据传输写入传输层
传输层TCP(以字节流)、UDP(以数据包传输),实现端对端的数据传输
网络层IP、ICMP(如ping)、IGMP,主要负责网络中数据包的传送等
链路层(网络接口和物理层)以太网、令牌环网、FDDI、ARP(地址解析协议),通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡,它们一起处理与传输媒介(如电缆或其他物理设备)的物理接口细节。

2 TCP/IP协议

   TCP/IP协议包含两个协议一个是位于传输层的TCP协议,另一个是位于网络层的IP协议。TCP用来检测网络传输中差错的传输控制协议;IP是专门负责对不同网络进行互联的互联网协议,IP还有对UDP进行分片和重组,网络协议最大不饿能超过1500个字节,超过了就会进行分片和重组。

2.1 TCP

传输控制协议(英语:Transmission Control Protocol,缩写:TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。用户数据报协议(UDP)是同一层内另一个重要的传输协议。

   1、 TCP协议特点

         TCP协议(传输控制协议)特点:全双工面向连接的传输层协议,它能提高可靠想通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。

  2、TCP协议适用情况

  • 适用于对传输质量要求高,以传输大量数据的通信;
  • 在需要可靠数据传输的唱歌软件,通常适用TCP协议;
  • MSN/QQ等即通信软件的用户登录账户管理相关的功能;

2.1.1 TCP报文格式

TCP 报文是 TCP 层传输的数据单元,也叫报文段,TCP 报文的格式如下图所示:

  • 源端口:16位,源端口和IP地址的作用是标识报文的返回地址。

  • 目的端口:16位,端口指明接收方计算机上的应用程序接口。TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。

  • 序号和确认号:是TCP可靠传输的关键部分。

    • 序号,sequence number,保证网络传输数据的顺序性。是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每一个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。

    • 确认号,acknowledgment number,即ACK,用来确认确实有收到相关封包,内容表示期望收到下一个报文的序列号,用来解决丢包的问题。指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。

    • TCP 序列号和确认号公式

      • 序列号 = 上一次发送的序列号 + len(数据长度)。特殊情况,如果上一次发送的报文是 SYN 报文或者 FIN 报文,则改为 上一次发送的序列号 + 1。

      • 确认号 = 上一次收到的报文中的序列号 + len(数据长度)。特殊情况,如果收到的是 SYN 报文或者 FIN 报文,则改为上一次收到的报文中的序列号 + 1。

  • 首部长度/数据偏移:4bits。由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。

  • 保留:4bits。为将来定义新的用途保留,现在一般置0。

  • 标志位:CWR、ECN-Echo、URG、ACK、PSH、RST、SYN、FIN,每一个标志位表示一个控制功能。

    • CWR:Congestion window reduced,拥塞窗口减少。拥塞窗口减少标志被发送主机设置,用来表明它接收到了设置ECE标志的TCP包。拥塞窗口是被TCP维护的一个内部变量,用来管理发送窗口大小。

    • ECN-Echo:显式拥塞提醒回应。当一个IP包的ECN域被路由器设置为11时,接收端而非发送端被通知路径上发生了拥塞。ECN使用TCP头部来告知发送端网络正在经历拥塞,并且告知接收端发送段已经受到了接收端发来的拥塞通告,已经降低了发送速率。

    • URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针

      • ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。

    • PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。

    • RST:重置连接标志,为1时,表示释放连接,重连。用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。

    • SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。

    • FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。

  • 窗口:占16bits。滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小是一个16bit字段,因而窗口大小最大为65535。

  • 校验和:占16bits。奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。

  • 紧急指针:占16bits。只有当 URG 标志置 1 时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。

  • 选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍

  • 数据部分:TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段。

2.1.2 UDP

   1、UDP协议特点

         UDP(用户数据报协议):是不可靠的无连接协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。

   2、UDP协议适用情况

  • 在接收到数据,给出应答较困难的网络中使用UDP(如:无线网络);
  • 适用于广播、组播通信中;

1.1.3 TCP与UDP区别

 共同点:

  1. TCP和UDP都是传输层协议
  2. 多路复用:TCP和UDP允许多个应用程序在同一网络连接上并发通信,通过端口号区分不同的通信流。
  3. 面向字节流:它们都是面向字节流的协议,可以传输任意类型的数据。
  4. 拥塞控制和流量控制:虽然TCP和UDP在实现上有所不同,但它们都可以支持拥塞控制和流量控制机制,以适应网络条件和接收方的处理能力
  5. 错误检测:两者都提供了一定程度的错误检测功能,TCP使用更复杂的校验和,而UDP使用较为简单的校验和。
  6. 支持广播和多播:TCP和UDP都可以支持广播和多播传输,允许同时向多个目的地发送数据。
  7. 动态端口分配:它们允许使用动态端口号,这在客户端应用程序生成大量短连接时非常有用。
  8. 不关心网络层的细节:TCP和UDP抽象了网络层的细节,使得应用程序可以不必关心底层网络的具体实现。

 不同点:

简单来说:TCP - 有连接、可靠;UDP - 无连接、不保证可靠、效率高;

  1. 连接方式:
    1. TCP是面向连接的协议,需建立连接
    2. UDP是无连接的,无需建立连接即可传输数据。
  2. 数据传输/可靠性:
    1. TCP提供可靠的数据传输,确保数据包正确、完整、按顺序到达;
    2. UDP则不保证数据包的可靠传输,可能会导致丢包、乱序或重复。数据传输;
  3. 速度:由于TCP需要建立连接和保证数据的可靠传输,其速度相对较慢;UDP由于简单直接,没有建立连接的步骤,因此速度更快。
  4. 报文结构:

    1. 有复杂的头部结构,包含序列号、确认号、窗口大小等控制信息。

    2. 头部结构简单,主要包含源端口、目的端口和长度校验等。

  5. 流量控制和拥塞控制:

    1. TCP:内置流量控制和拥塞控制机制,防止网络过载。

    2. UDP:不提供流量控制和拥塞控制,这些功能需要应用层实现。

2.2 IP协议

IP 协议(Internet Protocol),又称之为网际协议,IP 协议处于 IP 层工作,它是整个 TCP/IP 协议栈的核心协议,上层协议都要依赖 IP 协议提供的服务,IP 协议负责将数据报从源主机发送到目标主机,通过 IP 地址作为唯一识别码。

  • IP 协议是一种无连接的不可靠数据报交付协议,协议本身不提供任何的错误检查与恢复机制。
  • IP地址是协议地址MAC地址是硬件地址,所处的层级不一样。

2.2.1 IP地址分类

每一个IP地址包括两部分:网络地址+主机地址,IP地址分为A、B、C、D、E 五类,五类地址对所支持的网络数和主机数有不同的组合。     网络号的位数直接决定了可以分配的网络数(计算方法:2^网络号位数-2);主机号的位数则决定了网络中最大的主机数(计算方法:2^主机号位数-2)。【这里减2的原因是主机地址全0表示“本主机”所连接到的单个网络地址,而全1表示“所有”,即该网络上所有主机】。

  1. A类地址:1.0.0.1—126.155.255.254
  2. B类地址:128.0.0.1—191.255.255.254
  3. C类地址:192.0.0.1—223.255.255.254
  4. D类地址:224.0.0.1—239.255.255.254
  5. E类地址:240.0.0.1—255.255.255.254

2.2.2 子网掩码

子网掩码32个二进制位组成,对应ip地址的网络部分用1表示,对应ip地址的主机备份用0表示;

网络地址 = ip地址和子网掩码作逻辑“与”运算

子网掩码 & 判断是否在同一子网
IP 与 子网掩码 做 按位与 ,就可以得出该 IP 的子网网段。
如:

  • 子网掩码:255.255.255.0
  • IP-1: 192.168.1.2 & 255.255.255.0 = 192.168.1.0
  • IP-2: 192.168.1.123 & 255.255.255.0 = 192.168.1.0
  • IP-3: 192.168.2.123 & 255.255.255.0 = 192.168.2.0
  • 因为 192.168.1.0 = 192.168.1.0,所以IP-1与IP-2处于同一子网。
  • 因为 192.168.1.0 != 192.168.2.0,所以IP-1与IP-3不在同一子网。

在发数据包时,子网的作用

  • 若源IP和目标IP在同一子网:直接获取目标IP主机的MAC,然后把数据包丢出去。
  • 若源IP和目标IP不在同一子网:获取默认网关的 MAC ,然后把数据包丢给默认网关那边。

2.2.3 广播地址

2.2.3.1 受限广播地址

受限广播地址是网络号与主机号都为 1 的地址:255.255.255.255

为了避免这个广播地址往整个互联网里发送广播包,在任何情况下,路由器都会禁止转发目的地址为 255.255.255.255 的广播数据包,要不然会给整个互联网带来网络性灾难。

2.2.3.2 直接广播地址

接广播地址是主机号全为 1 而得到的地址,广播地址代表本网络内的所有主机,使用该地址可以向网络内的所有主机发送数据。

A 类地址的广播地址为:XXX.255.255.255

B 类地址的广播地址为:XXX.XXX.255.255

C 类地址的广播地址为:XXX.XXX.XXX.255

2.2.3.3 多播广播地址

多播地址属于分类编址中的 D 类地址D 类地址只能用作目的地址,而不能作为主机中的源地址。

2.2.4 本网络主机地址

IP 地址 32bit 全为 0 的地址(0.0.0.0)表示的是本网络本主机,只能做源IP地址。

在当设备启动时但又不知道自己的 IP 地址情况

2.2.5 IP报文格式

  •  版本号:4 bit,这个字段规定了数据报的 IP 协议版本。
    • IPv4:值为4;
    • IPv6:值为6;
  • 首部长度:4 bit,用于记录 IP 首部的数据的长度,单位:字,最大可以表示15*4=60字节。IP首部的长度默认是20 byte,但是如果有选项字段,就不止20 byte了。
  • 服务类型(TOS:type of service):8 bit,
    • (1)该字段用于描述当前IP数据报急需的服务类型,如:
      • 最小延时;
      • 最大吞吐量;
      • 最高可靠性;
      • 最小费用等。
    • (2)路由器在转发数据报时,可以根据该字段的值来为数据报选择最合理的路由路径。
  • 总长度:16 bit,是 IP 数据报的总长度(IP首部+数据区)。单位:字节。最大能表示65535字节。数据报很少有超过1500字节的,因为以太网数据帧的数据最大长度为1500字节。如果一个IP数据报过大时,需要进行分片处理。
  • 标识字段:16bit,用于标识IP层发出去的每一份IP数据报,每发送一份,该值+1。
    • (1) 标识字段、标志字段和13位偏移字段常在IP数据报分片时使用
    • (2) 如果IP数据报被分片,该字段在每个分片的IP数据报上是一致的,表示属于同一个IP数据报。
    • (3) 在接收端会根据该字段识别同一个IP数据报进行重装。
  • 标志:3bit,
    • (1) 第一个bit保留未用。
    • (2) 第二个bit是不分片标志位:
      • 0:则表示 IP 层在必要的时候可以对其进行分片处理。
      • 1:则表示 IP 数据报在发送的过程中不允许进行分片,如果这个 IP 数据报的大小超过链路层能承载的大小,这个 IP 数据报将被丢弃。
    • (3) 第三个bit是更多分片标志位:
      • 0:后续没有更多分片。即是当前分片的IP数据报是最后一个分片。
      • 1:表示后续还有分片。即是当前分片的IP数据报不是最后一个分片。
  • 分段偏移量:13 bit。表示当前分片所携带的数据在整个 IP 数据报中的相对偏移位置。单位:8字节(2个字)。目标主机要接收到从0分片偏移量到最高分片偏移量的所有分片才能进行组装出完整的IP数据报。
  • 生存时间(Time-To-Live,TTL):8 bit。该字段用来确保数据报不会永远在网络中循环。IP数据报每经过一台路由器处理,该值-1。如果TTL字段下降到0,则路由器会丢弃该数据报,且会返回一个 ICMP 差错报文给源主机
  • 协议:8 bit。表示上层协议类型。即是表示数据区的数据是哪个协议的数据报。如:
    • (1) 6:表示TCP协议。
    • (2) 17:表示UDP协议。
    • (3) 其它值可以自行度娘。
  • 首部校验和:16bit,只针对IP首部做校验,并不关系数据区在传输过程中是否出错,所以对于数据区的校验需要由上层协议负责。
    • 路由器要对每个收到的 IP 数据报计算其首部检验和,如果数据报首部中携带的检验和与计算得到的检验和不一致,则表示出现错误,路由器一般会丢弃检测出错误的 IP 数据报。
    • 需要注意的是,由于IP数据报首部的TTL字段每结果应该路由器都会-1,所以IP数据报首部检验和字段每经过一个路由器都要重新计算赋值

检验和计算可能由网络网络驱动,协议驱动,甚至是硬件完成。

高层校验通常是由协议执行,并将完成后的包转交给硬件。

比较新的网络硬件可以执行一些高级功能,如IP检验和计算,这被成为checksum offloading。网络驱动不会计算校验和,只是简单将校验和字段留空或填入无效信息,交给硬件计算。

发送数据时,首部校验和计算:二进制反码求和。

  1. 把IP数据包的校验和字段置为全0。
  2. 将首部中的每 2 个字节当作一个数,依次求和。
  3. 把结果取反码。
  4. 把得到的结果存入校验和字段中。


接收数据时,首部校验和验证过程:

  1. 首部中的每 2 个字节当作一个数,依次进行求和,包括校验和字段。
  2. 检查计算出的校验和的结果是否全为1(反码应为16个0)。
  3. 如果等于零,说明被整除,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。

为什么计算出的校验和结果全为1?
因为如果校验依时次求和,不包含校验和字段的话,得出的值就是校验和字段的反码。
校验和的反码和校验和求和,当然是全1啦。

IP/ICMP/IGMP/TCP/UDP等协议的校验和算法都是相同的。

参考:https://www.cnblogs.com/lizhuming/p/16859723.html

  • 选项字段:0到40字节。对于IP数据报首部来说,其大小必须为4字节的整数倍。如果选项字段长度不为4的倍数,则需要用0进行填充。在 LwIP 中只识别选项字段,不会处理选项字段的内容。该字段在IPv6报文中已经被移除了。
  • 数据区字段:IP 数据报的最后的一个字段,装载着当前IP数据报的数据,是上层协议的数据报。

 2.2.6 wireshark包分析

1.2 TCP/IP协议栈的数据封装与解封

封装:数据添加各层协议的首部;解封装:在各层间除去自层的首部


2 服务器与客户端

 2.1 服务器与客户端流程

2.2 服务器与客户端通信数据帧

为什么要自己定义协议包呢?一个原因是因为真正的业务逻辑往往都是复杂的,不会是很单纯的字符串或数字。通讯时网络传输是以字节为单位的。这一串串数据流在交互,如何能在数据流每次交互中正确识别出。真正有用的东西,就要双方定义一套方法。发送方把要传输的东东放在一起定义好包,转成网络字节序发送出去。接收方收到后,再按包的定义,拆解出有用的数据。
 

包定义各有各的方法,但通常会包含下面几部份:

  1. 包类型 :用于业务逻辑区分;
  2.  容长度 :传输数据的长度;
  3.  消息内容 :本次实际要传输的数据;
  4.  校验位 :检查用,防止错包出现;

包通常都是结构体(两种):

typedef struct_Packet
{
	unsigned short pkg_type;
	unsigned int pkg_len;
	char pkg_content[pkg_len];
	unsigned short pkg_cs;
}Packet;
typedef struct_PacketHeader
{
	unsigned short pkg_type;
	unsigned short pkg_len;
}PacketHeader;
 
typedef struct_PacketBody
{
	struct _PacketHeader pkg_header;
	char pkg_content[pkg_len];
	......
}Packet;

3 网络编程

二、socket

  socket特点:

  1.  是一个编程接口;
  2. 是一种特殊的文件描述符;
  3. 可以使用TCP/IP<流式套接字,面向连接、数据可靠>、可以使用UDP<数据报套接字:无连接、不保证数据可靠>;
  4. 使用原因:主要解决不同主机之间数据通信,并且保证数据的高效传输,即通过socket套接字文件。

三、IP地址

 (一)IP地址特点

  1. IP地址是Internet中的主机的标识;
  2. Internet中的主机要与别的机器通信必须具有一个IP地址;
  3. IP地址分为32位(IPV4)、128位(IPV6);
  4. 每个数据包都必须携带目的IP地址和源IP地址,路由依靠此信息为数据包选择路由;

 (二)IP地址解析

  •   IP地址 = 网络地址 + 主机地址
  •   网络地址 = IP地址 & 子网掩码
  •   主机地址 = IP地址 & -子网掩码

   IP地址分类:A(1+3)、B(2+2)、C(3+1)、D、E类,其中D、E类用于广播。

  (三)IP地址的转换

     1、inet_addr()

      将点分形式的IP地址转换为网络IP地址(大端存储的无符号32位整形)。        

        #include <arpa/inet.h>

        函数原型: in_addr_t inet_addr(const char *address);//返回转换后的地址

        address是以NULL结尾的点分IPV4字符串。

        返回32位的地址。如果字符串包含不合格法的IP地址,则函数返回-1;

struct in_addr addr;
addr.s_addr = inet_addr(192.168.1.122);

     2、inet_aton()

     #include <arpa/inet.h>

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

     3、inet_ntoa()

 #include <arpa/inet.h>

 char *inet_ntoa(struct in_addr in);

  in是IPV4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL。

 (四)字节序转换转换

     网络中传输的数据必须按网络字节序,即大端字节序。

   1、大小端

     大端序:低字节存储在高低序;

     小端序:低字节存储在高地址;

 2、字节序转换函数

  (1)主机字节序到网络字节序

       #include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

    (2)网络字节序到主机字节序

          #include <arpa/inet.h>

          uint32_t ntohl(uint32_t netlong);

          uint16_t ntohs(uint16_t netshort);

二、通信相关函数

(一)socket

#include <sys/types.h>          
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

 domain是地址族:AF_INET  ---  IPv4 Internet protocols
                                AF_INET6 ---  IPv6 Internet protocols

                                AF_UNIX --- unix协议,本地多进程间通信

  type是套接字类型:SOCK_STREAM --- 流式套接字(TCP)

                                   SOCK_DGRAM --- 数据报套接字(UDP)

                                   SOCK_RAW --- 原始套接字

   protocol --- 通常设为0

(二)bind

    1、地址相关数据结构

     (1)通用地址结构        

struct sockaddr {
               sa_family_t sa_family; //地址族,AF_xxx
               char        sa_data[14]; //14字节协议地址(存储服务器的IP地址和端口号)
           };

     (2)Internet协议地址结构

struct sockaddr_in struct sockaddr_un
{
    u_short sin_family; //地址族,AF_INET,2bytes
    u_short sin_port; 端口,2bytes =htons()
    struct in_addr sin_addr; //IPV4地址,4bytes 见下面结构体
    char sin_zero[8];    //8bytes unused,作为填充
};

   用法:

/*定义一个 struct sockaddr_in类型的变量并清空*/

struct sockaddr_in myaddr;

memset(&myaddr,0,sizeof(myaddr));
/*填充地址信息*/
myaddr.sin_family = PF_INET;
myaddr.sin_port = htons(8888);//端口号
myaddr.sin_addr.s_addr = inet_addr("192.168.1.123");

     (3) IPV4地址结构

struct in_addr
{
    in_addr_t s_addr;
};

  2、bind()

#include <sys/types.h>       
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

   sockfd --- socket()返回的文件描述符

   struct sockaddr --- 上面通用结构体,addr是指向sockaddr_in 结构的指针,包含本机IP地址和端口号

   addrlen --- sockaddr地址结构的长度;sizof(struct sockaddr_in)

  返回值:0、-1(出错)

(三)listen

     功能:启动服务器,启动监听。启动监听之后,套接字由主动变为被动

  #include <sys/types.h>       
  #include <sys/socket.h>

  int listen(int sockfd, int backlog);

    sockfd --- 监听连接的套接字

    backlog --- 指定了正在等待连续的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。

                       一般设其值为0

    返回值:0或-1

 (四)accept

 #include <sys/types.h>          
 #include <sys/socket.h>

 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  sockfd --- 监听套接字

  addr --- 对方地址

  addrlen --- 地址长度

                    socklen_t addrlen

                    addrlen = sizeof(socklen_t );

                    addrlen这个参数设为&addrlen

  返回值:已建立好连接的套接字(文件描述符)或-1

 (五)connect

#include <sys/types.h>          
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
 sockfd --- socket()返回的文件描述符

 addr --- 服务器端的地址信息

 addrlen --- addr的长度

(六)send

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
 buf --- 发送缓冲区首地址

 len --- 发送的字节数

 flags --- 发送方式

 (七)recv

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  (八) 服务器和客户端代码示例

      server.c

include <unistd.h>*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    struct sockaddr_in srvaddr1;
    socklen_t addrlen = sizeof(socklen_t) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//调用本地IP地址

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,0);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }

    /*等待服务器的链接请求*/
    connfd = accept(socketfd,(struct sockaddr *)&srvaddr1,&addrlen);
    if( connfd == -1)
    {
        perror("server->accept->error");
        return -1;
    }

    printf("connect success\n");
    while(1)
    {
        printf("%s",inet_ntoa(srvaddr1.sin_addr));//获取客户端的地址
        /*
        ret = read(connfd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("srvaddr->read->error");
            return -1;
        } 
        fputs(buf,stdout);

        memset(buf,0,sizeof(sizeof(buf)));
        fgets(buf,sizeof(buf),stdin);
        ret = write(connfd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("server->write->error");
            return -1;
        }
        */
        memset(buf,0,sizeof(buf));
        ret = recv(connfd,buf,sizeof(buf),0);
        if( ret == -1)
        {
            perror("srvaddr->read->error");
            return -1;
        } 
        fputs(buf,stdout);
/*
        memset(buf,0,sizeof(sizeof(buf)));
        fgets(buf,sizeof(buf),stdin);
        ret = send(connfd,buf,sizeof(buf),0);
        if( ret == -1)
        {
            perror("server->write->error");
            return -1;
        }
        */
    }
    return 0;
}


     client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>

int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_STREAM,0);

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }

    /*请求连接服务器*/
    memset(&cltaddr,0,sizeof(struct sockaddr));
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
    ret = connect(sockefd,(const struct sockaddr *)&cltaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("cltaddr->connect->error");
        return -1;
    }

    printf("connect is success\n");
    memset(buf,0,sizeof(buf));
    while(1)
    {
        /*
        fgets(buf,sizeof(buf),stdin);
        ret = write(sockefd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("cltent->write->error");
            return -1;
        }
        memset(buf,0,sizeof(buf));
        ret = read(sockefd,buf,sizeof(buf));
        fputs(buf,stdout);
        */
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        ret = send(sockefd,buf,sizeof(buf),0);
        if( ret == -1)
        {
            perror("send");
            return -1;
        }
   /*     memset(buf,0,sizeof(buf));
        ret = recv(sockefd,buf,sizeof(buf),0);
        fputs(buf,stdout);
        */
    }
    return 0;
}


  用进程来实现服务器与客户端,一个进程用于读数据,一个进程用于写数据: 

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:117187xxx@qq.com
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ***********************************************************************/
#include <unistd.h>*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(socklen_t) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,0);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }

    /*等待服务器的链接请求*/
    connfd = accept(socketfd,(struct sockaddr *)&srvaddr,&addrlen);
    if( connfd == -1)
    {
        perror("server->accept->error");
        return -1;
    }

    printf("connect success\n");
    while(1)
    {
        memset(buf,0,sizeof(buf));
        pid_t pid1;
        pid1 = fork();
        if( pid1 == -1 )
        {
            perror("fork");
            return -1;
        }
        else if( pid1 == 0 )
        {
            ret = read(connfd,buf,sizeof(buf));
            if( ret == -1)
            {
                perror("srvaddr->read->error");
                return -1;
            } 
            fputs(buf,stdout);
        
        }
        else
        {
            memset(buf,0,sizeof(sizeof(buf)));
            fgets(buf,sizeof(buf),stdin);
            ret = write(connfd,buf,sizeof(buf));
            if( ret == -1)
            {
                perror("server->write->error");
                return -1;
            }
        }
    }
    return 0;
}
/************************************************************************
> 文件名称:client.c
> 创建者:wl
> 邮箱:117187xxx@qq.com
> 时间:2017年09月19日 星期二 14时57分49秒
> 描述:
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>

int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_STREAM,0);

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }

    /*请求连接服务器*/
    memset(&cltaddr,0,sizeof(struct sockaddr));
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    ret = connect(sockefd,(const struct sockaddr *)&cltaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("cltaddr->connect->error");
        return -1;
    }

    printf("connect is success\n");
    memset(buf,0,sizeof(buf));
    while(1)
    {
        memset(buf,0,sizeof(buf));  
        pid_t pid1;
        pid1 = fork();
        if( pid1 == -1 )
        {
            perror("fork");
            return -1;
        }
        else if( pid1 == 0 )
        {
            fgets(buf,sizeof(buf),stdin);
            ret = write(sockefd,buf,sizeof(buf));
            if( ret == -1)
            {
                perror("cltent->write->error");
                return -1;
            }
        }
        else
        {
            memset(buf,0,sizeof(buf));
            ret = read(sockefd,buf,sizeof(buf));
            fputs(buf,stdout);
        }
    }
    return 0;
}

UDP

  UDP不需要建立连接,UDP服务器和客户端流程如下:

 

一、UDP的发送接收函数 

  (一)sendto()

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

  sockfd --- 套接字文件描述符

  buf --- 缓冲区的首地址

  len --- 发送数据的大小

  flags  --- 发送方式,默认设为0

  addrlen --- 源地址空间大小

  返回值 --- 成功:发送数据的字节数

                   失败: -1

   (二) recvfrom() 

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

二、UDP实现服务器和客户端 

  server.c

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:117187xxx@qq.com
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <stdlib.h>
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(struct sockaddr) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }
    
    while(1)
    {
        memset(buf,0,sizeof(buf));
        ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr *)&srvaddr,&addrlen);
        if( ret == -1 )
        {
            perror("recvfrom");
            return -1;
        }
        fputs(buf,stdout);
    }
    return 0;
}

    client.c 

9/************************************************************************
> 文件名称:client.c
> 创建者:wl
> 邮箱:117187xxx@qq.com
> 时间:2017年09月19日 星期二 14时57分49秒
> 描述:
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include <arpa/inet.h>

int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_DGRAM,0);
    socklen_t addrlen = sizeof(struct sockaddr);

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }

    /*请求连接服务器*/
    memset(&cltaddr,0,sizeof(struct sockaddr));
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    while(1)
    {
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        ret = sendto(sockefd,buf,sizeof(buf),0,(struct sockaddr *)&cltaddr,addrlen);
        if( ret == -1 )
        {
            perror("sendto");
            return -1;
        }
    }
    
}

三、网络信息检索函数 

    1、getsockopt()/setsockopt()

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);//获取一个套接口选项
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);//设置一个套接口选项

  sockfd --- 套接字

  level --- 指定控制套接字的层次,可以取下列三种值:

              SOL_SOCKET:通用套接字选项

               IPPROTO_IP:IP选项

               IPPROTO_TCP:TCP选项

  optname --- 指定控制的方式(选项名称)

  optval --- 获得或者是设置套接字选项。根据选项名称的数据类型进行转换

  optlen --- 选项值的最大长度

广播

  • 如果同时发给局域网中的所有主机,称为广播; 

  • 只有用户数据报(UDP协议)套接字才能广播;

 一、广播地址

  1. 以192.168.1.0(255.255.255.0网段为例,最大的主机地址192.168.1.255代表该网段的广播地址;
  2. 发送到该地址的数据包被所有的主机接收;
  3. 255.255.255.255在所有网段中都代表广播地址;

二、广播接收

  1. 绑定IP地址(广播IP或0.0.0.0)和端口号;
  2. 绑定的端口号必须和发送方指定的端口相同;

三、广播代码实现

     server.c

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:1171878xxx@qq.com
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(struct sockaddr) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("192.168.2.255");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }
    
    while(1)
    {
        memset(buf,0,sizeof(buf));
        ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr *)&srvaddr,&addrlen);
        if( ret == -1 )
        {
            perror("recvfrom");
            return -1;
        }
        fputs(buf,stdout);
    }
    return 0;
}

   client.c


/************************************************************************
> 文件名称:client.c
> 创建者:wl
> 邮箱:1171878xx@qq.com
> 时间:2017年09月19日 星期二 14时57分49秒
> 描述:
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include <arpa/inet.h>
 #include <netinet/in.h>
#include <netinet/ip.h>
int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_DGRAM,0);
    socklen_t addrlen = sizeof(struct sockaddr);
   // socklen_t addrlen;

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }
    printf("11\n");
    /*请求连接服务器*/
    int opt = 1;
    ret = setsockopt(sockefd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));
    printf("b\n");
    if( ret == -1 )
    {
        perror("setsockopt");
        return -1;
    }
    printf("c\n");
    memset(&cltaddr,0,sizeof(struct sockaddr_in));
    printf("d\n");
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("192.168.2.22");
    while(1)
    {
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
       ret = sendto(sockefd,buf,sizeof(buf),0,(struct sockaddr *)&cltaddr,addrlen);
        if( ret == -1 )
        {
            perror("sendto");
            return -1;
        }
    }
    
}

组播 

  • 组播(又称为多播)是一种折中的方式,只有加入某个多播组的主机,才能收到数据;
  • 多播方式既可以发给多个主机,又能避免像广播那样带来过多的负载;
  • D类地址(组播地址);
  • 不分网络地址和主机地址,第一个字节的前4位固定为1110,组播地址:224.0.0.1 - 239.255.255.255;
  • 绑定IP地址(加入组的组IP或0.0.0.0)和端口;
  • 绑定的端口必须和发送方指定的端口相同;

  一、加入组播 

struct ip_mreq
{
    struct in_addr imr_multiaddr;
    struct in_addr imr_interface;
};

 /*加入组播*/
struct ip_mreq mreq;
memset(&mreq,0,sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(socketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

二、组播代码实现 

  server.c

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:11718782xx@qq.com
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(struct sockaddr) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }

    /*加入组播*/
    struct ip_mreq mreq;
    memset(&mreq,0,sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    setsockopt(socketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

    
    /*设置服务器的IP地址和段口号*/

    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    while(1)
    {
        memset(buf,0,sizeof(buf));
        ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr *)&srvaddr,&addrlen);
        if( ret == -1 )
        {
            perror("recvfrom");
            return -1;
        }
        fputs(buf,stdout);
    }
    return 0;
}

  client.c 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

int main()
{
	int ret;
	int sockfd;
	char buf[256];
	struct sockaddr_in srvaddr;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd == -1) {
		perror("socket");
		return -1;
	}
	memset(&srvaddr, 0, sizeof(struct sockaddr_in));
	srvaddr.sin_family = AF_INET;
	srvaddr.sin_port = htons(9999);
	srvaddr.sin_addr.s_addr = inet_addr("224.10.10.1");

	while(1) {
		fgets(buf, sizeof(buf), stdin);
		ret = sendto(sockfd, buf, sizeof(buf), 0, (const struct sockaddr *)&srvaddr, sizeof(struct sockaddr));
		if (ret == -1) {
			perror("sendto");
			return -1;
		}
		printf("ret = %d\n", ret);
	}
	

	return 0;
}

I/O模型

   在UNIIX/Linux下主要有4中I/O模型:阻塞I//O、非阻塞I/O、I/O多路复用、信号驱动IO(一种异步通信模型)。

一、阻塞I/O

1、特点:最常用、最简单、效率低。

2、以read函数为例进行解释

    进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没数据可读,函数read将发生阻塞,它一直阻塞下去,等待套接字的接收缓冲区有数据可读。当缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。

int main()
{
    char buf[128];
    memset(&buf,0,sizeof(buf));
    while(1)
    {
        read(0,buf,sizeof(buf));
        printf("%s\n",buf);
    }
    return 0;
}

二、非阻塞I/O

  1、特点

  •  可防止进程阻塞在I/O操作上,需要轮询;
  • 当I/O操作不能马上完成,立刻返回一个错误;

  2、以fcntl为例

#include <unistd.h>
#include <fcntl.h>

 int fcntl(int fd, int cmd, ... /* arg */ );

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
	int ret;
	char buf[128];
	int flag = fcntl(0,F_GETFL,0);
	flag |= O_NONBLOCK;
	fcntl(0,F_SETFL,flag);
	while(1)
	{
		memset(buf,0,sizeof(buf));
		ret = read(0,buf,sizeof(buf));
		printf("buf = %s\n",buf);
		sleep(1);
	}
	return 0;
}

三、I/O多路复用

       允许同时对多个I/O进行控制,主要有三种机制:select()、poll()、epoll_ctl()。

    select()、poll()、epoll_ctl()的思路:

  1. 将所有要处理的文件描述符存储到一张表中;
  2. 检测表当中有没有已经就绪的文件描述符,如果有,返回就绪的文件描述符;
  3. 轮询所有就绪的文件描述符;
while(1)
{
    if(listenfd)
        //说明是新的客户端发起了请求;
    if(confd)
        //说明是已经连接的客户端发送了数据请求;
    if(/*普通文件*/)
        //说明普通文件的数据;
    if(0)
        //标准输入;
}

(一)select

    select机制思路及步骤:

  1. 根据描述符处理事件不同,创建不同的表;例如将所有文件描述符添加到一张读的表中;
  2. 检测表当中是否有就绪的文件描述符,如果有返回状态,同时返回就绪的文件描述符;
  3. 轮询

         (1)找到那些文件描述符;

         (2)处理数据;

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
 nfds --- 最大文件描述符+1

 readfds --- 所有要读的文件描述符的集合(读文件件描述表)

 writefds --- 所有要写的文件描述符的集合(写文件件描述表)

 exceptfds --- 其他要向我们通知的文件描述(错误处理文件描述符表)

 timeout --- 超时设置:NULL:一直阻塞,直到有文件描述符就绪或出错

                                           0:仅仅检测文件描述符集的状态,然后立即返回

                                    不为0:在指定时间内,如果没有事件发生,则超时返回

 返回值:成功 --- 准备就绪的文件描述符的个数

               失败 --- -1

               超时返回 ---- 0

 设置文件描述符的几个宏:

  1. void FD_CLR(int fd, fd_set *set);   //将fd从集合set当中清除
  2. int  FD_ISSET(int fd, fd_set *set);  //判断fd是否在集合set中
  3. void FD_SET(int fd, fd_set *set);   //将fd添加到集合set中
  4. void FD_ZERO(fd_set *set);          //清空集合set

  select实现并发服务器:

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:11718xxxx@qq.com
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include <unistd.h>*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<pthread.h>
int select_func(int connfd)
{
    char buf[100];
    int ret;
    
        memset(buf,0,sizeof(buf));
        ret = read(connfd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("srvaddr->read->error");
            return -1;
        } 
        if( ret == 0 )
        {
            return 1;
        
        }

        fputs(buf,stdout);

       

    return 0;
}

int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    int addrlen = sizeof(struct sockaddr_in ) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,1024);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }
    
    int fd;
    int nfds;
    /* 创建一个集合,用来存储所要读处理的所有的文件描述符*/
    fd_set readfds;
    fd_set rfds;
    /* 添加要读处理的文件描述符 */
    FD_SET(socketfd,&readfds);
    nfds = socketfd + 1;

    while(1)
    {
        printf("==========\n");
            rfds=readfds;
        /* 检测集合当中是否有继续的文件描述符 */
        ret = select(nfds,&rfds,NULL,NULL,NULL);
        /* 轮循 */
        for(fd = 0;fd < nfds;fd++)
        {
            /* 判断,寻找就绪的文件描述符 */
            if( FD_ISSET(fd,&rfds) )
            {
                /* 如果是监听套接字socketfd,则建立连接 */
                printf("---------------\n");
                if( fd == socketfd )
                {
                    printf("++++++++++++++++\n");
                    memset(&srvaddr,0,sizeof(srvaddr));
                    connfd = accept(socketfd,(struct sockaddr *)&srvaddr,&addrlen);
                    if( -1 == connfd )
                    {
                        perror("accept");
                        return -1;
                    }
                    printf("connnect success connfd = %d\n",connfd);

                    FD_SET(connfd,&readfds);
                    if( connfd >= nfds )
                    {
                        nfds = connfd + 1;
                    }
                }
                else
                {
                    ret = select_func(fd);
                    if( ret == 1 )
                    {
                        FD_CLR(fd,&readfds);
                        close(fd);
                    }
                }
            }
        }
    }
    close(socketfd);
    return 0;
}

(二)poll

   poll机制思路及步骤:

  1. 创建一个集合文件描述符及其所要处理的事件;
  2. 检测集合当中是否有事件发生(是否有文件描述符准备就绪);如果有,返回有的状态,同时返回就绪的文件描述符及事件;
  3. 轮询;

          (1)找到发生的事件;

          (2)处理数据;

功能:检测是否有文件描述和事件处于就绪状态,有返回,没有一直阻塞。 

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  fds --- 事件和文件描述符的集合

  nfds --- 最大文件描述符+1

  timeout --- 超时时间(ms)

  struct pollfd {
               int   fd;              /* 文件描述符 */
               short events;     /* 需要的事件*/
               short revents;    /* 返回的事件 */
           };

 poll()机制实现并发服务器:

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:117187xxx@qq.com
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ***********************************************************************nclude <unistd.h>*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include<pthread.h>
#include<poll.h>
int poll_func(int connfd)
{
    char buf[100];
    int ret;
    
    memset(buf,0,sizeof(buf));
    ret = read(connfd,buf,sizeof(buf));
    if( ret == -1)
    {
        perror("srvaddr->read->error");
        return -1;
    } 
    if( ret == 0 )
    {
        return 1;
    }
    fputs(buf,stdout);
    return 0;
}

int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    int addrlen = sizeof(struct sockaddr_in ) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,1024);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }
    /* 创建集合,并且将集合当中的每一个数组元素的fd成员赋值为-1;*/
    int nfds;
    int fd;
    int i,j;
    struct pollfd fds[1024];
    for(i=0;i<1024;i++)
    {
        fds[i].fd = -1;
    }
    /* 添加所需要处理的事件和文件描述符 */
    fds[0].fd = socketfd;
    fds[0].events = POLLIN;
    nfds = socketfd + 1;
    while(1)
    {
        /* 检测集合当中是否有继续的文件描述符 */
        ret = poll(fds,nfds,5000);
        if( ret == -1)
        {
            perror("poll");
            return -1;
        }
        else if( ret == 0 )
        {
            printf("timeout\n");
            continue;
        }

        /* 轮循 */
        for( i=0;i<1024;i++)
        {
            /* 判断是什么事件  */
            if( POLLIN == fds[i].events)
            {
                /* 判断,寻找就绪的文件描述符 */
                if( fds[i].fd != -1 )
                {
                    fd = fds[i].fd;
                    /* 如果是监听套接字listenfd,则建立连接 */
                    if( fd == socketfd )
                    {
                        memset(&srvaddr,0,sizeof(srvaddr));
                        connfd = accept(socketfd,(struct sockaddr *)&srvaddr,&addrlen);
                        if( connfd == -1 )
                        {
                            perror("accept");
                            return -1;
                        }
                        printf("connent success connfd = %d\n",connfd);
                        for( j=0;j<1024;j++)
                        {
                            if( fds[j].fd != -1 )
                            {
                                continue;
                            }
                            fds[j].fd = connfd;
                            fds[j].events = POLLIN;

                            if( nfds <= connfd )
                            {
                                nfds = connfd + 1;
                            }
                        }
                    }
                    else
                    {
                        ret = poll_func(fd);
                        if( ret == -1 )
                        {
                            fds[i].fd = -1;
                        }
                    }
                }
            }
        }
    }

    close(socketfd);
    return 0;
}

(三) epoll_ctl

 #include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值