linux网络编程模型总结Word格式.docx
- 文档编号:16420521
- 上传时间:2022-11-23
- 格式:DOCX
- 页数:29
- 大小:153.44KB
linux网络编程模型总结Word格式.docx
《linux网络编程模型总结Word格式.docx》由会员分享,可在线阅读,更多相关《linux网络编程模型总结Word格式.docx(29页珍藏版)》请在冰豆网上搜索。
Socket接口上TCP/IP网络应用程序接口(API),它提供了许多函数和例程,程序员可以使用它们来开发TCP/IP网络应用程序。
使用Socket接口进行网络通信的过程简要步骤如下:
(1)建立一个Socket
(2)按要求配置socket,将socket连接到远程主机或给socket指定以各本地协议端口。
(3)按要求通过socket发送和接受数据。
(4)关闭此socket。
这是通过Socket实现点对点通信需要掌握的4个编程要点。
抽象出来,Socket实质上提供了进程通信的端点。
进程通信之前,双方首先必须各自创建一个端点,否则是没有办法建立联系并相互通信的。
正如打电话之前,双方必须各自拥有一台电话机一样。
每一个Socket都用一个半相关描述:
{协议,本地地址,本地端口}
一个完整的Socket则用一个相关描述
{协议,本地地址,本地端口,远程地址,远程端口}
每一个Socket有一个本地的唯一Socket号,由操作系统分配。
Socket接口设计者最先是将接口放在Unix操作系统里面的。
如果了解Unix系统的输入和输出的话,就很容易了解Socket了。
网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。
1.1套接字的三种类型
套接字有三种类型:
流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)及原始套接字。
1.1.1流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向连接的通讯流。
如果你通过流式套接字发送了顺序的数据:
“1”、“2”。
那么数据到达远程时候的顺序也是“1”、“2”。
流式套接字可以做什么呢?
你听说过Telnet应用程序吗?
听过?
哦,最常用的BBS服务,以及系统的远程登陆都是通过Telnet协议连接的。
Telnet就是一个流式连接。
你是否希望你在Telnet应用程序上输入的字符(或汉字)在到达远程应用程序的时候是以你输入的顺序到达的?
答案应该是肯定的吧。
还有WWW浏览器,它使用的HTTP协议也是通过流式套接字来获取网页的。
事实上,如果你Telnet到一个WebSite的80端口上,然后输入“GET网页路径名”然后按两下回车(或者是两下Ctrl+回车)然后你就得到了“网页
路径名”所代表的网页!
流式套接字是怎样保证这种应用层次上的数据传输质量呢?
它使用了TCP(TheTransmissionControlProtocol)协议(可以参考RFC-793来得到TCP的细节)。
TCP保证了你的数据传输是正确的,并且是顺序的。
TCP是经常出现的TCP/IP中的前半部分。
IP代表InternetProtocol(因特网协议)IP只处理网络路由。
面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且往往是并发服务器。
使用面向连接的套接字编程,可以通过图1-1来表示。
套接字工作过程如下:
服务器首先启动,通过调用socket()建立一个套接字,然后调用
bind()将该套接字和本地网络地址联系在一起,再调用listen()使套接字做好侦听的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接。
客户在建立套接字后就可调用connect()和服务器建立连接。
连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据。
最后,待数据传送结束后,双方调用close()关闭套接字。
图1-1面向连接的socket的工作流程
1.1.2数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。
数据报套接字(DatagramSockets)怎样呢?
为什么它叫做“无连接”?
应该怎样处理它们呢?
为什么它们是不可靠的?
好的,这里有一些事实:
如果你发送了一个数据报,它可能不会到达。
它可能会以不同的顺序到达。
如果它到达了,它包含的数据中可能存在错误。
数据报套接字也使用IP,但是它不使用TCP,它使用使用者数据报协议UDP(UserDatagramProtocol)
为什么说它们是“无连接”的呢?
因为它(UDP)不像流式套接字那样维护一个打开
的连接,你只需要把数据打成一个包,把远程的IP贴上去,然后把这个包发送出去。
这个过程是不需要建立连接的。
UDP的应用例子有:
tftp,bootp等。
那么,数据包既然会丢失,怎样能保证程序能够正常工作呢?
事实上,每个使用UDP的程序都要有自己的对数据进行确认的协议。
比如,TFTP协议定义了对于每一个发送出去的数据包,远程在接受到之后都要回送一个数据包告诉本地程序:
“我已经拿到了!
”(一个“ACK”包)。
如果数据包发的送者在5秒内没有的得到回应,它就会重新发送这个数据包直到数据包接受者回送了“ACK”信号。
这些知识对编写一个使用UDP协议的程序员来说是非常必要的。
无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务
程序之间的相互作用。
若使用无连接的套接字编程,程序的流程可以用图1-2表示。
图1-2无连接的socket工作流程
注:
在客户端一般并不需要调用bind()函数,因为客户端是要去连接服务器的。
但在对等通信中,例如P2P点对点通信中,各节点是对等关系,需要调用bind()函数。
1.1.3原始套接字
原始套接字主要用于一些协议的开发,可以进行比较底层的操作。
它功能强大,但是没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字。
我们已经谈过了网络协议层,那么我们还应该继续多了解一些东西:
物理网络上的数
据是怎样传送的。
我们可以认为是这样的:
数据被分成一个一个的包(Packet),包的数据头(或数据尾)被第一层协议(比如TFTP协议)加上第一层协议数据;
然后整个包(包括内部加入的TFTP信息头)被下层协议再次包装(比如UDP),再这之后数据包会再次被下层协议包装(比如IP协议),最后是被最底层的硬件层(物理层)包装上最后一层信息(Ethernet信息头)。
当接受端的计算机接收到这个包后,硬件首先剥去数据包中的Ethernet信息头,然后内核在剥去IP和UDP信息头,最后把数据包提交给TFTP应用程序,由TFTP剥去TFTP信息头,最后得到了原始数据。
下面我们再大致回顾一下著名的网络层次模型。
通过这个网络模型,你可以写套接字的应用程序而不必在乎事实上数据在物理层中的传输方法(无论是以太网,还是并口、AUI或是其他的什么方法)。
因为已经有程序在底层为你处理了这些问题了。
下面是OSI模型,你可以记住它来应付一些测验。
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
物理层就是硬件层(比如并口,以太网)。
应用程序层离物理层很远很远,以至于它
可以不受物理层的影响。
上面这个模型是最一般的模型,但是在Linux中,真正用到的模型是下面这样子的:
应用层(Telnet,Ftp,等等)
主机间对话层(TCP和UDP)
网络层(IP和路由)
网络底层(相当于OSI模型中网络、数据链路和物理层)
现在,你大概已经明白各个协议层是怎样对原始数据进行包装和解包的了吧。
1.3socket基本数据结构
Socket中最常用的数据结构有:
in_addr、sockaddr和sockaddr_in。
其中in_addr存储主机IP地址,sockaddr_in用于表示internet协议簇地址结构,sockaddr存储主机IP地址与接口信息。
1.3.1structin_addr
其定义如下:
/*因特网地址(astructureforhistoricalreasons)*/
structin_addr{
unsignedlongs_addr;
};
如果你声明了一个“ina”作为一个structsockaddr_in的结构,那么
“ina.sin_addr.s_addr”就是4个字节的IP地址(按网络字节顺序排放)。
例如:
in_addrtest;
test.s_addr=inet_addr("
192.168.0.1"
);
1.3.2.structsockaddr
structsockaddr
{
unsignedshortsa_family;
/*地址族*/
charsa_data[14];
/*14字节的协议地址,包含该socket的IP地址和端口号。
*/
sa_family一般来说,都是“AF_INET”。
sa_data包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一
切的。
为了处理structsockaddr,程序员建立了另外一个相似的结构structsockaddr_in:
structsockaddr_in(“in”代表“Internet”)
1.3.3.structsockaddr_in
structsockaddr_in
shortintsa_family;
unsignedshortintsin_port;
/*端口号*/
structin_addrsin_addr;
/*IP地址*/
unsignedcharsin_zero[8];
/*填充0以保持与structsockaddr同样大小*/
这个结构提供了方便的手段来访问socketaddress(structsockaddr)结构中的每一个元素。
注意sin_zero[8]是为了是两个结构在内存中具有相同的尺寸,使用sockaddr_in的时候要把sin_zero全部设成零值(使用bzero()或memset()函数)。
而且,有一点很重要,就是一个指向structsockaddr_in的指针可以声明指向一个sturctsockaddr的结构。
所以虽然socket()函数需要一个structaddr*,你也可以给他一个sockaddr_in*。
注意在structsockaddr_in中,sin_family相当于在structsockaddr中的sa_family,需要设成“AF_INET”。
最后一定要保证sin_port和sin_addr必须是网络字节顺序(见下节)!
1.4基本转换函数
在前面提到了网络字节顺序。
那么什么是网络字节顺序,它有什么特殊性,又如何将我们通常使用的数据转换成这种格式呢?
1.4.1网络字节顺序
因为每一个机器内部对变量的字节存储顺序不同(有的系统是高位在前,底位在后,而有的系统是底位在前,高位在后),而网络传输的数据大家是一定要统一顺序的。
所以对与内部字节表示顺序和网络字节顺序不同的机器,就一定要对数据进行转换(比如IP地址的表示,端口号的表示)。
但是内部字节顺序和网络字节顺序相同的机器该怎么办呢?
是这样的:
它们也要调用转换函数,但是真正转换还是不转换是由系统函数自己来决定的。
1.4.2网络字序转换函数族
我们通常使用的有两种数据类型:
短型(两个字节)和长型(四个字节)。
下面介绍的这些转换函数对于这两类的无符号整型变量都可以进行正确的转换。
如果你想将一个短型数据从主机字节顺序转换到网络字节顺序的话,有这样一个函数:
它是以“h”开头的(代表“主机”);
紧跟着它的是“to”,代表“转换到”;
然后是“n”代表“网络”;
最后是“s”,代表“短型数据”。
H-to-n-s,就是htons()函数(可以使用HosttoNetworkShort来助记)
下面是几个字节顺序转换函数:
头文件:
<
netinet/in.h>
·
htonl():
把32位值从主机字节序转换成网络字节序
htons():
把16位值从主机字节序转换成网络字节序
ntohl():
把32位值从网络字节序转换成主机字节序
ntohs():
把16位值从网络字节序转换成主机字节序
在structsockaddr_in中的sin_addr和sin_port他们的字节顺序都是网络字节顺序,而
sin_family却不是网络字节顺序的。
为什么呢?
这个是因为sin_addr和sin_port是从IP和UDP协议层取出来的数据,而在IP和UDP协议层,是直接和网络相关的,所以,它们必须使用网络字节顺序。
然而,sin_family域只是内核用来判断structsockaddr_in是存储的什么类型的数据,并且,sin_family永远也不会被发送到网络上,所以可以使用主机字节顺序来存储。
1.4.3IP地址转换函数
sys/socket.h>
<
arpa/inet.h>
主要函数:
intinet_aton(constchar*cp,structin_addr*inp);
//将字符串表示IP地址转structin_addr表示
unsignedlongintinet_addr(constchar*cp);
//将字符串表示IP地址转32bits表示
char*inet_ntoa(structin_addrin);
//将structin_addr表示IP地址转字符串表示
首先,让我假设你有一个structsockaddr_inina,并且你的IP是166.111.69.52,你想把你的IP存储到ina中。
你可以使用的函数:
inet_addr(),它能够把一个用数字和点表示IP地址的字符串转换成一个无符号长整型。
你可以像下面这样使用它:
ina.sin_addr.s_addr=inet_addr(“166.111.69.52”);
注意:
inet_addr()返回的地址已经是网络字节顺序了,你没有必要再去调用htonl()函数。
如果你有一个structin_addr并且你想把它代表的IP地址打印出来(按照“数字.数字.数字.数字”的格式)……
这里,你可以使用函数inet_ntoa()(“ntoa”代表“NetworktoASCII”):
printf(“%s”,inet_ntoa(ina.sin_addr));
这段代码将会把structin_addr里面存储的网络地址以“数字.数字.数字.数字”的格式显示出来。
2、常用套接字函数
Linux支持伯克利(BSD)风格的套接字编程.它同时支持面向连接和不连接类型的套接字。
在面向连接的通讯中服务器和客户机在交换数据之前先要建立一个连接.再不连接通讯中数据被作为信息的一部分被交换.无论那一种方式,服务器总是最先启动,把自己绑定(Banding)在一个套接字上,然后侦听信息.服务器究竟怎样试图去侦听就得依靠你编程所设定的连接的类型了。
你需要了解的一些系统调用:
?
socket()
bind()
connect()
listen()
accept()
send()
recv()
sendto()
recvfrom()
close()
shutdown()
setsockopt()
getsockopt()
getpeername()
getsockname()
gethostbyname()
gethostbyaddr()
getprotobyname()
fcntl()
我们将在以下详细介绍这些系统调用。
2.1socket()函数
取得套接字描述符!
(记得我们以前说过的吗?
它其实就是一个文件描述符)
socket函数的定义是下面这样子的:
#include<
sys/types.h>
intsocket(intdomain,inttype,intprotocol);
首先,domain需要被设置为“AF_INET”,就像上面的structsockaddr_in。
然后,type参数告诉内核这个socket是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。
最后,只需要把protocol设置为0。
事实上,domain参数可以取除了“AF_INET”外的很多值,types参数也可以取除了“SOCK_STREAM”或“SOCK_DGRAM”的另外类型。
具体可以参考socket的manpages(帮助页)。
套接字创建时没有指定名字.客户机用套接字的名字读写它。
这就是下面的绑定函数所要做之事.
socket()函数只是简单的返回一个你以后可以使用的套接字描述符。
如果发生错误,socket()函数返回–1。
全局变量errno将被设置为错误代码。
(可以参考perror()的manpages)
2.2bind()函数
bind()函数可以帮助你指定一个套接字使用的端口。
当你使用socket()函数得到一个套接字描述符,你也许需要将socket绑定上一个你的机器上的端口。
当你需要进行端口监听listen()操作,等待接受一个连入请求的时候,一般都需要经过这一步。
比如网络泥巴(MUD),Telneta.b.c.d4000。
如果你只是想进行连接一台服务器,也就是进行connect()操作的时候,这一步并不是必须的。
bind()的系统调用声明如下:
intbind(intsockfd,structsockaddr*my_addr,intaddrlen);
参数说明:
vsockfd是由socket()函数返回的套接字描述符。
vmy_addr是一个指向structsockaddr的指针,包含有关你的地址的信息:
名称、端口和IP地址。
vaddrlen可以设置为sizeof(structsockaddr)。
好,下面我们看一段程序:
string.h>
#defineMYPORT4000
main()
intsockfd;
structsockaddr_inmy_addr;
sockfd=socket(AF_INET,SOCK_STREAM,0);
/*在你自己的程序中要进行错误检查!
!
*/
my_addr.sin_family=AF_INET;
/*主机字节顺序*/
my_addr.sin_port=htons(MYPORT);
/*网络字节顺序,短整型*/
my_addr.sin_addr.s_addr=inet_addr(“166.111.69.52”);
bzero(&
(my_addr.sin_zero),8);
/*将整个结构剩余部分数据设为0*/
/*不要忘记在你自己的程序中加入判断bind错误的代码!
!
bind(sockfd,(structsockaddr*)&
my_addr,sizeof(structsockaddr));
……
这里有一些值得注意的代码段:
my_addr.sin_port是网络字节顺序。
my_addr.sin_addr.s_addr也是网络字节顺序。
代码段包含的头文件,在不同的系统中可能有一点小小的区别。
(不过在Linux中是如此)如果并非如此,你可以查一查manpages来获取帮助。
最后,bind()可以在程序中自动获取你自己的IP地址和端口。
当bind()函数调用错误的时候,它也是返回–1作为错误发生的标志。
errn的值为错误代码。
另外一件必须指出的事情是:
当你调用bind()的时候,不要把端口数设置的过小!
小于1024的所有端口都是保留下来作为系统使用端口的,没有root权利无法使用。
你可以使用1024以上的任何端口,一直到65535:
你所可能使用的最大的端口号(当然,你还要保证你所希望使用的端口没有被其他程序所使用)。
最后注意有关bind()的是:
有时候你并不一定要调用bind()来建立网络连接。
比如你只是想连接到一个远程主机上面进行通讯,你并不在乎你究竟是用的自己机器上的哪个端口进行通讯(比如Telnet),那么你可以简单的直接调用connect()函数,connect()将自动寻找出本地机器上的一个未使用的端口,然后调用bind()来将其socket绑定到那个端口上。
2.3connect()函数
connect()函数的定义是这样的:
intconnect(intsockfd,structsockaddr*serv_addr,intaddrlen);
connect()的三个参数意义如下:
sockfd:
套接字文件描述符,由socket()函数返回的。
serv_addr是一个存储远程计算机的IP地址和端口信息的结构。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 网络 编程 模型 总结