资源预览内容
第1页 / 共21页
第2页 / 共21页
第3页 / 共21页
第4页 / 共21页
第5页 / 共21页
第6页 / 共21页
第7页 / 共21页
第8页 / 共21页
第9页 / 共21页
第10页 / 共21页
亲,该文档总共21页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
I/O模式五种五种 I/OI/O操作方式:操作方式: 阻塞阻塞 I/O I/O 非阻塞非阻塞 I/O I/O I/OI/O多路复用多路复用 信号驱动信号驱动 I/OI/O(SIGIOSIGIO) 异步异步 I/OI/O一般来说,程序进行输入操作有两步:一般来说,程序进行输入操作有两步: 1 1等待有数据可以读等待有数据可以读 2 2将数据从系统内核中拷贝到程序的数据区。将数据从系统内核中拷贝到程序的数据区。对于一个对套接字的输入操作,第一步一般来说是等待数据对于一个对套接字的输入操作,第一步一般来说是等待数据从网络上传到本地。当数据包到达的时候,数据将会从网络从网络上传到本地。当数据包到达的时候,数据将会从网络层拷贝到内核的缓存中;第二步是从内核中把数据拷贝到程层拷贝到内核的缓存中;第二步是从内核中把数据拷贝到程序的数据区中。序的数据区中。 阻塞 I/O模式 阻塞阻塞 I/OI/O模式是最普遍使用的模式是最普遍使用的 I/OI/O模式。大部分程序使用模式。大部分程序使用的都是阻塞模式的的都是阻塞模式的 I/O I/O 。缺省的,一个套接字建立后所处于。缺省的,一个套接字建立后所处于的模式就是阻塞的模式就是阻塞 I/OI/O模式。模式。 对于一个对于一个 UDPUDP套接字来说,数据就绪的标志比较简单:套接字来说,数据就绪的标志比较简单: 已经收到了一整个数据报已经收到了一整个数据报 没有收到。没有收到。 而而 TCPTCP这个概念就比较复杂,需要附加一些其他的变量。这个概念就比较复杂,需要附加一些其他的变量。一个进程调用一个进程调用 recvfromrecvfrom ,然后系统调用并不返回直到有数,然后系统调用并不返回直到有数据报到达本地系统,然后系统将数据拷贝到进程的缓存中。据报到达本地系统,然后系统将数据拷贝到进程的缓存中。(如果系统调用收到一个中断信号,则它的调用会被中断)(如果系统调用收到一个中断信号,则它的调用会被中断)我们称这个进程在调用我们称这个进程在调用 recvfromrecvfrom一直到从一直到从 recvfromrecvfrom返回这返回这段时间是阻塞的。当段时间是阻塞的。当 recvfromrecvfrom正常返回时,我们的进程继续正常返回时,我们的进程继续它的操作。它的操作。非阻塞模式 I/O 当我们将一个套接字设置为非阻塞模式,我们相当于告诉当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:了系统内核:“ “当我请求的当我请求的 I/OI/O操作不能够马上完成,你想让操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。错误给我。” ” 我们开始对我们开始对 recvfromrecvfrom的三次调用,因为系统还没有接收到的三次调用,因为系统还没有接收到网络数据,所以内核马上返回一个网络数据,所以内核马上返回一个 EWOULDBLOCKEWOULDBLOCK的错误。的错误。第四次我们调用第四次我们调用 recvfromrecvfrom函数,一个数据报已经到达了,内函数,一个数据报已经到达了,内核将它拷贝到我们的应用程序的缓冲区中,然后核将它拷贝到我们的应用程序的缓冲区中,然后 recvfromrecvfrom正正常返回,我们就可以对接收到的数据进行处理了。常返回,我们就可以对接收到的数据进行处理了。 当一个应用程序使用了非阻塞模式的套接字,它需要使用当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不听的测试是否一个文件描述符有数据可读(称做一个循环来不听的测试是否一个文件描述符有数据可读(称做 pollingpolling)。应用程序不停的)。应用程序不停的 pollingpolling内核来检查是否内核来检查是否I/OI/O操作已操作已经就绪。这将是一个极浪费经就绪。这将是一个极浪费 CPUCPU资源的操作。这种模式使用资源的操作。这种模式使用中不是很普遍。中不是很普遍。I/O多路复用 在使用在使用 I/OI/O多路技术的时候,我们调用多路技术的时候,我们调用 select()select()函数和函数和 poll()poll()函数的时候阻塞,而不是调函数的时候阻塞,而不是调用用 recvfromrecvfrom(或(或 recvrecv)的时候阻塞。)的时候阻塞。 当我们调用当我们调用 selectselect函数阻塞的时候,函数阻塞的时候,selectselect函数等待数据报套接字进入读就绪状态。当函数等待数据报套接字进入读就绪状态。当 selectselect函数返回的时候,也就是套接字可以读取数据的时候。这时候我们就可以调用函数返回的时候,也就是套接字可以读取数据的时候。这时候我们就可以调用recvfromrecvfrom函数来将数据拷贝到我们的程序缓冲区中。函数来将数据拷贝到我们的程序缓冲区中。 和阻塞模式相比较,和阻塞模式相比较,select()select()和和 poll()poll()并没有什么高级的地方,而且,在阻塞模式下只需并没有什么高级的地方,而且,在阻塞模式下只需要调用一个函数:读取或发送,在使用了多路复用技术后,我们需要调用两个函数了:先要调用一个函数:读取或发送,在使用了多路复用技术后,我们需要调用两个函数了:先调用调用 select()select()函数或函数或 poll()poll()函数,然后才能进行真正的读写。函数,然后才能进行真正的读写。 多路复用的高级之处在于,它能同时等待多个文件描述符,而这些文件描述符(套接字多路复用的高级之处在于,它能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,描述符)其中的任意一个进入读就绪状态,select()select()函数就可以返回。函数就可以返回。 假设我们运行一个网络客户端程序,要同时处理套接字传来的网络数据又要处理本地的假设我们运行一个网络客户端程序,要同时处理套接字传来的网络数据又要处理本地的标准输入输出。在我们的程序处于阻塞状态等待标准输入的数据的时候,假如服务器端的标准输入输出。在我们的程序处于阻塞状态等待标准输入的数据的时候,假如服务器端的程序被程序被 killkill(或是自己(或是自己 DownDown掉了),那么服务器程端的掉了),那么服务器程端的 TCPTCP协议会给客户端(我们这端)协议会给客户端(我们这端)的的 TCPTCP协议发送一个协议发送一个 FINFIN数据代表终止连接。但是我们的程序阻塞在等待标准输入的数据数据代表终止连接。但是我们的程序阻塞在等待标准输入的数据上,在它读取套接字数据之前(也许是很长一段时间),它不会看见结束标志我们就不上,在它读取套接字数据之前(也许是很长一段时间),它不会看见结束标志我们就不能够使用阻塞模式的套接字。能够使用阻塞模式的套接字。 IOIO多路技术一般在下面这些情况中被使用:多路技术一般在下面这些情况中被使用: 当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的输当一个客户端需要同时处理多个文件描述符的输入输出操作的时候(一般来说是标准的输入输出和网络套接字),入输出和网络套接字), I/OI/O多路复用技术将会有机会得到使用。多路复用技术将会有机会得到使用。 当程序需要同时进行多个套接字的操作的时候。当程序需要同时进行多个套接字的操作的时候。 如果一个如果一个 TCPTCP服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接字。服务器程序同时处理正在侦听网络连接的套接字和已经连接好的套接字。 如果一个服务器程序同时使用如果一个服务器程序同时使用 TCPTCP和和 UDPUDP协议。协议。 如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如如果一个服务器同时使用多种服务并且每种服务可能使用不同的协议(比如 inetdinetd就是这就是这样的)。样的)。 I/OI/O多路服用技术并不只局限与网络程序应用上。几乎所有的程序都可以找到应用多路服用技术并不只局限与网络程序应用上。几乎所有的程序都可以找到应用 I/OI/O多路多路复用的地方。复用的地方。 信号驱动 I/O模式 我们可以使用信号,让内核在文件描述符就绪的时候使用我们可以使用信号,让内核在文件描述符就绪的时候使用 SIGIOSIGIO信号来通知我们。我们将这信号来通知我们。我们将这种模式称为信号驱动种模式称为信号驱动 I/OI/O模式。模式。 使用这种模式,我们首先需要允许套接字使用信号驱动使用这种模式,我们首先需要允许套接字使用信号驱动 I/O I/O ,还要安装一个,还要安装一个 SIGIOSIGIO的处理函的处理函数。在这种模式下,系统调用将会立即返回,然后我们的程序可以继续做其他的事情。当数据数。在这种模式下,系统调用将会立即返回,然后我们的程序可以继续做其他的事情。当数据就绪的时候,系统会向我们的进程发送一个就绪的时候,系统会向我们的进程发送一个 SIGIOSIGIO信号。这样我们就可以在信号。这样我们就可以在 SIGIOSIGIO信号的处理信号的处理函数中进行函数中进行 I/OI/O操作(或是我们在函数中通知主函数有数据可读)。操作(或是我们在函数中通知主函数有数据可读)。 对于信号驱动对于信号驱动 I/OI/O模式,它的先进之处在于它在等待数据的时候不会阻塞,程序可以做自己模式,它的先进之处在于它在等待数据的时候不会阻塞,程序可以做自己的事情。当有数据到达的时候,系统内核会向程序发送一个的事情。当有数据到达的时候,系统内核会向程序发送一个 SIGIOSIGIO信号进行通知,这样我们的信号进行通知,这样我们的程序就可以获得更大的灵活性,因为我们不必为等待数据进行额外的编码。程序就可以获得更大的灵活性,因为我们不必为等待数据进行额外的编码。 信号信号 I/OI/O可以使内核在某个文件描述符发生改变的时候发信号通知我们的程序。异步可以使内核在某个文件描述符发生改变的时候发信号通知我们的程序。异步 I/OI/O可可以提高我们程序进行以提高我们程序进行 I/OI/O读写的效率。通过使用它,当我们的程序进行读写的效率。通过使用它,当我们的程序进行 I/OI/O操作的时候,内核可操作的时候,内核可以在初始化以在初始化 I/OI/O操作后立即返回,在进行操作后立即返回,在进行 I/OI/O操作的同时,我们的程序可以做自己的事情,直到操作的同时,我们的程序可以做自己的事情,直到 I/OI/O操作结束,系统内核给我们的程序发消息通知。操作结束,系统内核给我们的程序发消息通知。 基于基于 BerkeleyBerkeley接口的接口的 SocketSocket信号驱动信号驱动 I/OI/O使用信号使用信号 SIGIOSIGIO。有的系统。有的系统 SIGPOLLSIGPOLL信号,它也是信号,它也是相当于相当于 SIGIOSIGIO的。的。 为了在一个套接字上使用信号驱动为了在一个套接字上使用信号驱动 I/OI/O操作,下面这三步是所必须的。操作,下面这三步是所必须的。(1 1)一个和)一个和 SIGIOSIGIO信号的处理函数必须设定。信号的处理函数必须设定。(2 2)套接字的拥有者必须被设定。一般来说是使用)套接字的拥有者必须被设定。一般来说是使用 fcntlfcntl函数的函数的 F_SETOWNF_SETOWN参数来进行设定拥参数来进行设定拥有者。有者。(3 3)套接字必须被允许使用异步)套接字必须被允许使用异步 I/OI/O。一般是通过调用。一般是通过调用 fcntlfcntl函数的函数的 F_SETFLF_SETFL命令,命令, O_ASYNCO_ASYNC为参数来实现。为参数来实现。 注意:我们在设置套接字的属主之前必须将注意:我们在设置套接字的属主之前必须将 SIGIOSIGIO的信号处理函数设好,的信号处理函数设好,SIGIOSIGIO的缺省动作的缺省动作是被忽略。因此我们如果以相反的顺序调用这两个函数调用,那么在是被忽略。因此我们如果以相反的顺序调用这两个函数调用,那么在 fcntlfcntl函数调用之后,函数调用之后,signalsignal函数调用之前就有一小段时间程序可能接收到函数调用之前就有一小段时间程序可能接收到 SIGIOSIGIO信号。那样的话,信号将会被丢弃。信号。那样的话,信号将会被丢弃。在在 SVR4SVR4系统中,系统中,SIGIOSIGIO在在 sys/ 头文件中被定义为头文件中被定义为 SIGPOLLSIGPOLL,而,而 SIGPOLLSIGPOLL信号的缺信号的缺省动作是终止这个进程。所以我们一定要保证这两个函数的调用顺序:先调用省动作是终止这个进程。所以我们一定要保证这两个函数的调用顺序:先调用 signalsignal设置好设置好 SIGIOSIGIO信号处理函数,然后在使用信号处理函数,然后在使用 fcntlfcntl函数设置套接字的属主。函数设置套接字的属主。信号驱动 I/O模式 虽然设定套接字为异步虽然设定套接字为异步 I/OI/O非常简单,但是使用起来困难的部分是怎样在程序中断定产生非常简单,但是使用起来困难的部分是怎样在程序中断定产生 SIGIOSIGIO信号发送给套接字属主的时候,程序处在什么状态。信号发送给套接字属主的时候,程序处在什么状态。 1 1UDPUDP套接字的套接字的 SIGIOSIGIO信号在信号在 UDPUDP协议上使用异步协议上使用异步 I/OI/O非常简单这个信号将会在这个时非常简单这个信号将会在这个时候产生:候产生: 套接字收到了一个数据报的数据包。套接字收到了一个数据报的数据包。 套接字发生了异步错误。套接字发生了异步错误。当我们在使用当我们在使用 UDPUDP套接字异步套接字异步 I/OI/O的时候,我们使用的时候,我们使用 recvfromrecvfrom()()函数来读取数据报数据或是函数来读取数据报数据或是异步异步 I/OI/O错误信息。错误信息。 2 2TCPTCP套接字的套接字的 SIGIOSIGIO信号信号不幸的是,异步不幸的是,异步 I/OI/O几乎对几乎对 TCPTCP套接字而言没有什么作用。因为对于一个套接字而言没有什么作用。因为对于一个 TCPTCP套接字来说,套接字来说, SIGIOSIGIO信号发生的几率太高了,所以信号发生的几率太高了,所以 SIGIOSIGIO信号并不能告诉我们究竟发生了什么事情。在信号并不能告诉我们究竟发生了什么事情。在 TCPTCP连接中,连接中, SIGIOSIGIO信号将会在这个时候产生:信号将会在这个时候产生: 在一个监听某个端口的套接字上成功的建立了一个新连接。在一个监听某个端口的套接字上成功的建立了一个新连接。 一个断线的请求被成功的初始化。一个断线的请求被成功的初始化。 一个断线的请求成功的结束。一个断线的请求成功的结束。 套接字的某一个通道(发送通道或是接收通道)被关闭。套接字的某一个通道(发送通道或是接收通道)被关闭。 套接字接收到新数据。套接字接收到新数据。 套接字将数据发送出去。套接字将数据发送出去。 发生了一个异步发生了一个异步 I/OI/O的错误。的错误。 举例来说,如果一个正在进行读写操作的举例来说,如果一个正在进行读写操作的 TCPTCP套接字处于信号驱动套接字处于信号驱动 I/OI/O状态下,那么每当状态下,那么每当新数据到达本地的时候,将会产生一个新数据到达本地的时候,将会产生一个 SIGIOSIGIO信号,每当本地套接字发出的数据被远程确认信号,每当本地套接字发出的数据被远程确认后,也会产生一个后,也会产生一个 SIGIOSIGIO信号。对于我们的程序来讲,是无法区分这两个信号。对于我们的程序来讲,是无法区分这两个 SIGIOSIGIO有什么区别有什么区别的。在这种情况下使用的。在这种情况下使用 SIGIOSIGIO,TCPTCP套接字应当被设置为无阻塞模式来阻止一个阻塞的套接字应当被设置为无阻塞模式来阻止一个阻塞的 readread和和 writewrite(recvrecv和和 sendsend)操作。我们可以考虑在一个只进行监听网络连接操作的套接字上使)操作。我们可以考虑在一个只进行监听网络连接操作的套接字上使用异步用异步 I/OI/O,这样当有一个新的连接的时候,这样当有一个新的连接的时候,SIGIOSIGIO信号将会产生。信号将会产生。异步 I/O模式 当我们运行在异步当我们运行在异步 I/OI/O模式下时,我们如果想进行模式下时,我们如果想进行 I/OI/O操作,操作,只需要告诉内核我们要进行只需要告诉内核我们要进行 I/OI/O操作,然后内核会马上返回。操作,然后内核会马上返回。具体的具体的 I/OI/O和数据的拷贝全部由内核来完成,我们的程序可以和数据的拷贝全部由内核来完成,我们的程序可以继续向下执行。当内核完成所有的继续向下执行。当内核完成所有的 I/OI/O操作和数据拷贝后,内操作和数据拷贝后,内核将通知我们的程序。核将通知我们的程序。 异步异步 I/OI/O和信号驱动和信号驱动 I/OI/O的区别是:的区别是: 信号驱动信号驱动 I/OI/O模式下,内核在操作可以被操作的时候通知给我模式下,内核在操作可以被操作的时候通知给我们的应用程序发送们的应用程序发送 SIGIOSIGIO消息。消息。 异步异步 I/OI/O模式下,内核在所有的操作都已经被内核操作结束之模式下,内核在所有的操作都已经被内核操作结束之后才会通知我们的应用程序。后才会通知我们的应用程序。 当我们进行一个当我们进行一个 IOIO操作的时候,我们传递给内核我们的文操作的时候,我们传递给内核我们的文件描述符,我们的缓存区指针和缓存区的大小,一个偏移量件描述符,我们的缓存区指针和缓存区的大小,一个偏移量 offsetoffset,以及在内核结束所有操作后和我们联系的方法。这种,以及在内核结束所有操作后和我们联系的方法。这种调用也是立即返回的,我们的程序不需要阻塞住来等待数据的调用也是立即返回的,我们的程序不需要阻塞住来等待数据的就绪。我们可以要求系统内核在所有的操作结束后(包括从网就绪。我们可以要求系统内核在所有的操作结束后(包括从网络上读取信息,然后拷贝到我们提供给内核的缓存区中)给我络上读取信息,然后拷贝到我们提供给内核的缓存区中)给我们发一个消息。们发一个消息。socket编程socket() 函数#include sys/#include #include sys/#include intint socket socket(intint domain , domain , intint type , type , intint protocol protocol); ; domaindomain需要被设置为需要被设置为 “ “AF_INET”AF_INET”typetype参数告诉内核这个参数告诉内核这个 socketsocket是什么类型,是什么类型,“ “SOCK_STREAM”SOCK_STREAM”或是或是“ “ SOCK_DGRAM”SOCK_DGRAM”。把把 protocolprotocol设置为设置为 0 0 。套接字创建时没有指定名字客户机用套接字的名字读写它。套接字创建时没有指定名字客户机用套接字的名字读写它。socketsocket()()函数只是简单的返回一个你以后可以使用的套接字描述符。函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误,如果发生错误, socket()socket()函数返回函数返回 1 1 。全局变量。全局变量 errnoerrno将被将被设置为错误代码。设置为错误代码。bind() 函数#include sys/#include #include sys/#include intint bind ( bind (intint sockfdsockfd , , structstruct sockaddrsockaddr * *my_addrmy_addr , , intint addrlenaddrlen) ) ; ;参数说明:参数说明: sockfdsockfd是由是由 socket()socket()函数返回的套接字描述符。函数返回的套接字描述符。 my_addrmy_addr是一个指向是一个指向 structstruct sockaddrsockaddr的指针。的指针。 addrlenaddrlen可以设置为可以设置为 sizeof(structsizeof(struct sockaddrsockaddr) )。bind()bind()函数可以帮助你指定一个套接字使用的端口。函数可以帮助你指定一个套接字使用的端口。当你需要进行端口监听当你需要进行端口监听 listen()listen()操作,等待接受一个连入请求操作,等待接受一个连入请求的时候,一般都需要经过这一步。如果你只是想进行连接一的时候,一般都需要经过这一步。如果你只是想进行连接一台服务器,也就是进行台服务器,也就是进行 connect() connect() 操作的时候,这一步并不操作的时候,这一步并不是必须的。是必须的。 bind()bind()函数调用错误的时候,返回函数调用错误的时候,返回11作为错误发生的标志。作为错误发生的标志。errnoerrno的值为错误代码。的值为错误代码。connect()函数#include sys/#include #include sys/#include intint connect ( connect (intint sockfdsockfd, , structstruct sockaddrsockaddr * *serv_addrserv_addr, , intint addrlenaddrlen); ); sockfdsockfd :套接字文件描述符,由:套接字文件描述符,由 socket()socket()函数返回的。函数返回的。 serv_addrserv_addr是一个存储远程计算机的是一个存储远程计算机的 IPIP地址和端口信息的结构。地址和端口信息的结构。 addrlenaddrlen应该是应该是 sizeof(structsizeof(struct sockaddrsockaddr) )。一定要检测一定要检测 connect()connect()的返回值:如果发生了错误(比如无法的返回值:如果发生了错误(比如无法连接到远程主机,或是远程主机的指定端口无法进行连接等)连接到远程主机,或是远程主机的指定端口无法进行连接等)它将会返回错误值它将会返回错误值 1 1 。全局变量。全局变量 errnoerrno将会存储错误代码。将会存储错误代码。listen() 函数#include sys/#include intint listen(intlisten(int sockfdsockfd, , intint backlog); backlog); sockfdsockfd是一个套接字描述符,由是一个套接字描述符,由 socket()socket()系统调用获得。系统调用获得。 backlogbacklog是未经过处理的连接请求队列可以容纳的最大数目。是未经过处理的连接请求队列可以容纳的最大数目。 每一个连入请求都要进入一个连入请求队列,等待每一个连入请求都要进入一个连入请求队列,等待 listenlisten的程的程序调用序调用 accept()accept()函数来接受这个连接。当系统还没有调用函数来接受这个连接。当系统还没有调用 acceptaccept()()函数的时候,如果有很多连接,那么本地能够等待的最大数函数的时候,如果有很多连接,那么本地能够等待的最大数目就是目就是 backlogbacklog的数值。的数值。listen()listen()如果返回如果返回 1 1 ,那么说明在,那么说明在 listen()listen()的执行过程中发生的执行过程中发生了错误。全局变量了错误。全局变量 errnoerrno中存储了错误代码。中存储了错误代码。在在 listen()listen()函数调用之前,需要使用函数调用之前,需要使用 bind() bind() 函数来指定使用本函数来指定使用本地的哪一个端口数值。地的哪一个端口数值。如果你想在一个端口上接受外来的连接请求的话,那么函数如果你想在一个端口上接受外来的连接请求的话,那么函数的调用顺序为:的调用顺序为: socket() ; socket() ; bind() ; bind() ; listen() ; listen() ; /* /* 在这里调用在这里调用 accept()accept()函数函数 * */ / accept()函数#include sys/#include intint accept(intaccept(int sockfdsockfd, void *, void *addraddr, , intint * *addrlenaddrlen); ); sockfdsockfd是正在是正在 listen() listen() 的一个套接字描述符。的一个套接字描述符。 addraddr一般是一个指向一般是一个指向 structstruct sockaddr_insockaddr_in结构的指针;里面存储着远程连结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的接过来的计算机的信息(比如远程计算机的 IPIP地址和端口)。地址和端口)。 addrlenaddrlen是一个本地的整型数值,在它的地址传给是一个本地的整型数值,在它的地址传给 accept()accept()前它的值应该前它的值应该是是 sizeof(structsizeof(struct sockaddr_insockaddr_in) );accept()accept()不会在不会在 addraddr中存储多余中存储多余addrlenaddrlen bytesbytes大小的数据。如果大小的数据。如果accept()accept()函数在函数在 addraddr中存储的数据量不足中存储的数据量不足 addrlenaddrlen,则,则 accept()accept()函数会改变函数会改变 addrlenaddrlen的值来反应这个情况。的值来反应这个情况。如果调用如果调用 accept()accept()失败的话,失败的话,accept()accept()函数会返回函数会返回 11来表明调用失败,来表明调用失败,同时全局变量同时全局变量 errnoerrno将会存储错误代码。将会存储错误代码。当调用当调用accept()accept()的时候,大致过程是下面这样的:有人尝试调用的时候,大致过程是下面这样的:有人尝试调用 connectconnect()()来连接你的机器上的某个端口(当然是你已经在来连接你的机器上的某个端口(当然是你已经在 listen()listen()的)。他的连的)。他的连接将被接将被 listenlisten加入等待队列等待加入等待队列等待 accept()accept()函数的调用(加入等待队列的最函数的调用(加入等待队列的最多数目由调用多数目由调用 listen()listen()函数的第二个参数函数的第二个参数 backlogbacklog来决定)。来决定)。 你调用你调用 acceptaccept()()函数,告诉他你准备连接。函数,告诉他你准备连接。 accept()accept()函数返回一个新的套接字描述符,这个描述符就代表了这个连接。函数返回一个新的套接字描述符,这个描述符就代表了这个连接。这时候你有了两个套接字描述符,返回给你的那个就是和远程计算机的连这时候你有了两个套接字描述符,返回给你的那个就是和远程计算机的连接,而第一个套接字描述符仍然在你的机器上原来的那个端口上接,而第一个套接字描述符仍然在你的机器上原来的那个端口上listen()listen()。这时候你所得到的那个新的套接字描述符就可以进行这时候你所得到的那个新的套接字描述符就可以进行 send()send()操作和操作和 recvrecv()()操作了。操作了。send()函数#include sys/#include #include sys/#include intint send(intsend(int sockfdsockfd, const void *, const void *msgmsg, , intint lenlen, , intint flags); flags); sockfdsockfd是代表你与远程程序连接的套接字描述符。是代表你与远程程序连接的套接字描述符。 msgmsg是一个指针,指向你想发送的信息的地址。是一个指针,指向你想发送的信息的地址。 lenlen是你想发送信息的长度。是你想发送信息的长度。 flagsflags发送标记。一般都设为发送标记。一般都设为 0 0。send()send()函数在调用后会返回它真正发送数据的长度。函数在调用后会返回它真正发送数据的长度。注意:注意:send() send() 所发送的数据可能少于你给它的参数所指定的所发送的数据可能少于你给它的参数所指定的长度!因为如果你给长度!因为如果你给 send()send()的参数中包含的数据的长度远远的参数中包含的数据的长度远远大于大于 send()send()所能一次发送的数据,则所能一次发送的数据,则 send()send()函数只发送它所函数只发送它所能发送的最大数据长度,然后它相信你会把剩下的数据再次能发送的最大数据长度,然后它相信你会把剩下的数据再次调用它来进行第二次发送。所以,记住如果调用它来进行第二次发送。所以,记住如果 send()send()函数的返函数的返回值小于回值小于 lenlen的话,则你需要再次发送剩下的数据。幸运的是,的话,则你需要再次发送剩下的数据。幸运的是,如果包足够小(小于如果包足够小(小于 1K1K),那么),那么 send()send()一般都会一次发送光一般都会一次发送光的。的。send()send()函数如果发生错误,则返回函数如果发生错误,则返回 1 1 ,错误代码存储在全,错误代码存储在全局变量局变量 errnoerrno中。中。recv()函数#include sys/#include #include sys/#include intint recv(intrecv(int sockfdsockfd, void *, void *bufbuf, , intint lenlen, unsigned , unsigned intint flags flags); ; sockfdsockfd是你要读取数据的套接字描述符。是你要读取数据的套接字描述符。 bufbuf是一个指针,指向你能存储数据的内存缓存区域。是一个指针,指向你能存储数据的内存缓存区域。 lenlen是缓存区的最大尺寸。是缓存区的最大尺寸。 flagsflags是是 recvrecv() () 函数的一个标志,一般都为函数的一个标志,一般都为 0 0(具体的其他数(具体的其他数值和含义请参考值和含义请参考 recvrecv()()的的 man pagesman pages)。)。 recvrecv() () 返回它所真正收到的数据的长度。(也就是存到返回它所真正收到的数据的长度。(也就是存到 bufbuf中中数据的长度)。如果返回数据的长度)。如果返回 11则代表发生了错误(比如网络则代表发生了错误(比如网络以外中断、对方关闭了套接字连接等),全局变量以外中断、对方关闭了套接字连接等),全局变量 errnoerrno里面里面存储了错误代码。存储了错误代码。sendto()函数#include sys/#include #include sys/#include intint sendtosendto(intint sockfdsockfd, const void *, const void *msgmsg, , intint lenlen, unsigned , unsigned intint flags, const flags, const structstruct sockaddrsockaddr *to, *to, intint tolentolen); ;sockfdsockfd是代表你与远程程序连接的套接字描述符。是代表你与远程程序连接的套接字描述符。 msgmsg是一个指针,指向你想发送的信息的地址。是一个指针,指向你想发送的信息的地址。 lenlen是你想发送信息的长度。是你想发送信息的长度。 flagsflags发送标记。一般都设为发送标记。一般都设为 0 0。(你可以查看。(你可以查看 sendsend的的 man man pagespages来获得其他的参数值并且明白各个参数所代表的含义)来获得其他的参数值并且明白各个参数所代表的含义) toto是一个指向是一个指向 structstruct sockaddrsockaddr结构的指针,里面包含了远程结构的指针,里面包含了远程主机的主机的 IPIP地址和端口数据。地址和端口数据。 tolentolen只是指出了只是指出了 structstruct sockaddrsockaddr在内存中的大小在内存中的大小 sizeof(structsizeof(struct sockaddrsockaddr) )。和。和 send()send()一样,一样,sendtosendto()()返回它所返回它所真正发送的字节数(当然也和真正发送的字节数(当然也和 send()send()一样,它所真正发送的一样,它所真正发送的字节数可能小于你所给它的数据的字节数)。当它发生错误字节数可能小于你所给它的数据的字节数)。当它发生错误的时候,也是返回的时候,也是返回 1 1 ,同时全局变量,同时全局变量 errnoerrno存储了错误代存储了错误代码。码。recvfrom()函数#include sys/#include #include sys/#include intint recvfrom(intrecvfrom(int sockfdsockfd, void *, void *bufbuf, , intint lenlen, unsigned , unsigned intint flags, flags, structstruct sockaddrsockaddr *from, *from, intint * *fromlenfromlen); );sockfdsockfd是你要读取数据的套接字描述符。是你要读取数据的套接字描述符。 bufbuf是一个指针,指向你能存储数据的内存缓存区域。是一个指针,指向你能存储数据的内存缓存区域。 lenlen是缓存区的最大尺寸。是缓存区的最大尺寸。 flagsflags是是 recvrecv() () 函数的一个标志,一般都为函数的一个标志,一般都为 0 0(具体的其他数值和含义请参(具体的其他数值和含义请参考考 recvrecv()()的的 man pagesman pages)。)。 fromfrom是一个本地指针,指向一个是一个本地指针,指向一个 structstruct sockaddrsockaddr的结构(里面存有源的结构(里面存有源 IPIP地址和端口数)地址和端口数) fromlenfromlen是一个指向一个是一个指向一个 intint型数据的指针,它的大小应该是型数据的指针,它的大小应该是 sizeofsizeof( structstruct sockaddrsockaddr)当函数返回的时候,)当函数返回的时候,formlenformlen指向的数据是指向的数据是 fromfrom指向指向的的 structstruct sockaddrsockaddr的实际大小的实际大小 recvfromrecvfrom() () 返回它接收到的字节数,如果发生了错误,它就返回返回它接收到的字节数,如果发生了错误,它就返回1 1 ,全,全局变量局变量 errnoerrno存储了错误代码存储了错误代码如果一个信息大得缓冲区都放不下,那么附加信息将被砍掉。该调用可以如果一个信息大得缓冲区都放不下,那么附加信息将被砍掉。该调用可以立即返回立即返回, ,也可以永久的等待。这取决于你把也可以永久的等待。这取决于你把 flagsflags设置成什么类型。你甚设置成什么类型。你甚至可以设置超时至可以设置超时(timeout)(timeout)值。值。注意:如果你使用注意:如果你使用 cnnectcnnect()()连接到了一个数据报套接字的服务器程序上,连接到了一个数据报套接字的服务器程序上,那么你就可以使用那么你就可以使用 send() send() 和和 recvrecv() () 函数来传输你的数据不要以为你在函数来传输你的数据不要以为你在使用一个流式的套接字,你所使用的仍然是一个使用者数据报的套接字,使用一个流式的套接字,你所使用的仍然是一个使用者数据报的套接字,只不过套接字界面在只不过套接字界面在 send() send() 和和 recvrecv()()的时候自动帮助你加上了目标地址,的时候自动帮助你加上了目标地址,目标端口的信息目标端口的信息 close()和 shutdown()函数程序进行网络传输完毕后,需要关闭这个套接字描述符所表程序进行网络传输完毕后,需要关闭这个套接字描述符所表示的连接。只需要使用标准的关闭文件的函数:示的连接。只需要使用标准的关闭文件的函数:close()close()。close(sockfdclose(sockfd); );执行执行 close()close()之后,套接字将不会在允许进行读操作和写操作。之后,套接字将不会在允许进行读操作和写操作。任何有关对套接字描述符进行读写操作都会接收到一个错误。任何有关对套接字描述符进行读写操作都会接收到一个错误。如果想对网络套接字的关闭进行进一步的操作的话,可使用如果想对网络套接字的关闭进行进一步的操作的话,可使用函数函数 shutdown()shutdown()。它允许进行单向的关闭操作或是全部禁止。它允许进行单向的关闭操作或是全部禁止。 #include sys/#include intint shutdown shutdown(intint sockfdsockfd, , intint how how); ;sockfdsockfd是一个你所想关闭的套接字描述符是一个你所想关闭的套接字描述符 howhow可以取下面的值。可以取下面的值。0 0表示不允许以后数据的接收操;表示不允许以后数据的接收操;1 1表表示不允许以后数据的发送操作;示不允许以后数据的发送操作;2 2表示和表示和 close()close()一样,不允一样,不允许以后的任何操作(包括接收,发送数据)许以后的任何操作(包括接收,发送数据) shutdown() shutdown() 如果执行成功将返回如果执行成功将返回 0 0,如果在调用过程中发生,如果在调用过程中发生了错误,它将返回了错误,它将返回11,全局变量,全局变量 errnoerrno中存储了错误代码中存储了错误代码如果你在一个未连接的数据报套接字上使用如果你在一个未连接的数据报套接字上使用 shutdown()shutdown()函数,函数,它将什么也不做。它将什么也不做。socket数据结构1 1structstruct sockaddrsockaddr这个结构用来存储套接字地址。这个结构用来存储套接字地址。structstruct sockaddrsockaddr unsigned short unsigned short sa_familysa_family; /* address; /* address族族, , AF_xxxAF_xxx */ */ char sa_data14; char sa_data14; /* 14 bytes/* 14 bytes的协议地址的协议地址 * */ / ; ; sa_familysa_family一般来说,都是一般来说,都是“ “AF_INET”AF_INET”。 sa_datasa_data包含了一些远程电脑的地址、端口和套接字的数目,包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是融杂在一起的。它里面的数据是融杂在一起的。socket数据结构structstruct sockaddr_insockaddr_in short short intint sin_familysin_family; /* Internet; /* Internet地址族地址族 * */ / unsigned short unsigned short intint sin_portsin_port; /* ; /* 端口号端口号 * */ / structstruct in_addrin_addr sin_addrsin_addr; /* Internet; /* Internet地址地址 * */ / unsigned char sin_zero8; /* unsigned char sin_zero8; /* 添添0 0(和(和structstruct sockaddrsockaddr一样一样大小)大小)* */ / ;这个结构提供了方便的手段来访问这个结构提供了方便的手段来访问 socket addresssocket address(structstruct sockaddrsockaddr)结构中的每一个元素。虽然)结构中的每一个元素。虽然 socket() socket() 函数需要一函数需要一个个 structaddrstructaddr * * ,你也可以给他一个,你也可以给他一个 sockaddr_insockaddr_in * * 。在。在 structstruct sockaddr_insockaddr_in中,中,sin_familysin_family相当于在相当于在 structstruct sockaddrsockaddr中的中的 sa_familysa_family,需要设成,需要设成 “ “ AF_INET”AF_INET”。最后一定要保证最后一定要保证 sin_portsin_port和和 sin_addrsin_addr必须是网络字节顺序必须是网络字节顺序socket数据结构2 2structstruct in_addrin_addrstructstruct in_addrin_addr unsigned long unsigned long s_addrs_addr; ; ;如果你声明了一个如果你声明了一个 “ “ inaina” ”作为一个作为一个 structstruct sockaddr_insockaddr_in的结的结构,那么构,那么 “ “ina.sin_addr.s_addrina.sin_addr.s_addr” ”就是就是 4 4个字节的个字节的 IPIP地址地址(按网络字节顺序排放)。需要注意的是,即使你的系统仍(按网络字节顺序排放)。需要注意的是,即使你的系统仍然使用联合而不是结构来表示然使用联合而不是结构来表示 structstruct in_addrin_addr,你仍然可以,你仍然可以用上面的方法得到用上面的方法得到 4 4个字节的个字节的 IPIP地址(一些地址(一些 #defines#defines帮了帮了你的忙)。你的忙)。
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号