1 TCP/IP协议和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等 |
物理层 | 例如铜缆、网线、光缆、无线电等 |
应用层 | 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区别
共同点:
- TCP和UDP都是传输层协议;
- 多路复用:TCP和UDP允许多个应用程序在同一网络连接上并发通信,通过端口号区分不同的通信流。
- 面向字节流:它们都是面向字节流的协议,可以传输任意类型的数据。
- 拥塞控制和流量控制:虽然TCP和UDP在实现上有所不同,但它们都可以支持拥塞控制和流量控制机制,以适应网络条件和接收方的处理能力
- 错误检测:两者都提供了一定程度的错误检测功能,TCP使用更复杂的校验和,而UDP使用较为简单的校验和。
- 支持广播和多播:TCP和UDP都可以支持广播和多播传输,允许同时向多个目的地发送数据。
- 动态端口分配:它们允许使用动态端口号,这在客户端应用程序生成大量短连接时非常有用。
- 不关心网络层的细节:TCP和UDP抽象了网络层的细节,使得应用程序可以不必关心底层网络的具体实现。
不同点:
简单来说:TCP - 有连接、可靠;UDP - 无连接、不保证可靠、效率高;
- 连接方式:
- TCP是面向连接的协议,需建立连接;
- UDP是无连接的,无需建立连接即可传输数据。
- 数据传输/可靠性:
- TCP提供可靠的数据传输,确保数据包正确、完整、按顺序到达;
- UDP则不保证数据包的可靠传输,可能会导致丢包、乱序或重复。数据传输;
- 速度:由于TCP需要建立连接和保证数据的可靠传输,其速度相对较慢;UDP由于简单直接,没有建立连接的步骤,因此速度更快。
-
报文结构:
-
有复杂的头部结构,包含序列号、确认号、窗口大小等控制信息。
-
头部结构简单,主要包含源端口、目的端口和长度校验等。
-
-
流量控制和拥塞控制:
-
TCP:内置流量控制和拥塞控制机制,防止网络过载。
-
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表示“所有”,即该网络上所有主机】。
- A类地址:1.0.0.1—126.155.255.254
- B类地址:128.0.0.1—191.255.255.254
- C类地址:192.0.0.1—223.255.255.254
- D类地址:224.0.0.1—239.255.255.254
- 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)路由器在转发数据报时,可以根据该字段的值来为数据报选择最合理的路由路径。
- (1)该字段用于描述当前IP数据报急需的服务类型,如:
- 总长度: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。网络驱动不会计算校验和,只是简单将校验和字段留空或填入无效信息,交给硬件计算。
发送数据时,首部校验和计算:二进制反码求和。
- 把IP数据包的校验和字段置为全0。
- 将首部中的每 2 个字节当作一个数,依次求和。
- 把结果取反码。
- 把得到的结果存入校验和字段中。
接收数据时,首部校验和验证过程:
- 首部中的每 2 个字节当作一个数,依次进行求和,包括校验和字段。
- 检查计算出的校验和的结果是否全为1(反码应为16个0)。
- 如果等于零,说明被整除,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。
为什么计算出的校验和结果全为1?
因为如果校验依时次求和,不包含校验和字段的话,得出的值就是校验和字段的反码。
校验和的反码和校验和求和,当然是全1啦。IP/ICMP/IGMP/TCP/UDP等协议的校验和算法都是相同的。
- 选项字段:0到40字节。对于IP数据报首部来说,其大小必须为4字节的整数倍。如果选项字段长度不为4的倍数,则需要用0进行填充。在 LwIP 中只识别选项字段,不会处理选项字段的内容。该字段在IPv6报文中已经被移除了。
- 数据区字段:IP 数据报的最后的一个字段,装载着当前IP数据报的数据,是上层协议的数据报。
2.2.6 wireshark包分析
1.2 TCP/IP协议栈的数据封装与解封
封装:数据添加各层协议的首部;解封装:在各层间除去自层的首部
2 服务器与客户端
2.1 服务器与客户端流程
2.2 服务器与客户端通信数据帧
为什么要自己定义协议包呢?一个原因是因为真正的业务逻辑往往都是复杂的,不会是很单纯的字符串或数字。通讯时网络传输是以字节为单位的。这一串串数据流在交互,如何能在数据流每次交互中正确识别出。真正有用的东西,就要双方定义一套方法。发送方把要传输的东东放在一起定义好包,转成网络字节序发送出去。接收方收到后,再按包的定义,拆解出有用的数据。
包定义各有各的方法,但通常会包含下面几部份:
- 包类型 :用于业务逻辑区分;
- 容长度 :传输数据的长度;
- 消息内容 :本次实际要传输的数据;
- 校验位 :检查用,防止错包出现;
包通常都是结构体(两种):
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特点:
- 是一个编程接口;
- 是一种特殊的文件描述符;
- 可以使用TCP/IP<流式套接字,面向连接、数据可靠>、可以使用UDP<数据报套接字:无连接、不保证数据可靠>;
- 使用原因:主要解决不同主机之间数据通信,并且保证数据的高效传输,即通过socket套接字文件。
三、IP地址
(一)IP地址特点
- IP地址是Internet中的主机的标识;
- Internet中的主机要与别的机器通信必须具有一个IP地址;
- IP地址分为32位(IPV4)、128位(IPV6);
- 每个数据包都必须携带目的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 protocolsAF_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协议)套接字才能广播;
一、广播地址
- 以192.168.1.0(255.255.255.0网段为例,最大的主机地址192.168.1.255代表该网段的广播地址;
- 发送到该地址的数据包被所有的主机接收;
- 255.255.255.255在所有网段中都代表广播地址;
二、广播接收
- 绑定IP地址(广播IP或0.0.0.0)和端口号;
- 绑定的端口号必须和发送方指定的端口相同;
三、广播代码实现
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()的思路:
- 将所有要处理的文件描述符存储到一张表中;
- 检测表当中有没有已经就绪的文件描述符,如果有,返回就绪的文件描述符;
- 轮询所有就绪的文件描述符;
while(1)
{
if(listenfd)
//说明是新的客户端发起了请求;
if(confd)
//说明是已经连接的客户端发送了数据请求;
if(/*普通文件*/)
//说明普通文件的数据;
if(0)
//标准输入;
}
(一)select
select机制思路及步骤:
- 根据描述符处理事件不同,创建不同的表;例如将所有文件描述符添加到一张读的表中;
- 检测表当中是否有就绪的文件描述符,如果有返回状态,同时返回就绪的文件描述符;
- 轮询
(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 --- 最大文件描述符+1readfds --- 所有要读的文件描述符的集合(读文件件描述表)
writefds --- 所有要写的文件描述符的集合(写文件件描述表)
exceptfds --- 其他要向我们通知的文件描述(错误处理文件描述符表)
timeout --- 超时设置:NULL:一直阻塞,直到有文件描述符就绪或出错
0:仅仅检测文件描述符集的状态,然后立即返回
不为0:在指定时间内,如果没有事件发生,则超时返回
返回值:成功 --- 准备就绪的文件描述符的个数
失败 --- -1
超时返回 ---- 0
设置文件描述符的几个宏:
- void FD_CLR(int fd, fd_set *set); //将fd从集合set当中清除
- int FD_ISSET(int fd, fd_set *set); //判断fd是否在集合set中
- void FD_SET(int fd, fd_set *set); //将fd添加到集合set中
- 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)处理数据;
功能:检测是否有文件描述和事件处于就绪状态,有返回,没有一直阻塞。
#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);