当前位置: 首页 > Linux

Linux网络编程1

时间:2023-04-06 22:35:14 Linux

1.网络编程基础网络编程本身就是一门很大的科学,涉及的东西很多,尤其是各种协议。先看图:如上图所示,网络编程包括五个层次(和六个层次)。从应用层到物理层,可以明显看出越往下层次越接近计算机硬件。我不是专业的网络编程工程师,所以对这五个层次也只是粗浅的了解。这篇文章是博主写的比较详细的网络编程技巧。大多数所谓的网络编程其实都是在传输层和网络层。方面。2.套接字编程首先,套接字(socket)编程应该属于传输层。主要实现端到端的通信,很像很久以前的固网通信。应用程序可以通过它发送或接收数据。它可以像文件一样被读取、写入和关闭。套接字允许应用程序将I/O插入网络并与网络上的其他应用程序通信。其次,要在网络中的两台或多台主机之间进行通信,必须知道对应主机的地址,即它的IP地址,但仅仅知道IP地址是远远不够的。想象一下,如果你在本机A上发送一条消息,另一台主机B也收到了这条消息,但是主机B的哪个进程接受并处理了这条消息呢?就像你用QQ给B发消息,B却不可能通过陌陌收到消息。因此,相互通信的主机之间必须确定一个一一对应的消息处理接口——端口。端口的存在主要是为了确认消息的一一对应。另外,端口号实际上是一个从0开始,范围为65535到65535的整数。0到1023的端口,通常被称为静态端口,已经被操作系统用作其他用途(http,https,ftp等不同协议占用),我们可以使用的端口范围只能从1024开始,即动态端口值[1024,65535]。但是可以看出,如果我们要在网络之间进行通信,socket至少要包含两个IP+port,另一方面,实际上是这样的。还是用有线电话打个比方,socket其实就是你家里的一部电话,其中IP就是你家的地址,port就是你家的电话号码。当你要给别人打电话的时候,当然是对方家里的你也必须有自己的座机和座机的专属号码。或许我们也能猜到,套接字编程是网络编程中必不可少的、极其重要的部分。三、Linux+socket实践1、目的熟悉Linux(这里我使用的是Ubuntu16.04版本,其他版本类似),掌握socket编程的基本原理,了解Linux下socket编程所必需的功能和用法。实验:在本地模拟两台机器,服务器和客户端,服务器监听客户端信息并可以发送广播,客户端可以主动向服务器发送消息,其中消息的输入是从标准输入设备输入,输出到标准输出——Linux终端。在开始之前,你必须明白什么是文件描述符,在UnixLinux系统中,文件描述符是一个非负整数,它的存在更像是一个索引。系统内核通过这个“索引”找到对应的文件、设备、外设、安装的软件等,并通过Descriptors对其进行操作。总而言之,文件描述符对应于系统上的所有文件。这里的文件不是“传统意义上的普通文件”,而是指Linux系统内核所能管理的一切,包括文档、文件、硬件设备、系统软件等等。这也体现了Linux系统的设计思想——把一切都当作一个文件。2.必要的接口1)、socket函数既然socket这么重要,那我们就来看看它是什么吧。在linux终端执行:mansocket,会出现:通过linux手动查询,可以知道函数需要的头文件、函数声明和函数说明。从[DESCRIPTION]字段中,我们可以知道该函数创建了一个用于通信的端点并返回端点的描述符。如果创建成功,返回创建socket的文件描述符,否则返回负数。函数声明intsocket(intdomain,inttype,intprotocol);parameterdomain:表示创建socket所用的通信协议族——地址族,现在一般使用IPv4协议,所以通常选择AF_INET;参数类型:指定需要的通讯类型。包括数据流(SOCK_STREAM)<-->TCP协议、数据报(SOCK-DGRAM)<-->UDP协议和原始类型(S0CK_RAW)<-->新网格协议的开发测试。参数protocol:说明设置套接字使用的协议族中的特定协议。如果您不想指定使用的协议,请将其设置为0并使用默认连接方式。如果要基于TCPIP进行网络开发测试,函数创建方式一般为:intlistenfd=socket(AF_INET,SOCK_STREAM,0);2)由于bind函数有一个“电话”,需要为电话绑定一个唯一的“拥有地址”。同样,Linux命令行执行:manbind,同样的函数声明为:intbind(intsockfd,conststructsockaddr*addr,socklen_taddrlen);从手册中的描述可以看出,当socketsocket创建成功后,调用该函数可以将创建的socket(sockfd)绑定到指定地址(addr)上。地址由这样一个结构指定:structsockaddr{sa_family_tsa_family;//地址族字符sa_data[14];//14字节协议地址}上面的structsockaddr是一个通用的地址。在网络编程中,internetsockaddr使用如下地址,这两个地址可以互换:structsockaddr_in{shortintsin_family;/*地址族,AF_xxx在socket编程中只能是AF_INET*/unsignedshortintsin_port;/*端口号(使用网络字节顺序)*/structin_addrsin_addr;/*存储IP地址4字节*/unsignedcharsin_zero[8];/*一共8个字节,其实没什么用,只是为了和structsockaddr保持一样的长度*/};bind()函数的第三个参数表示地址占用的字节长度,socklen_t本质上是一个unsignedint宏定义。地址可以这样指定:structsockaddr_inserveraddr;memset(&serveraddr,0,sizeof(serveraddr));服务器地址sin_family=AF_INET;服务器地址.sin_port=htons(5188);服务器地址.sin_addr.s_addr=htonl(INADDR_ANY);//服务器地址.sin_addr.s_addr=inet_addr("127.0.0.1");首先声明网络接口地址结构,必须在分配地址Clear之前进行分配。依次设置地址的地址族、IP和端口(这里设置一个),顶部出现另一个新函数htons,终端也显示manhtons。可以看出该函数的主要作用是将主机字节序转换成网络字节序,后续研究这两种字节序。这里可以理解为htons()的主要作用是将十进制的ip地址和端口号转换成网络可以识别的“东东”。至此,基本可以完成座机、登录、号码绑定的安装:bing(listenfd,(structsockaddr*)&serveraddr,sizeof(serveraddr));3)、listen函数对于我们的服务器,需要监听incomingmessagesfromclient可以在linix终端manlisten查看详细信息。函数声明为:intlisten(intsocdfd,intbacklog);其中参数sockfd是指要监听的socket文件描述符,参数backlog是指要监听的socket文件描述符。函数挂起时,可以接受请求的最大队列长度。函数成功返回0,否则返回-1。必须注意的是,调用该函数时,参数socdfd指定的socket会变成被动socket。所谓被动套接字,就是只能用来接收其他用户的连接请求。它类似于改变套接字的状态,使其只能用于接收。4)、accept函数是针对我们服务器的,也就是说,既然只有接收的功能,那么就必须创建一个accept函数:intaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);函数参数不言自明,参数1sockfd表示服务器套接字描述符,参数2指客户端的协议地址,参数3是地址的长度。该函数成功返回监听队列中第一个套接字的描述符。3、服务端实现服务端监听客户端发送的消息并广播消息给客户端的功能。因此需要一个循环来实时监听客户端发送的消息,搭建一个简单的服务端本地如下:#include#include#include#include#include#include#include#include#include#include#include#defineERR_EXIT(m)\do\{\perror(m);\exit(EXIT_FAILURE);\}while(0)intmain(void){intlistenfd;如果((listenfd=socket(PF_INET,SOCK_STREAM,0))<0){ERR_EXIT("socket");}结构sockaddr_in服务器地址;memset(&serveraddr,0,sizeof(serveraddr));服务器地址.sin_family=AF_INET;服务器地址.sin_port=htons(5188);服务器地址.sin_addr.s_addr=htonl(INADDR_ANY);//服务器地址.sin_addr.s_addr=inet_addr("127.0.0.1");if(bind(listenfd,(structsockaddr*)&serveraddr,sizeof(serveraddr))<0)ERR_EXIT("bind");//一旦监听,就是被动套接字(只能接受连接,在调用accep函数之前调用),这里随机给定一个最大队列长度if(listen(listenfd,100)<0)ERR_EXIT(“听”);//声明一个地址,用来存放客户端连接时的协议地址structsockaddr_inpeeraddr;socklen_tpeerlen=sizeof(peeraddr);诠释康恩;//返回一个活动套接字if((conn=accept(listenfd,(structsockaddr*)&peeraddr,&peerlen))<0)ERR_EXIT("accept");charrecvbuff[1024];while(1){memset(recvbuff,0,sizeof(recvbuff));intret=read(conn,recvbuff,sizeof(recvbuff));fputs(recvbuff,标准输出);写(conn,recvbuff,ret);}关闭(lis??tenfd);return0;}它使用了几个非套接字API函数:ssize_tread(intfd,void*buf,size_tcount);ssize_twrite(intfd,constvoid*buf,size_tcount);read()函数:负责从fd指定的文件描述符中读取字节大小为count的数据到buf中。如果成功,返回实际读取的字节大小,否则返回负数,返回0表示文件结束。write():将buf中count个字节写入文件描述符fd。成功返回写入的字节数。4、客户端实现客户端的实现和服务端的实现类似,都需要“安装电话”,但客户端的功能只是“打电话”。不同的是,客户端是主动发起连接请求的,所以在服务器响应之前,它必须知道自己要连接的目标。同样,客户端不需要监听,只需要接收服务器的广播即可。发起连接请求需要函数connect:intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen);在上面的连接函数中,参数sockfd代表本机(客户端)的socket套接字描述符,参数addr代表服务器的地址,参数3代表地址的长度。代码实现:#include#include#include#include#include#include#include#include#include#include#include#defineERR_EXIT(m)\do\{\perror(m);\exit(EXIT_FAILURE);\}while(0)intmain(void){intsock;如果((sock=socket(PF_INET,SOCK_STREAM,0))<0){ERR_EXIT("socket");}结构sockaddr_in服务器地址;memset(&serveraddr,0,sizeof(serveraddr));服务器地址.sin_family=AF_INET;服务器地址.sin_port=htons(5188);//服务器地址.sin_addr.s_addr=htonl(INADDR_ANY);服务器地址.sin_addr.s_addr=inet_addr("127.0.0.1");//发起连接connect(sock,(structsockaddr*)&serveraddr,sizeof(serveraddr));charrecvbuf[1024]={0};charsendbuf[1024]={0};while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL){write(sock,sendbuf,strlen(sendbuf));读取(袜子,recvbuf,sizeof(recvbuf));fputs(recvbuf,标准输出);memset(recvbuf,0,sizeof(recvbuf));}关闭(袜子);return0;}上面的函数是客户端主动向服务端发送一个连接请求,并在字符等标准设备??上,服务端接受并返回。实现模拟两台机器之间的通信。5、结果如下:用gcc编译以上两个文件,先启动server,再启动client。在客户端输入字符,服务器解析并广播回来。至此,目的基本完成。三、总结目前创建服务器的一般流程是:1.创建socket套接字(`socket`函数);2、创建服务器地址,地址包含协议族、IP和端口号(`conststructsockaddr*`);3、绑定socket和服务器地址(bind函数);4.系统监听服务器,一旦监听,socket就变成被动socket,只能用来接收数据(`listen`函数);5.作为服务器,它应该能够接收客户端信息(`accept`函数),它返回一个活动的套接字;基于以上步骤,一个简单的服务端和客户端的搭建就简单多了:1.创建一个用于连接的socket;2.将socket连接到服务器地址;3.发送消息。慢慢填。