摘要:listen函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数在一般在调用bind之后-调用accept之前调用,它的函数原型是:
int listen(int sockfd, int backlog)
返回:0──成功, -1──失败
- 参数sockfd
- 被listen函数作用的套接字,sockfd之前由socket函数返回。在被socket函数返回的套接字fd之时,它是一个主动连接的套接字,也就是此时系统假设用户会对这个套接字调用connect函数,期待它主动与其它进程连接,然后在服务器编程中,用户希望这个套接字可以接受外来的连接请求,也就是被动等待用户来连接。由于系统默认时认为一个套接字是主动连接的,所以需要通过某种方式来告诉系统,用户进程通过系统调用listen来完成这件事。
- 参数backlog
- 这个参数涉及到一些网络的细节。在进程正理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
- 毫无疑问,服务器进程不能随便指定一个数值,内核有一个许可的范围。这个范围是实现相关的。很难有某种统一,一般这个值会小30以内。
当调用listen之后,服务器进程就可以调用accept来接受一个外来的请求。关于accept更的信息,请接着关注本系统文章。
摘要:在套接口中,一个套接字只是用户程序与内核交互信息的枢纽,它自身没有太多的信息,也没有网络协议地址和端口号等信息,在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程。许多时候内核会我们自动绑定一个地址,然而有时用户可能需要自己来完成这个绑定的过程,以满足实际应用的需要,最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口以等待客户来连接。这个事由bind的函数完成。 从bind函数功能我们很容易推测出这个函数的需要的参数与相应的返回值,如果此时大家已经对socket接口有点熟悉了: bind函数并不是总是需要调用的,只有用户进程想与一个具体的地址或端口相关联的时候才需要调用这个函数。如果用户进程没有这个需要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,而不需要调用bind的函数,同时也避免不必要的复杂度。在一般情况下,对于服务器进程问题需要调用bind函数,对于客户进程则不需要调用bind函数。 摘要:网络编程socket api存在一批核心接口,而这一批核心接口就是几个看似简单的函数,尽管实际上这些函数没有一个是简单。connect函数就是这些核心接口的一个函数,它完成主动连接的过程。 connect函数的功能是完成一个有连接协议的连接过程,对于TCP来说就是那个三路握手过程,它的函数原型: 为了理解connect函数,我们需要对connect函数的功能进行介绍。connect函数的功能可以用一句话来概括,就是完成面向连接的协议的连接过程,它是主要连接的。面向连接的协议,在建立连接的时候总会有一方先发送数据,那么谁调用了connect谁就是先发送数据的一方。如此理解connect三个参数是容易了,我必需指定数据发送的地址,同时也必需指定数据从哪里发送,这正好是connect的前两个参数,而第三个参数是为第二个参数服务的。 与所有的socket网络接口一样,connect总会在某个时候可能失败,此时它会返回-1,相应的errno会被设置,用户可能通过这个值确定是哪个错误。常见的错误有对方主机不可达或者超时错误,也可以是对方主机没有相应的进程在对应端口等待。 摘要:socket函数是任何套接口网络编程中第一个使用的函数,它向用户提供一个套接字,即套接口描述文件字,它是一个整数,如同文件描述符一样,是内核标识一个IO结构的索引。通过socket函数,我们指定一个套接口的协议相关的属性,为进行使用socket api做好准备。 如同所有的介绍API的文档一样,我们先给出socket函数的原型: 下面给出几个使用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其它复杂的地方,地址结构可能还是相对简单的。 摘要:对于网络TCP面向连接的程序,它需要在某个时候终止已经存在的连接。用户可以主动终止一个连接,这很重要,尤其对于服务器进程而言,因为一个进程可以同时打开的连接是有限的,如果不在某个时候主动终止已有的连接,那么对于服务器进程来说,它总会在某个时候因为无法打开新连接而失败。 对于UNIX系统而言,无论是一般的文件描述符,还是网络中使用的套接字都是描述字的范围,所以它们都可以用close函数来完成关闭的任务,然后对于网络套接字这一个特殊的描述字,我们却可以使用更加丰富的shutdown函数完成有选择的关闭。下面我们先来看看这个两个函数: 让我们来回忆一下,一个文件描述符关联着一个实际的文件——不管这个文件是什么,普通文件或网络套接口等等,但是多个打字可以同时与一个文件关联,并且内核维护一个文件引用计数。正常情况下,close函数不武断地释放一个描述字关联的文件,除了这个引用计数为0的时候,并且无论如何,当对一个描述字调用了close函数,用户无法再次使用这个描述字。这是close相对shutdown的两点差别,相应地shutdown是针对socket套接口定制的函数,所以它会做的更好。 shutdown函数不是参考引用计数,它会直接关闭相应的socket套接口,无论引用计数是多少。我们还知道,socket套接口是全双工的,也就是用户可以读,也可以写。存在一个这样的情况,此时用户已经把所有要写的数据都写完了,他想告诉对等端这一点;或者用户把所有要读的数据都读完成了,同样要告诉对等端。此时就是关闭读这一半或写这一半,使用shutdown可以完成这一个。系统定义了3个宏,这3个宏分别用作shutdown的后一个参数: 刚才我写完了,这个文章中我们使用close函数,试问一下:我们可以使用shutdown函数代替吗?简单的思考之后,我们知道不可以使用shutdown函数代替,因为我们在子进程中只是想解除sockfd与那个监听套接口的关联,并不想释放这个套接口,原因是在父进程还要使用它;相应在父进程我也只是想解除cfd与其套接口的关联,我们在子进程还需要使用cfd。从这个例子中可以看到,close与shutdown有各自的用处,并不能相互代替,就算在socket套接字这一特定情况下也如此。 摘要:网络服务器编程是一个巨大的挑战,这个挑战来自服务器需要完成的任务太多,这个挑战来自可能存在的恶意或无意破坏,这个挑战来自大量的与内核的交互,同时这个挑战也来自网络本身的极端不稳定性。在面对这些挑战时,服务器却被要求为快速、稳定、可用的结合物!本文关注这些挑战,并讨论如何避免其中遇到的一些问题。 让我们逐一来讨论上面说到的问题,首先说说服务器要完成的任务太多。现在的服务器大多非常繁忙,比如每秒都可以需要处理上千上万个客户请求,这对于稍等大一点网站或者游戏服务器来说都是经常的事。此时我们有两个思路来解决这个问题,一个思路是提高硬件的速度,另一个思路就是提高软件的性能,通常情况是两路齐走。本文不说硬件,所以只是软件这条思路如何走。 为了提高软件性能当然是算法,此时我必须使用并行的算法来处理,并行是当前提高恨不能的必要方法,也是基本方法。在一台主机上的并行是通过软件来实现的,此时有进程模式和线程模式,由于现代的操作系统已经很强大了,所以我们还可以依赖操作系统提供的IO复用、异步IO等等功能实现隐匿的并行。当然后者简单易于实现,但性能的提升空间却没有手工并行再加上此类技术大,所以人们都会手工实现线程或进程,然后在线程或进程内部再使用IO复用、异步IO等技术,所谓为性能无所不用其极。 服务器编程的另一个挑战是需要识别有意无意的破坏,或把有意无意的破坏限制在最小的范围内,单一客户的请求应该是独立与其他用户的请求的,此时一个用户的失败不至于影响其他用户,从而把这种失败破坏限制在有限的范围内。服务器最常见的破坏就是拒绝服务器攻击,如果没有此种特性的服务器,面对拒绝服务攻击只能听之任之了。有时服务器可能需要维护一个统计信息,这样可以比较容易识别出有意破坏,但这一思路可以提高服务器的可用性的同时却降低了服务器的速度。对,服务器的三个特性有时是相互矛盾的,我们提高了其中一个,相应的另一个却在下降,此时需要一种权衡,或更复杂的决择机制。分层或分级是此时我没用的策略,在不同的级别上采用不同的决择。并行在此时仍然是基本的思路与措施。 摘要:在UNIX系统编程过程中,一个进程往往需要等待多个描述字发生某一事件,如可读、可写或异常等等。进程不能永远地等待其中任何单独一个描述字,它需要同时等待所有描述字,此时就是IO复用技术,系统调用select就是实现这一目标的方式之一。本文详细介绍select函数。 select广泛应用于各种场合,因为select对于任何描述字都有作用,它被应用网络程序,也被应用于终端程序,也被应用其它场合。下面我们先来看看它的函数原型: select函数有5个参数,我们会介绍每一个参数。 上面我们简单地针对select自身进行了说明。我们注意到,select的参数有4个是指针,如果这些指针取值是空的话会如何?对于指定描述字集合的参数,如果取空的话,它们的意义很直接,就是没有相应的关心的描述字集合。比如readset==NULL,则说明用户不关心任何一个描述字是否可读,对于writeset、exceptset也完全一样。但是对于timeout==NULL的情况就是很直接的,它表示永远等待下去,此时我们不关心需要等待多长时间,我们只要求至少有一个描述字满足用户所关心的。 对于timeout里面两个成员都取值0的时候,相应意义很直接,就是等待0秒0微秒,也就是说不等待任何时间。此时就相当于简单的轮询。 上面我们介绍了最后一个参数的,那么先前的三个参数我需要注意一下,就是它们的类型是fd_set,fd_set是什么样的类型呢?我并不知道,但有一点,就是POSIX标准为我们提供了四个宏,这四个宏可以完成我们需要对fd_set的操作,而fd_set的实际类型留给系统去定义。这四个宏分别是: select是一相十分复杂的函数,它的返回值如上所说,-1为错误,0为超时,正数为就绪的打字个数。但是事实上打字的个数往往很小,系统会一个限制,一般为1024;这是因为fd_set这个类型的限制,同时select也不应该处理过多的打字,否则话会有性能问题。 select也会设置errno值,最常见的值可能就是EINT,表示一个信号中断了select调用,作为应用程序应该在select返回错误的时候查看errno。网络编程socket之bind函数
int bind(int sockfd, struct sockaddr* addr, socklen_t addrlen)
返回:0──成功, -1──失败网络编程socket之connect函数
int connect(int sockfd, const struct sockaddr* server_addr, socklen_t addrlen)
返回:0──成功, -1──失败。网络编程socket之socket函数
int socket(int family, int type, int protocol)
返回:非负描述字──成功, -1──出错
//使用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之close与shutdown函数
int close(int fd)
返回:0——成功, -1——失败
#include
int shutdown(int sockfd, int howto)
返回:0——成功, -1——失败网络编程socket之服务器编程的挑战
socket网络编程:IO复用_select函数
#include
int select(int max_fd_p_1, fd_set *readset, fd_set* writeset, fd_set* exceptset,struct timeval* timeout);
返回:就绪描述字的个数,0——超时,-1——出错
{
long tv_sec;//秒数
long tv_usec;//微秒数
}
void FD_SET(int fd, fd_set* set);//从把fd添加到set
void FD_CLR(int fd, fd_set* set);//从set中删除fd
int FD_ISSET(int fd, fd_set* set);//判断fd是否在set中被设置