第5章Linux内核简介5.8网络系统在Linux网络中,网络数据从用户进程传输到网络设备需要经历四个层次5.8.1socket►一个套接字就是与网络的一个连接►socket在逻辑上有三个特征(或要素):(1)网域。它表明一个插口用于哪一种网络。(2)类型。它表明在网络中通信所遵循的模式。网络通信中有两种主要的模式,一种称为“有连接”模式,一种称为“无连接”模式。(3)协议。它表明具体的网络规程。套接字的三种类型►套接字有三种类型:流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM)及原始套接字。►1.流式套接字(SOCK_STREAM):可靠的、面向连接的通讯流。它使用了TCP,保证了你的数据传输是正确的,并且是顺序的。►2.数据报套接字(SOCK_DGRAM):定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错。它使用使用者数据报协议UDP(UserDatagramProtocol)►3.原始套接字:主要用于一些协议的开发,可以进行比较底层的操作。它功能强大,但是没有上面介绍的两种套接字使用方便,一般的程序也涉及不到原始套接字。5.8.2网络分层结构socket描述符►在Linux系统中,任何对I/O的操作,都是通过读或写一个文件描述符来实现的。一个文件描述符只是一个简单的整形数值,代表一个被打开的文件。►socket是一个网络文件描述符——首先调用系统函数socket(),返回一个套接字socket描述符,然后就可以通过对这个套接字描述符进行一些操作:系统函数send()和recv()。►事实上,write()和read()也可以对套接字描述符进行操作的,但是使用send()和recv()函数可以对网络数据的传输进行更好的控制。socket数据结构►structsockaddr{►unsignedshortsa_family;/*地址家族,AF_xxx*/►charsa_data[14];/*14字节协议地址*/►};►sa_family能够是各种各样的类型,一般都是“AF_INET”。sa_data包含套接字中的目标地址和端口信息。►structsockaddr_in(in代表Internet。)►structsockaddr_in{►shortintsin_family;/*通信类型*/►unsignedshortintsin_port;/*端口*/►structin_addrsin_addr;/*Internet地址*/►unsignedcharsin_zero[8];/*与sockaddr结构的长度相同*/►};►用这个数据结构可以轻松处理套接字地址的基本元素。►一定要保证sin_port和sin_addr必须是网络字节顺序基本转换函数►通常使用的整数有两种数据类型:短型(两个字节)和长型(四个字节)。如果你想将一个短型数据从主机字节顺序转换到网络字节顺序的话,有这样一个函数:它是以“h”开头的(代表“主机”);紧跟着它的是“to”,代表“转换到”;然后是“n”代表“网络”;最后是“s”,代表“短型数据”。H-to-n-s,就是htons()函数。►htons()——“HosttoNetworkShort”主机字节顺序转换为网络字节顺序(对无符号短型进行操作4bytes)►htonl()——“HosttoNetworkLong”主机字节顺序转换为网络字节顺序(对无符号长型进行操作8bytes)►ntohs()——“NetworktoHostShort“网络字节顺序转换为主机字节顺序(对无符号短型进行操作4bytes)►ntohl()——“NetworktoHostLong“网络字节顺序转换为主机字节顺序(对无符号长型进行操作8bytes)IP地址转换►函数:inet_addr(),把一个用数字和点表示IP地址的字符串转换成一个无符号长整型。►ina.sin_addr.s_addr=inet_addr(“166.111.69.52”);►注意:inet_addr()返回的地址已经是网络字节顺序了。►函数inet_ntoa()(“ntoa”代表“NetworktoASCII”):►printf(“%s”,inet_ntoa(ina.sin_addr));►这段代码将会把structin_addr里面存储的网络地址以数字.数字.数字.数字的格式显示出来。►inet_ntoa()返回一个字符指针,它指向一个定义在函数inet_ntoa()中的static类型字符串。所以每次你调用inet_ntoa(),都会改变最后一次调用inet_ntoa()函数时得到的结果。socket编程►支持socket编程的常用函数:►socket()函数►bind()函数►connect()函数►listen()函数►accept()函数►send()和recv()函数►sendto()和recvfrom()函数►close()和shutdown()函数socket编程socket()函数►#includesys/types.h►#includesys/socket.h►intsocket(intdomain,inttype,intprotocol);►domain需要被设置为“AF_INET”►type参数告诉内核这个socket是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。►把protocol设置为0。►套接字创建时没有指定名字.客户机用套接字的名字读写它。socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误,socket()函数返回–1。全局变量errno将被设置为错误代码。bind()函数►#includesys/types.h►#includesys/socket.h►intbind(intsockfd,structsockaddr*my_addr,intaddrlen);►参数说明:►sockfd是由socket()函数返回的套接字描述符。►my_addr是一个指向structsockaddr的指针。►addrlen可以设置为sizeof(structsockaddr)。►bind()函数可以帮助你指定一个套接字使用的端口。►当你需要进行端口监听listen()操作,等待接受一个连入请求的时候,一般都需要经过这一步。如果你只是想进行连接一台服务器,也就是进行connect()操作的时候,这一步并不是必须的。►bind()函数调用错误的时候,返回–1作为错误发生的标志。errno的值为错误代码。connect()函数►#includesys/types.h►#includesys/socket.h►intconnect(intsockfd,structsockaddr*serv_addr,intaddrlen);►sockfd:套接字文件描述符,由socket()函数返回的。►serv_addr是一个存储远程计算机的IP地址和端口信息的结构。►addrlen应该是sizeof(structsockaddr)。►一定要检测connect()的返回值:如果发生了错误(比如无法连接到远程主机,或是远程主机的指定端口无法进行连接等)它将会返回错误值–1。全局变量errno将会存储错误代码。listen()函数►#includesys/socket.h►intlisten(intsockfd,intbacklog);►sockfd是一个套接字描述符,由socket()系统调用获得。►backlog是未经过处理的连接请求队列可以容纳的最大数目。每一个连入请求都要进入一个连入请求队列,等待listen的程序调用accept()函数来接受这个连接。当系统还没有调用accept()函数的时候,如果有很多连接,那么本地能够等待的最大数目就是backlog的数值。►listen()如果返回–1,那么说明在listen()的执行过程中发生了错误。全局变量errno中存储了错误代码。►在listen()函数调用之前,需要使用bind()函数来指定使用本地的哪一个端口数值。►如果你想在一个端口上接受外来的连接请求的话,那么函数的调用顺序为:►socket();►bind();►listen();►/*在这里调用accept()函数*/accept()函数►#includesys/socket.h►intaccept(intsockfd,void*addr,int*addrlen);►sockfd是正在listen()的一个套接字描述符。►addr一般是一个指向structsockaddr_in结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的IP地址和端口)。►addrlen是一个本地的整型数值,在它的地址传给accept()前它的值应该是sizeof(structsockaddr_in);accept()不会在addr中存储多余addrlenbytes大小的数据。如果accept()函数在addr中存储的数据量不足addrlen,则accept()函数会改变addrlen的值来反应这个情况。►如果调用accept()失败的话,accept()函数会返回–1来表明调用失败,同时全局变量errno将会存储错误代码。►当调用accept()的时候,大致过程是下面这样的:有人尝试调用connect()来连接你的机器上的某个端口(当然是你已经在listen()的)。他的连接将被listen加入等待队列等待accept()函数的调用(加入等待队列的最多数目由调用listen()函数的第二个参数backlog来决定)。你调用accept()函数,告诉他你准备连接。►accept()函数返回一个新的套接字描述符,这个描述符就代表了这个连接。这时候你有了两个套接字描述符,返回给你的那个就是和远程计算机的连接,而第一个套接字描述符仍然在你的机器上原来的那个端口上listen()。这时候你所得到的那个新的套接字描述符就可以进行send()操作和recv()操作了。send()函数►#includesys/types.h►#includesys/socket.h►intsend(intsockfd,constvoid*msg,intlen,intflags);►sockfd是代表你与远程程序连接的套接字描述符。►msg是一个指针,指向你想发送的信息的地址。►len是你想发送信息的长度。►flags发送标记。一般都设为0。►send()函数在调用后会返回它真正发送数据的长度。►注意:send()所发送的数据可能少于你给它的参数所指定的长度!因为如果你给send()的参数中包含的数据的长度远远大于send()所能一次发送的数据,则send()函数只发送它所能发送的最大数据长度,然后它相信你会把剩下的数据再次调用它来进行第二次发送。所以,记住如果send()函数的返回值小于len的话,则你需要再次发送剩下的数据。幸运的是,如果包足够小(小于1K),那么send()一般都会一次发送光的。►send()函数如果发生错误,则返回–1,错误代码存储在全局变量errno中。recv()函数►#includesys/types.h►#includesys/socket.h►intrecv(intsockfd,void*buf,intlen,unsignedintflags);►sockfd是你要读取数据的套接字描述符。►buf是一个指针,指向你能存储数据的内存缓存区域。►len是缓存区的最大尺寸。►flags是recv()函数的一个标志,一般都为0(具体的其他数值和含义请参考recv()的manpages)。►recv()返回它所真正收到的数据的长度。(也就是存到buf中数据的长度)。如果返回–1则代表发生了错误(比如网络以外中断、对方关闭了套接字连接等),全局变量errno里面存储了错误代码。sendto()