2010年10月8日星期五

网络编程socket之accept函数

摘要:对于服务器编程中最重要的一步等待并接受客户的连接,那么这一步在编程中如何完成,accept函数就是完成这一步的。它从内核中取出已经建立的客户连接,然后把这个已经建立的连接返回给用户程序,此时用户程序就可以与自己的客户进行点到点的通信了。

accept函数等待并接受客户请求:

#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* len)
返回:非负描述字——成功, -1——失败

accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是一个新可用的套接字,这个套接字是连接套接字。此时我们需要区分两种套接字,一种套接字正如accept的参数sockfd,它是监听套接字,在调用listen函数之后,一个套接字会从主动连接的套接字变身为一个监听套接字;而accept返回是一个连接套接字,它代表着一个网络已经存在的点点连接。自然要问的是:为什么要有两种套接字?原因很简单,如果使用一个描述字的话,那么它的功能太多,使得使用很不直观,同时在内核确实产生了一个这样的新的描述字。

参数sockfd
参数sockfd就是上面解释中的监听套接字,这个套接字用来监听一个端口,当有一个客户与服务器连接时,它使用这个一个端口号,而此时这个端口号正与这个套接字关联。当然客户不知道套接字这些细节,它只知道一个地址和一个端口号。
参数addr
这是一个结果参数,它用来接受一个返回值,这返回值指定客户端的地址,当然这个地址是通过某个地址结构来描述的,用户应该知道这一个什么样的地址结构。如果对客户的地址不感兴趣,那么可以把这个值设置为NULL。
参数len
如同大家所认为的,它也是结果的参数,用来接受上述addr的结构的大小的,它指明addr结构所占有的字节个数。同样的,它也可以被设置为NULL。

如果accept成功返回,则服务器与客户已经正确建立连接了,此时服务器通过accept返回的套接字来完成与客户的通信。


来源:
博爱声明: 

本文档在《博爱协议(版本一)》下授权,更多内容请查看:博爱。
请关心支持人类的慈善事业,把爱与帮助给予需要她们的儿童与贫困家庭。
请关注如下站点:
联合国儿童基金会 | 中华慈善总会

网络编程socket之listen函数

摘要:listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。

listen函数在一般在调用bind之后-调用accept之前调用,它的函数原型是:

#include<sys/socket.h>
int listen(int sockfd, int backlog)
返回:0──成功, -1──失败

参数sockfd
被listen函数作用的套接字,sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时,它是一个主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。
参数backlog
这个参数涉及到一些网络的细节。在进程正理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。

当调用listen之后,服务器进程就可以调用accept来接受一个外来的请求。关于accept更的信息,请接着关注本系统文章。


来源:
博爱声明: 

本文档在《博爱协议(版本一)》下授权,更多内容请查看:博爱。
请关心支持人类的慈善事业,把爱与帮助给予需要她们的儿童与贫困家庭。
请关注如下站点:
联合国儿童基金会 | 中华慈善总会

网络编程socket之bind函数

摘要:在套接口中,一个套接字只是用户程序与内核交互信息的枢纽,它自身没有太多的信息,也没有网络协议地址和端口号等信息,在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程。许多时候内核会我们自动绑定一个地址,然而有时用户可能需要自己来完成这个绑定的过程,以满足实际应用的需要,最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口以等待客户来连接。这个事由bind的函数完成。

从bind函数功能我们很容易推测出这个函数的需要的参数与相应的返回值,如果此时大家已经对socket接口有点熟悉了:

#include<sys/socket.h>
int bind(int sockfd, struct sockaddr* addr, socklen_t addrlen)
返回:0──成功, -1──失败

参数sockfd
指定地址与哪个套接字绑定,这是一个由之前的socket函数调用返回的套接字。调用bind的函数之后,该套接字与一个相应的地址关联,发送到这个地址的数据可以通过这个套接字来读取与使用。
参数addr
指定地址。这是一个地址结构,并且是一个已经经过填写的有效的地址结构。调用bind之后这个地址与参数sockfd指定的套接字关联,从而实现上面所说的效果。
参数addrlen
正如大多数socket接口一样,内核不关心地址结构,当它复制或传递地址给驱动的时候,它依据这个值来确定需要复制多少数据。这已经成为socket接口中最常见的参数之一了。

bind函数并不是总是需要调用的,只有用户进程想与一个具体的地址或端口相关联的时候才需要调用这个函数。如果用户进程没有这个需要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,而不需要调用bind的函数,同时也避免不必要的复杂度。在一般情况下,对于服务器进程问题需要调用bind函数,对于客户进程则不需要调用bind函数。


来源:
博爱声明: 

本文档在《博爱协议(版本一)》下授权,更多内容请查看:博爱。
请关心支持人类的慈善事业,把爱与帮助给予需要她们的儿童与贫困家庭。
请关注如下站点:
联合国儿童基金会 | 中华慈善总会

网络编程socket之connect函数

摘要:网络编程socket api存在一批核心接口,而这一批核心接口就是几个看似简单的函数,尽管实际上这些函数没有一个是简单。connect函数就是这些核心接口的一个函数,它完成主动连接的过程。

connect函数的功能是完成一个有连接协议的连接过程,对于TCP来说就是那个三路握手过程,它的函数原型:

#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr* server_addr, socklen_t addrlen)
返回:0──成功, -1──失败。

为了理解connect函数,我们需要对connect函数的功能进行介绍。connect函数的功能可以用一句话来概括,就是完成面向连接的协议的连接过程,它是主要连接的。面向连接的协议,在建立连接的时候总会有一方先发送数据,那么谁调用了connect谁就是先发送数据的一方。如此理解connect三个参数是容易了,我必需指定数据发送的地址,同时也必需指定数据从哪里发送,这正好是connect的前两个参数,而第三个参数是为第二个参数服务的。

参数sockfd
指定数据发送的套接字,解决从哪里发送的问题。内核需要维护大量IO通道,所以用户必需通过这个参数告诉内核从哪个IO通道,此处就是从哪个socket接口中发送数据。sockfd是先前socket返回的值。
参数server_addr
指定数据发送的目的地,也就是服务器端的地址。这里服务器是针对connect说的,因为connect是主动连接的一方调用的,所以相应的要存在一个被连接的一方,被动连接的一方需要调用listen以接受connect的连接请求,如此被动连接的一方就是服务器了。
参数addrlen
指定server_addr结构体的长度。我们知道系统中存在大量的地址结构,但socket接口只是通过一个统一的结构来指定参数类型,所以需要指定一个长度,以使内核在进行参数复制的时候有个有个界限。

与所有的socket网络接口一样,connect总会在某个时候可能失败,此时它会返回-1,相应的errno会被设置,用户可能通过这个值确定是哪个错误。常见的错误有对方主机不可达或者超时错误,也可以是对方主机没有相应的进程在对应端口等待。


来源:
博爱声明: 

本文档在《博爱协议(版本一)》下授权,更多内容请查看:博爱。
请关心支持人类的慈善事业,把爱与帮助给予需要她们的儿童与贫困家庭。
请关注如下站点:
联合国儿童基金会 | 中华慈善总会

网络编程socket之socket函数

摘要:socket函数是任何套接口网络编程中第一个使用的函数,它向用户提供一个套接字,即套接口描述文件字,它是一个整数,如同文件描述符一样,是内核标识一个IO结构的索引。通过socket函数,我们指定一个套接口的协议相关的属性,为进行使用socket api做好准备。

如同所有的介绍API的文档一样,我们先给出socket函数的原型:

#include<sys/socket.h>
int socket(int family, int type, int protocol)
返回:非负描述字──成功, -1──出错

参数family
这个参数指定一个协议簇,也往往被称为协议域。系统存在许多可以的协议簇,常见有AF_INET──指定为IPv4协议,AF_INET6──指定为IPv6,AF_LOCAL──指定为UNIX 协议域等等。它值都是系统预先定义的宏,系统支持哪些协议我们才可以使用,否则会调用失败。协议簇是网络层的协议。
参数type
这个参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW等等,它们分别表明字节流、数据报、有序分组、原始套接口。这实际上是指定内核为我们提供的服务抽象,比如我们要一个字节流。需要注意的,并不是每一种协议簇都支持这里的所有的类型,所以类型与协议簇要匹配。
参数protocol
指定相应的传输协议,也就是诸如TCP或UDP协议等等,系统针对每一个协议簇与类型提供了一个默认的协议,我们通过把protocol设置为0来使用这个默认的值。注意这里的协议与上面的协议簇是两个不同的概念,前者是指网络层的协议,由于它对于到传输层会出现许多协议,比如IPv4可以用来实现TCP或UDP等等传输层协议,所以称为协议簇。相应的传输层的协议就简单地称为协议。常见的协议有TCP、UDP、SCTP,要指定它们分别使用宏IPPROTO_TCP、IPPROTO_UPD、IPPROTO_SCTP来指定。
返回值
socket函数返回一个套接字,即套接口描述字。如果出现错误,它返回-1,并设置errno为相应的值,用户应该检测以判断出现什么错误。

下面给出几个使用socket函数的示例:

#include<sys/socket.h>
//使用IPv4作为协议簇,使用字节流类型,
//使用系统针对IPv4与字节流的默认的协议,一般为TCP
int sockfd=socket(AF_INET, SOCK_STRAM, 0);

//使用STCP作为协议
int sockfd=socket(AF_INET, SOCK_STRAM, IPPROTO_SCTP);

//使用数据报
int sockfd=socket(AF_INET, SOCK_DGRAM, 0);


来源:
博爱声明: 

本文档在《博爱协议(版本一)》下授权,更多内容请查看:博爱。
请关心支持人类的慈善事业,把爱与帮助给予需要她们的儿童与贫困家庭。
请关注如下站点:
联合国儿童基金会 | 中华慈善总会

网络编程之socket套接口地址结构

摘要:socket api是网络编程的经典选择,可能在许多时候也是不二选择,本文介绍socket套接口中存在的一个知识点:套接口地址结构。socket编程中常存在一个问题,就是填写一个地址结构,然后绑定相应地址,所以对地址结构的理解是基本的要求。

在我第一次写网络程序的时候,或者说我第一次使用socket的时候,深深地感觉到socket中的地址填写有点难写或者有点难受,这个难受在于第一次接触socket本身的生疏,也在于socket地址看上去有点复杂和乱。出于这个原因,Stevens在他们的《UNIX网络编程》中拿一章来说明这个地址结构。

socket接口中地址结构有很多,这是一个基本的事实,但对于多数程序来说可能只会用到其中的少数。这些地址结构存在两类完全不同目的的结构,一类是实际中使用的结构,一类却是只是为参数传递的结构。前者与具体协议有关,不同的地址结构之间的差异很大;而后者就只有两个结构,称为通过结构。

地址结构的内容

我在这里不准备具体介绍哪一个地址结构,而是就地址结构的总体上来说明一下。在地址结构上倾向于为它自身指定一个长度,也就是地址结构往往会有一个长度成员,它指出当前正在使用结构体占有多少字节,但是由于历史原因并不能保证每个实现中都存在这个成员,所以看上去有的这个结构成员可能没有用处──在用户层上这个成员总是很少使用。并且socket api中问题假设用户没有指定这个成员,从而要求把这个长度的值显式地通过参数传递给这些api,如果connect、bind等等。这一点让人难受,有而不用。

在一些地址结构中存在一个未用的扩展区,也就是这个地方现在没有使用,但是在以后的某个时候可能会使用。这样就给人一种感觉,如果有一天这个地方使用了,那么我现在写的程序怎么办,总叫人不放心。可是socket已经产生了几十年,这个预留的东西从来没有使用过,所以历史告诉我们这些东西将来也不会使用。但是出于安全等等各种因素我们需要把这块内存处理为0。

在地址结构存在一个这样的成员它指定相应的地址簇或者说协议簇,它会非常有用,内核会根据这个值来对传入的内容作不同的处理。我们可能已经注意到了,诸如connect、bind这些接口并没有与具体地址结构相关的,所以但是内核必须在某个时候来区分这些地址结构,无论内核在哪里区分它们,区分他们总要一个依据,这个成员就是依据。

地址结构的其它成员就与具体的协议有关了,它们都协议相关的地址结构,各个协议之间可能存在巨大差异,所以我们在这里就不说了。

地址结构的使用

地址结构各有不同,所以用户在使用地址结构的时候必须要知道自己需要使用什么,内核不会为我们做决定,做决定是在用户层上进行的。这就是说,用户需要在使用不同的协议的时候选择一个合适的地址结构并正确的填写其中的成员,接着把填写好的结构交内核,由内核作进一步处理。

由于历史原因,我们可能没有办法知道或者确切了解一个结构成员,所以我们不能一个成员一个成员地来处理,在这里我们使用一个原则:只设置我们感兴趣的成员,把不感兴趣的成员都设置为0。如此就有了一个基本的思路:把整个结构置0,然后把设置我们感兴趣的成员为合适的值。

此外地址结构中大多数成员都使用是网络字节序列,所以用户在设置成员值的时候可能需要把机器字节序列调整为网络字节序列,socket给我们两个工具了(htonl一簇函数),但在网络应用中,可能还需要更强大的工具,此时需要我们手工调整字节序列。

socket套接口地址结构本身可能很复杂,再加上历史原因又加上一些人为的复杂性,所以学习与使用的时候需要小心一些。不过相对socket其它复杂的地方,地址结构可能还是相对简单的。


来源:
博爱声明: 

本文档在《博爱协议(版本一)》下授权,更多内容请查看:博爱。
请关心支持人类的慈善事业,把爱与帮助给予需要她们的儿童与贫困家庭。
请关注如下站点:
联合国儿童基金会 | 中华慈善总会

初识socket网络编程接口

开始学习socket网络编程了,大概已经有几日,在有点感受以记录下来。

socket的历史非常久远,可能追溯到70年代、60年代,当然网络还刚刚起步,berkeley计算机研究组就开始研究如果编写网络程序,如何回调网络应用,从而开发socket的原始版本,socket这个原始版本经过多个厂商与组织发展和扩展最后形成一个有着深厚历史的socket API。这里的深厚二字有许多意义,一方面它是历史的沉积,所以它的功能与作用是经得起验证的,另一方面又因为它的历史,所以它会有许多历史偶然性的东西在作怪,表现为非本质复杂性,因为socket不是被简单地设计出来,它是在实践在产生的。

基本的socket编程接口很诱人,因为我们写一个网络程序只需要几个简单的socket系统调用,然后整体的资源都可以使用了。可能就是简单的几个函数,简单地让人都忘记了,网络编程是最复杂的程序之一。socket-connect-read-write-close构成一个TCP客户端,而在这个客户端上加入bind、listen、accept三个调用就构成了一个TCP服务器程序。那么为什么说网络程序是最难编写的一类程序?

网络程序难以编写有它的本质复杂性在其中,它的本质复杂性就是:网络是非常不稳定的,并且可能随时崩溃。操作系统已经为我们屏蔽大多数这种不稳定性,但操作系统不可能把所有的问题都处理好,而不需要应用的帮助,所以应用不仅仅要关心自己的事,还关心网络,关心网络可能出现的各种情况,为了处理这种复杂的不稳定性,网络应用为此代价惨重。这就是本质复杂性。

socket接口是主流的网络编程接口,它是历史的产物,有着深深的历史烙印,当然也有深深的历史错误。socket中存在一些概念或类型它们的存在从现代的C语言角度来说都是不需要,比如sockaddr结构体类型,它的存在只是为方便传递一个结构指针,但是用户却从来不需要使用这个类型。再就是socket中存在一个地址簇与协议簇的概念,这两个概念看似相似,却看似不相似,原本完全可以没有这个概念的,事实上现在人们都不认为需要这两个概念,但在写程序的时候却要生生地区分它们。

尽管socket给了许多非本质复杂性,但socket仍然不愧为最成功的一类api,试想数十年后的今天我还在使用,这就是充分说明它的成功,历史曾创造了多少奇迹,可是这些奇迹又有多少还在被我们使用?



来源:

http://www.easycpp.org/content/science_doc/%E5%88%9D%E8%AF%86socket%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E6%8E%A5%E5%8F%A3

博爱声明: 

本文档在《博爱协议(版本一)》下授权,更多内容请查看:博爱。
请关心支持人类的慈善事业,把爱与帮助给予需要她们的儿童与贫困家庭。
请关注如下站点:
联合国儿童基金会 | 中华慈善总会