Linux C++服务器项目——网络编程1 (socket通信,服务端,客户端)

牛客 C++高并发服务器开发
参考笔记

1.MAC地址

网卡是一块被设计用来允许计算机在计算机网络上进行通讯的计算机硬件,又称为网络适配器或网络接口卡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地址就如同身份证上的身份证号码,具有唯一性。

如: 00-16-EA-AE-3C-40就是一个MAC地址,前3个字节,代表网络硬件制造商的编号,而后3个字节,代表该制造商所制造的某个网络产品(如网卡)的系列号

2 IP地址

2.1 简介

IP协议是为计算机网络相互连接进行通信而设计的协议。在因特网中,它是能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在因特网上进行通信时应当遵守的规则。任何厂家生产的计算机系统,只要遵守IP协议就可以与因特网互连互通。各个厂家生产的网络系统和设备,如以太网、分组交换网等,它们相互之间不能互通,不能互通的主要原因是因为它们所传送数据的基本单元(技术上称之为“帧")的格式不同。IP协议实际上是一套由软件程序组成的协议软件,它把各种不同“帧"统一转换成"IP数据报"格式,这种转换是因特网的一个最重要的特点,使所有各种计算机都能在因特网上实现互通,即具有"开放性"的特点。正是因为有了IP协议,因特网才得以迅速发展成为世界上最大的、开放的计算机通信网络。因此,IP协议也可以叫做"因特网协议""。

**IР地址(Internet Protocol Address)是指互联网协议地址,**又译为网际协议地址。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

IP地址是一个32位的二进制数,通常被分割为4个 “8位二进制数”(也就是4个字节)。IP地址通常用**"点分十进制"表示**成 (a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)。

2.2 IP地址编址方式

最初设计互联网络时,为了便于寻址以及层次化构造网络,每个IP地址包括两个标识码(ID),即网络ID和主机ID。同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。Internet委员会定义了5种IP地址类型以适合不同容量的网络,即A类~E类。其中A、B、C3类(如下表格)由InternetNIC在全球范围内统一分配,D、E类为特殊地址。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
群就是广播原理,发一条消息,所有人都能收到;
在这里插入图片描述

2.3 子网掩码

子网掩码((subnet mask)又叫网络掩码、地址掩码、子网络遮罩,它是一种用来指明一个IP地址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合IP地址一起使用。子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分。

子网掩码是一个32位地址,用于屏蔽IP地址的一部分以区别网络标识和主机标识,并说明该IP地址是在局域网上,还是在广域网上。

子网掩码是在IPv4地址资源紧缺的背景下为了解决IР地址分配而产生的虚拟IP技术,通过子网掩码将A、B、C三类地址划分为若干子网,从而显著提高了IP地址的分配效率,有效解决了IP地址资源紧张的局面。另一方面,在企业内网中为了更好地管理网络,网管人员也利用子网掩码的作用,人为地将一个较大的企业内部网络划分为更多个小规模的子网,再利用三层交换机的路由功能实现子网互联,从而有效解决了网络广播风暴和网络病毒等诸多网络管理方面的问题。

在大多数的网络教科书中,一般都将子网掩码的作用描述为通过逻辑运算,将IP地址划分为网络标识(Net.lD)和主机标识(Host.ID),只有网络标识相同的两台主机在无路由的情况下才能相互通信。

根据RFC950定义,子网掩码是一个32位的2进制数,其对应网络地址的所有位都置为1,对应于主机地址的所有位置都为0。子网掩码告知路由器,地址的哪一部分是网络地址,哪一部分是主机地址,使路由器正确判断任意IP地址是否是本网段的,从而正确地进行路由。网络上,数据从一个地方传到另外一个地方,是依靠IP寻址。从逻辑上来讲,是两步的。第一步,从IP中找到所属的网络,好比是去找这个人是哪个小区的;第二步,再从IP中找到主机在这个网络中的位置,好比是在小区里面找到这个人。

子网掩码的设定必须遵循一定的规则。与二进制IP地址相同,子网掩码由1和0组成,且1和0分别连续。子网掩码的长度也是32位,左边是网络位,用二进制数字“1"表示,1的数目等于网络位的长度;右边是主机位,用二进制数字“0”表示,0的数目等于主机位的长度。这样做的目的是为了让掩码与IP地址做按位与运算时用О遮住原主机数,而不改变原网络段数字,而且很容易通过0的位数确定子网的主机数(2的主机位数次方-2,因为主机号全为1时表示该网络广播地址,全为0时表示该网络的网络号,这是两个特殊地址)。通过子网掩码,才能表明一台主机所在的子网与其他子网的关系伸网终正常工作。

3 端口

3.1 简介

“端口”是英文port 的意译,可以认为是设备与外界通讯交流的出口。端口可分为虚拟端口和物理端口,其中虚拟端口指计算机内部或交换机路由器内的端口,不可见,是特指TCP/IP协议中的端口,是逻辑意义上的端口。例如计算机中的80端口、21端口、23端口等。物理端口又称为接口,是可见端口,计算机背板的R45网口,交换机路由器集线器等RJ45端口。电话使用R11插口也属于物理端口的范畴。

如果把IP地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口可以有65536(即:2^ 16)个之多!端口是通过端口号来标记的,端口号只有整数,范围是从0到 65535(2^16-1)。

3.2 端口类型

1.周知端口(Well Known Ports)|
周知端口是众所周知的端口号,也叫知名端口、公认端口或者常用端口,范围从0到1023,它们紧密绑定于一些特定的服务。例如80端口分配给WWW服务,21端口分配给FTP服务,23端口分配给Telnet服务等等。我们在IE的地址栏里输入一个网址的时候是不必指定端口号的,因为在默认情况下WwW服务的端口是80"。网络服务是可以使用其他端口号的,如果不是默认的端口号则应该在地址栏上指定端口号,方法是在地址后面加上冒号“∵”(半角),再加上端口号。比如使用8080”作为WwW服务的端口,则需要在地址栏里输入"网址:8080"。但是有些系统协议使用固定的端口号,它是不能被改变的,比如139端口专门用于NetBIOS与TCP/IP之间的通信,不能手动改变。

2.注册端口(Registered Ports)
端口号从1024到49151,它们松散地绑定于一些服务,分配给用户进程或应用程序,这些进程主要是用户选择安装的一些应用程序,而不是已经分配好了公认端口的常用程序。这些端口在没有被服务器资源占用的时候,可以用用户端动态选用为源端口。

3.动态端口/私有端口(Dynamic Ports / Private Ports)
动态端口的范围是从49152到65535。之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。

4 网络模型

4.1 OSI七层参考模型

七层模型,亦称OSI (Open System Interconnection)参考模型,即开放式系统互联。参考模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系,一般称为OSI参考模型或七层模型。它是一个七层的、抽象的模型体,不仅包括一系列抽象的术语或概念,也包括具体的协议。

在这里插入图片描述
1.物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特流

⒉数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能。定义了如何让格式化数据以帧为单位进行传输,以及如何让控制对物理介质的访问。将比特组合成字节进而组合成帧,用MAC地址访问介质。

3.网络层:进行逻辑地址寻址,在位于不同地理位置的网络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的用户数大大增加,而网络层正是管理这种连接的层。

4.传输层:定义了一些传输数据的协议和端口号(www端口80等),如:TCP
(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。

5.会话层:通过传输层((端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求

6.表示层:数据的表示、安全、压缩。主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。

7.应用层:网络服务与最终用户的一个接口。这一层为用户的应用程序(例如电子邮件、文件传输和终端仿真)提供网络服务。

4.2 TCP/IP 四层模型(非常重要)

简介

现在Internet(因特网)使用的主流协议族是TCP/IP协议族,它是一个分层、多协议的通信体系。TCP/IP协议族是一个四层协议系统,自底而上分别是数据链路层、网络层、传输层和应用层。每一层完成不同的功能,且通过若干协议来实现,上层协议使用下层协议提供的服务。
在这里插入图片描述
四层介绍
1.应用层:应用层是TCP/IP协议的第一层,是直接为应用进程提供服务的。

(1)对不同种类的应用程序它们会根据自己的需要来使用应用层的不同协议
邮件传输应用使用了SMTP协议、万维网应用使用了HTTP协议、远程登录服务应用使用了有TELNET协议;

(2)应用层还能加密、解密、格式化数据。

(3)应用层可以建立或解除与其他节点的联系,这样可以充分节省网络资源;

⒉.传输层:作为TCP/IP协议的第二层,运输层在整个TCP/IP协议中起到了中流砥柱的作用。且在运输层中,TCP和UDP也同样起到了中流砥柱的作用。

3.网络层:网络层在TCP/IP协议中的位于第三层。在TCP/IP协议中网络层可以进行网络连接的建立和终止以及IP地址的寻找等功能。

4.网络接口层:在TCP/IP协议中,网络接口层位于第四层。由于网络接口层兼并了物理层和数据链路层所以,网络接口层既是传输数据的物理媒介,也可以为网络层提供一条准确无误的线路。
在这里插入图片描述

在这里插入图片描述

5 协议

5.1 常见协议

应用层常见的协议有:FTP协议(File Transfer Protocol文件传输协议)、HTTP协议(Hyper Text TransferProtocol超文本传输协议)、NFS (Network File System网络文件系统)。

传输层常见协议有:TCP协议(Transmission Control Protocol传输控制协议)、UDP协议(User DatagramProtocol用户数据报协议)。

网络层常见协议有:IP协议(Internet Protocol因特网互联协议)、ICMP协议(Internet Control MessageProtocol因特网控制报文协议)、IGMP协议(Internet Group Management Protocol因特网组管理协议)。

网络接口层常见协议有∶ARP协议(Address Resolution Protocol地址解析协议)、RARP协议(ReverseAddress Resolution Protocol反向地址解析协议)。

在这里插入图片描述

1.源端口号:发送方端口号;
2.目的端口号:接收方端口号;
3.长度:UDP用户数据报的长度,最小值是8(仅有首部);
4.校验和:检测UDP用户数据报在传输中是否有错,有错就丢弃;

5.2 IP协议

在这里插入图片描述
1.版本:IP协议的版本。通信双方使用过的IP协议的版本必须一致,目前最厂泛使用的IP协议版本亏乃4(即IPv4)

2.首部长度:单位是32位(4字节)3.服务类型:一般不适用,取值为0

4.总长度:指首部加上数据的总长度,单位为字节

5.标识(identification):IP软件在存储器中维持一个计数器,每产生一个数据报,计数器就加1,并将此值赋给标识字段

8.生存时间:TTL,表明是数据报在网络中的寿命,即为“跳数限制"”,由发出数据报的源点设置这个字段。路由器在转发数据之前就把 TTL值减一,当TTL值减为零时,就丢弃这个数据报。

9.协议:指出此数据报携带的数据时使用何种协议,以便使目的主机的IP层知道应将数据部分上交给哪个处理过程,常用的ICMP(1),IGMP(2),TCP(6),UDP(17),IPv6 (41)

10.首部校验和:只校验数据报的首部,不包括数据部分。

11.源地址:发送方IP地址

12.目的地址:接收方IP地址

5.3 以太网协议 ARP协议

在这里插入图片描述
1.硬件类型:1表示MAC地址

2.协议类型:Ox800表示IP地址

3.硬件地址长度:6

4.协议地址长度:4

5.操作:1表示ARP请求,2表示ARP应答,3表示RARP请求,4表示RARP应答

5.4 封装

在这里插入图片描述
在这里插入图片描述

5.5 分用

在这里插入图片描述

在这里插入图片描述

6 网络通信过程

在这里插入图片描述
1.源端口号:发送方端口号

2.目的端口号:接收方端口号

3.序列号:本报文段的数据的第一个字节的序号

4.确认序号:期望收到对方下一个报文段的第一个数据字节的序号

5.首部长度(数据偏移)︰TCP报文段的数据起始处距离TCP报文段的起始处有多远,即首部长度。单位: 32位,即以4字节为计算单位

6.保留:占6位,保留为今后使用,目前应置为0

7.紧急URG:此位置1,表阴紧急指针字段有效,它告诉系统此报文段中有紧急数据,应尽快传送

8.确认ACK:仅当 ACK=1时确认号字段才有效,TCP规定,在连接建立后所有传达的报文段都必须把.ACK置19.推送 PSH:当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,TCP就可以使用推送((push)操作,这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去,接收方收到PSH= 1的报文段,就尽快地(即“推送"向前)交付给接收应用进程,而不再等到整个缓存都境满后再向上交付

10.复位RST:用于复位相应的TCP 连接

11.同步SYN:仅在三次握手建立TCP连接时有效。当SYN=1而ACK =0时,表明这是一个连接请求报文段,
对方若同意建立连接,则应在相应的报文段中使用SYN=1和ACK =1。因此,
SYN 置1就表示这是一个连接请求或连接接受报文

12.终止FIN:用来释放一个连接。当FIN=1时,表明此报文段的发送方的数据已经发送完毕,并要求释放运输;

在这里插入图片描述

7 socket介绍

所谓socket(套接字),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。

socket可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是
一个逻辑上的概念。它是网络环境中进程间通信的API,也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的socket中,该socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的socket中,使对方能够接收到这段信息。socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。

socket本身有“插座"的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。
在这里插入图片描述

//套接字通信分两部分:
-服务器端:被动接受连接,一般不会主动发起连接
-客户端:主动向服务器发起连接

socket是一套通信的接口,Linux和 windows都有,但是有一些细微的差别。

8 字节序

8.1 简介

现代CPU的累加器一次都能装载(至少)4字节(这里考虑32位机),即一个整数。那么这4字节在内存中排列的顺序将影响它被累加器装载成的整数的值,这就是字节序问题。在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编码/译码从而导致通信失败。

字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。

字节序分为大端字节序(Big-Endian)和小端字节序(Little-Endian)。

大端字节序是指一个整数的最高位字节(23~31 bit)存储在内存的低地址处,低位字节(0 ~7 bit)存储在内存的高地址处;

小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。

8.2 字节序举例

在这里插入图片描述
//判断是大端还是小端模式

//方法一:使用union(共用体,所有成员共用一段内存)
union{
    short value; //2字节
    char bytes[sizeof(short)]; //char[2]
}test;

void test1(){
    test.value = 0x0102;
    if(test.bytes[0] == 1 && test.bytes[1] == 2)
        printf("大端模式\n");
    else if(test.bytes[0] == 2 && test.bytes[1] == 1)
        printf("小端模式\n");
    else
        printf("未知\n");
}
//方法2:类型降低;;;short big = 0xff00;char little = big;大端little = 0xff,小端little = 00。
void test2(){
    short big = 0x0102;
    char little = big;

    if(little == 1)
        printf("大端模式\n");
    else if(little == 2)
        printf("小端模式\n");
    else
        printf("未知\n");
}
int main(){
        test2();
        return 0;
}

在这里插入图片描述

8.3 字节序转换函数

当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释之。解决问题的万法是∶反送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用大端排序方式。

BSD Socket提供了封装好的转换接口,方便程序员使用。包括从主机字节序到网络字节序的转换函数: htons;htonl;从网络字节序到主机字节序的转换函数:ntohs、ntohl。

h	-host主机,主机字节序
to	-转换成什么
n	-network网络字节序
s	-short unsigned short
l 	-long unsigned int
#include <arpa/inet.h>//转换端口
uint16_t htons(uint16_t hostshort);	//主机字节序–网络字节序
uint16_t ntohs(uint16_t netshort);	//主机字节序–网络字节序

//转IP
uint32_t htonl(uint32_t hostlong);	//主机字节序–网络字节序
uint32_t ntohl(uint32_t netlong);	//主机字节序–网络字节序
/*
    网络通信时,需要将主机字节序转换成网络字节序(大端),
    另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。

    主机字节序:可能大端,也可能小端;
    网络字节序:都是大端

    //转换端口 (端口2字节)
    uint16_t htons(uint16_t hostshort);	//主机字节序–网络字节序  htons -> host to network short
    uint16_t ntohs(uint16_t netshort);	//网络字节序-主机字节序  ntohs -> network to host short

    //转IP (IP4字节)
    uint32_t htonl(uint32_t hostlong);	//主机字节序–网络字节序  htonl -> host to network long
    uint32_t ntohl(uint32_t netlong);	//网络字节序-主机字节序  ntohl -> network to host long
*/
#include<stdio.h>
#include<arpa/inet.h>

int main(){
    //htons 转换端口
    unsigned short a = 0x0102;
    printf("a : %#x\n",a);

    unsigned short b = htons(a);
    printf("b : %#x\n",b);

    printf("==============================================\n");

    //htonl 转换IP
    char buf[4] = {192,168,1,102};
    int num = *(int *)buf;
    int sum = htonl(num);
    unsigned char *p = (char *)&num;

    printf("%d %d %d %d\n",*p, *(p+1), *(p+2), *(p+3) );

    printf("==============================================\n");

     //ntohs 转换端口
    unsigned short a1 = 0x0102;
    printf("a1 : %#x\n",a1);

    unsigned short b1 = ntohs(a1);
    printf("b1 : %#x\n",b1);

    printf("==============================================\n");

    //ntohl 转换IP
    char buf1[4] = {192,168,1,102};
    int num1 = *(int *)buf1;
    int sum1 = ntohl(num1);
    unsigned char *p1 = (char *)&num1;

    printf("%d %d %d %d\n",*p1, *(p1+1), *(p1+2), *(p1+3) );

    printf("==============================================\n");

    return 0;
}

在这里插入图片描述

/*
    #include <arpa/inet.h>
    // p:点分十进制的IP字符串,n:表示network,网络字节序的整数
    int inet_pton(int af, const char *src, void *dst);
        af:地址族:AF__INET AF_INET6
        src:需要转换的点分十进制的IP字符串
        dst:转换后的结果保存在这个里面

    //将网络字节序的整数,转换成点分十进制的IP地址字符串
    const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
        af:地址族:AF_INET AF_INET6
        src:要转换的ip的整数的地址
        dst:转换成IP地址字符串保存的地方
        size:第三个参数的大小(数组的大小)
        返回值:返回转换后的数据的地址(字符串),和 dst是一样的
*/
#include<stdio.h>
#include<arpa/inet.h>

int main(){
    //创建一个IP字符串,点分十进制的IP地址字符串
    char buf[] = "192.168.1.102";
    unsigned int num = 0;

    //将点分十进制的IP字符串转换成网络字节序的整数
    inet_pton(AF_INET, buf, &num);
    unsigned char *p = (unsigned char *)&num;
    printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));

    //将网络字节序的IP整数转换成点分十进制的IP字符串
    char ip[16] = ""; // "192.168.1.102"加'\0'结束符,共16个
    const char *str = inet_ntop(AF_INET, &num, ip, 16); 
    printf("str : %s\n", str);
    printf("ip : %s\n", str);
    printf("%d\n", ip == str);

    return 0;
}

在这里插入图片描述

9.socket地址

//socket地址:其实是一个结构体,封装端口号和IP等信息。后面的socket相关的api中需要使用到这个socket地址。
//客户端->服务器(IP,Port)

9.1 通用socket地址

socket网络编程接口中表示socket地址的是结构体sockaddr,其定义如下:

#include <bits/socket.h>
	struct sockaddr {
	sa_family_t sa_family;
	char sa_data[14];
};
typedef unsigned short int sa_family_t;

sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见的协议族(protocol family,也称domain)和对应的地址族入下所示:
在这里插入图片描述
宏PF_*和AF_*都定义在 bits/socket.h头文件中,且后者与前者有完全相同的值
所以二者通常混用。

sa_data成员用于存放socket地址值。但是,不同的协议族的地址值具有不同的含义和长度,如下所示:
在这里插入图片描述
由上表可知,14字节的sa_data根本无法容纳多数协议族的地址值。因此,Linux定义了下面这个新的通用的socket地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的。

#include <bits/socket.h>
struct sockaddr_storage{
	sa_fami1y_t sa_family;
	unsigned long int _ss_align;
	char __ss_padding[ 128 - sizeof(_ss_align) ];
};
typedef unsigned short int sa_fami1y_t;

9.2 专用socket地址

很多网络编程函数诞生早于IPv4协议,那时候都使用的是struct sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是sockaddr_in6,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

在这里插入图片描述

UNIX本地域协议族使用如下专用的socket 地址结构体:

#include <sys/un.h>
struct sockaddr__un{
	sa_family_t sin_family;
	char sun_path[108];
};

TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用的socket地址结构体,它们分别用于IPv4和IPv6:

#include <netinet/in.h>struct sockaddr_in
{
	sa_family_t sin_family; /*_sOCKADDR_COMMON(sin_) */
	in_port_t sin_port; /* Port number.*/
	struct in_addr sin_addr; /*Internet address.*/
	/*Pad to size of `struct sockaddr ' . */
	unsigned char sin_zero[sizeof (struct sockaddr) -___SOCKADDR_COMMON_SIZE-sizeof (in_port_t) - sizeof (struct in_addr)];
};

struct in_addr{
	in_addr_t s_addr;
};

struct sockaddr_in6{
	sa_family_t sin6_family;
	in_port_t sin6_port;	/*Transport layer port #*/
	uint32_t sin6_flowinfo;	/*IPv6 flow information */
	struct in6_addr sin6_addr;	/* IPv6 address */
	uint32_t sin6_scope_id;		/* IPv6 scope-id */
};

typedef unsigned short uint16_t;
typedef unsigned intuint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
#define _sOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

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

10.IP地址转换(字符串ip-整数,主机、网络字节序的转换)

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

#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp,struct in_addr *inp);
char *inet_ntoa(struct in_addr in);

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

#include <arpa/inet.h>
// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af,const char *src,void *dst);
	af:地址族:AF_INET AF_INET6
	src:需要转换的点分十进制的IP字符串
	dst:转换后的结果保存在这个里面
	
//将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af,const void *src,char *dst,socklen_t size);
	af:地址族:AF_INET AF_INET6
	src:要转换的ip的整数的地址
	dst:转换成IP地址字符串保存的地方size:第三个参数的大小(数组的大小)
	返回值:返回转换后的数据的地址(字符串),和 dst是一样的

11.TCP通信流程

//TCP 和 UDP -→>传输层的协议
UDP:用户数据报协议,面向无连接,可以单播,多播,广播,面向数据报,不可靠
TCP:传输控制协议,面向连接的,可靠的,基于字节流,仅支持单播传输

在这里插入图片描述
在这里插入图片描述
客户端,主动连接方;服务端,被动链接方。

服务器端:
bind(),绑定端口,以供客户端链接;
listen(),监听客户端有没有链接进来;
accept(),当有客户端链接进来,通过accept()接收客户端链接,返回一个文件描述符,通信文件描述符;accept是阻塞的,如果没有客户端链接进来,就会一直在这里等待。
recv(),send(),接收链接之后,通过recv,send发收数据;
close(),传输完毕,关闭连接;

客户端:
connect(),不需要绑定端口,直接链接服务器;
recv(),send(),通信;
close();

//TCP迪信的流程
//服务器端(被动接受连接的角色)
1.创建一个用于监听的套接字
	-监听:监听有客户端的连接
	-套接字:这个套接字其实就是一个文件描述符
2.将这个监听文件描述符和木地的IP和端口绑定(IP和端口就是服务器的地址信息)
	-客户端连接服务器的时候使用的就是这个IP和端口
3.设置监听,监听的fd开始工作
4.阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字(fd)
5.通信
	-接收数据
	-发送数据
6.通信结束,断开连接
//客户端
1.创建一个用于通信的套接字(fd)
2.连接服务器,需要指定连接的服务器的IP和端口;
3.连接成功了,客户端可以直接和服务器通信
	-接收数据
	-发送数据
4.通信结束,断开连接

12.套接字函数

#include csys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> //包含了这个头文件,上面两个就可以省略
int socket(int domain,int type,int protoco1);
	-功能:创建一个套接字
	-参数:
		- domain:协议族
			AF_INET : ipv4
			AF_INET6 : ipv6
			AF_UNIX,AF_LOCAL :本地套接字通信(进程间通信)
		- type:通信过程中使用的协议类型
			sock_STREAM:流式协议
			sock_DGRAM :报式协议
		- protocol :具体的一个协议。一般写0
			- sock_STREAM︰流式协议默认使用 TCP
			- sock_DGRAM:报式协议默认使用 UDP
		-返回值:
			-成功:返回文件描述符,操作的就是内核缓冲区。
			-失败:-1
		
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen); // socket命名
	-功能;绑定,将fd和本地的IP +端口进行绑定
	-参数:
		- sockfd :通过socket函数得到的文件描述符
		- addr :需要绑定的socket地址,这个地址封装了ip和端口号的信息
		- addrlen :第二个参数结构体占的内存大小
		
int listen(int sockfd,int backlog); // /proc/sys/net/core/somaxconn
	-功能:监听这个socket上的连接
	-参数:
		- sockfd :通过socket(函数得到的文件描述符
		- backlog :未连接的和已经连接的和的最大值,5
		
int accept(int sockfd,struct sockaddr *addr,socklen_t "addrlen) ;
	-功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
	-参数:
		- sockfd :用于监听的文件描述符
		- addr :传出参数,记录了连接成功后客户端的地址:信息(ip,port)
		- addrlen :指定第二个参数的对应的内存大小
	-返回值:成功0,失败-1
	
ssize_t write(int fd,const void*buf,size_t count);	//写数据
ssize_t read(int fd, void *buf,size_t count);	//读数据

13 socket通信,代码

// TCP 通信服务器端

// TCP 通信服务器端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>

int main(){

    // 1.创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);//IPv4, 流式协议,TCP

    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    // 2. 绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    //inet_pton(AF_INET, "192.168.37.128", saddr.sin_addr.s_addr);

    saddr.sin_addr.s_addr = INADDR_ANY; //0.0.0.0
    saddr.sin_port = htons(9999);//0~1023 是周知端口,不能使用,要从1024开始,防止被占用,写一个不常用的
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1){
        perror("bind");
        exit(-1);
    }  

    // 3. 监听 ,监听有没有客户端到达
    ret = listen(lfd, 8);//lfd 监听文件描述符,值随便写一个8,或者更大一些
    if(ret == -1){
        perror("listen");
        exit(0);
    }


    // 4. 接收客户端链接
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len); //accept是阻塞的,如果没有客户端链接进来,就会一直在这里等待。

    if(cfd == -1){
        perror("accept");
        exit(-1);
    }

    //输出客户端信息
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    //5.通信
    char recvBuf[1024] = {0};
    while(1){
        //获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == -1){   
            perror("accept");
            exit(-1);      
        }
        else if(num > 0)
            printf("recv client data : %s\n",recvBuf);
        else if(num == 0){ //表示读到末尾了
            //表示客户端,断开链接
            printf("client closed...");
            break;
        }   

        char *data = "hello, i am server";
        //给客户端发送数据
        write(cfd, data, strlen(data));
    }
    //关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}

// TCP 通信客户端

// TCP 通信客户端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>

int main(){

    // 1.创建socket(用于监听的套接字)
    int fd = socket(AF_INET, SOCK_STREAM, 0);//IPv4, 流式协议,TCP

    if(fd == -1){
        perror("socket");
        exit(-1);
    }

    // 2. 连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.37.128", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);//端口
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
   
     if(ret == -1){
        perror("connect");
        exit(-1);
     }
   
    //3.通信
    char recvBuf[1024] = {0};
    while(1){

         char *data = "hello, i am client";
        //给服务器发送数据
        write(fd, data, strlen(data));

        //获取服务器端的数据
        int num = read(fd, recvBuf, sizeof(recvBuf));
        if(num == -1){   
            perror("accept");
            exit(-1);      
        }
        else if(num > 0)
            printf("recv client data : %s\n",recvBuf);
        else if(num == 0){ //表示读到末尾了
            //表示服务器端,断开链接
            printf("client closed...");
            break;
        }   
       sleep(1);//一秒发送一次,不然发送太快,看不清
    }
    //关闭文件描述符
    close(fd);

    return 0;
}

client.c 和server.c 分别编译后;
先启动服务端server,不然客户端连接不上;
服务端启动后,如果没有客户端链接进来,就会阻塞,一直在这里等待。

在这里插入图片描述
复制一个会话,启动客户端。
服务器接收到客户端请求,先打印客户机,信息;
然后,服务器和客户端便可以通信了;

在这里插入图片描述
在这里插入图片描述

  • 4
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
很高兴能够回答你的问题。以下是一个简单的即时通信系统的C++网络编程示例,基于Linux平台使用socket库进行开发。 客户端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接服务器 int connect_result = connect(client_socket, (sockaddr *) &server_address, sizeof(server_address)); if (connect_result == -1) { cout << "连接服务器失败!" << endl; return -1; } // 发送消息 char message[1024] = {0}; while (true) { cin.getline(message, sizeof(message)); if (strlen(message) == 0) { continue; } send(client_socket, message, strlen(message), 0); memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); return 0; } ``` 服务器端代码: ```c++ #include <iostream> #include <cstring> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> using namespace std; int main() { // 创建socket int server_socket = socket(AF_INET, SOCK_STREAM, 0); // 配置服务器地址 sockaddr_in server_address{}; server_address.sin_family = AF_INET; server_address.sin_port = htons(8080); server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); // 绑定socket和地址 bind(server_socket, (sockaddr *) &server_address, sizeof(server_address)); // 监听客户端连接 listen(server_socket, 10); // 接受客户端连接 sockaddr_in client_address{}; socklen_t client_address_len = sizeof(client_address); int client_socket = accept(server_socket, (sockaddr *) &client_address, &client_address_len); // 接收消息 char message[1024] = {0}; while (true) { int recv_result = recv(client_socket, message, sizeof(message), 0); if (recv_result <= 0) { break; } cout << "收到消息:" << message << endl; memset(message, 0, sizeof(message)); } // 关闭socket close(client_socket); close(server_socket); return 0; } ``` 在上面的代码中,客户端服务器端都使用了socket库中的函数来完成网络通信客户端使用`socket`函数创建socket,使用`connect`函数连接服务器,并使用`send`函数发送消息。服务器端使用`socket`函数创建socket,使用`bind`函数将socket服务器地址绑定,使用`listen`函数监听客户端连接,并使用`accept`函数接受客户端连接,最后使用`recv`函数接收客户端发送的消息。 当然,这只是一个简单的示例。在实际开发中,你需要考虑更多的细节,例如如何处理多个客户端连接、如何处理异常情况等等。 希望这个示例能够帮助你了解LinuxC++ socket网络编程的基本流程和方法。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

R-G-B

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值