套接字连接的过程如同(客户)打一个电话到一个大公司,接线员(服务器进程)接听电话并把它转接到你要找的部门,然后再从那里转到你要找的人(服务器套接字),然后接线员(服务器进程)再继续转接其它(客户)的电话。
套接字有本地套接字和网络套接字两种。本地套接字的名字是Linux文件系统中的文件名,一般放在/tmp或/usr/tmp目录中;网络套接字的名字是与客户连接的特定网络有关的服务标识符(端口号或访问点)。这个标识符允许Linux将进入的针对特定端口号的连接转到正确的服务器进程。
套接字通信建立过程
服务器端:
- 服务器应用程序用系统调用socket创建一个套接字。 它是系统分配给服务器进程的类似文件描述符的资源。
- 服务器进程用系统调用bind命名套接字。 然后服务器进程就开始等待客户连接到这个命名套接字。
- 系统调用listen创建一个队列用来存放来自客户的进入连接。
- 服务器通过系统调用accept来接受客户的连接。 accept会创建一个不同于命名套接字的新套接字来与这个特定客户进行通信,而命名套接字则被保留下来继续处理其他客户的连接请求。
客户端:
- 调用socket创建一个未命名套接字。
- 调用connect与服务器建立连接,将服务器的命名套接字作为一个地址。
套接字地址
每个套接字域都有自己的地址格式。
AF_UNIX 域套接字格式
- #include <sys/un.h>
- struct sockaddr_un
- {
- sa_family_t sun_family; //AF_UNIX
- char sun_path[]; //文件路径,Linux规定不超过108个字符
- }
AF_INET 域套接字格式
- #include <netinet/in.h>
- struct sockaddr_in
- {
- short int sin_family; //AF_INET
- unsigned short int sin_port; //端口号
- struct in_addr sin_addr; //IP地址
- };
- IP地址结构
- struct in_addr
- {
- unsigned long int s_addr;
- };
IP地址是由4个字节组成的一个32位的值。
对于应用程序来说,套接字就和文件描述符一样,并且通过一个唯一的整数值来区分。
套接字通信涉及的系统调用
1.创建套接字(服务器/客户端)
- #include <sys/types.h>
- #include <sys/socket.h>
- int socket(int domain, int type, int protocol);
- domain:套接字域,用于指定协议族。 最常用的套接字域是AF_UNIX和AF_INET。前者用于通过*inx文件系统实现的本地套接字,后者用于UNIX网络套接字。
- type:通信类型。 取值包括SOCK_STREAM 和 SOCK_DGRAM 。对AF_INET域来说,前者默认是通过TCP连接实现的,是有序、可靠和面向连接的双向字节流,可重传丢失的数据;后者默认通过UDP数据报来实现,不保证消息肯定会被正确和有序地传递。
- protocol:使用的协议。由套接字类型和套接字域来决定,一般设置为0,表示使用默认协议。
socket系统调用返回一个描述符,当连接建立以后,就可以通过这个描述符在套接字上发送和接收数据了。
close系统调用用于结束套接字连接。
2.命名套接字(服务器)
- #include <sys/socket.h>
- int bind(int socket, const struct sockaddr *addr, size_t addr_len);
通过socket调用创建的套接字必须经过命名后才能使用。
bind系统调用把addr中的地址分配给与描述符socket关联的未命名套接字,地址结构的长度由addr_len指定。 addr和addr_len因地址族(AF_UNIX、AF_INET等)的不同而不同,bind调用时需要将指向特定地址结构的指针转化为指向通用地址的指针,即(struct sockaddr *)。
参数socket为由服务器创建的未命名的套接字描述符;
参数addr为通用地址结构,一般只需提供固定的端口号,即如下设置
- struct sockaddr_in server_add;
- server_add.sin_family = AF_INET;
- server_add.sin_addr.s_addr = htonl(INADDR_ANY); //接受任意IP地址的客户连接
- server_add.sin_port = htons(port); //port为服务器端指定的端口号,unsigned short int类型
- server_len = sizeof(server_add);
- bind(server_sockfd, (struct sockaddr*)&server_add, server_len);
参数addr_len为服务器地址结构的长度;
bind调用成功返回0,失败返回-1并设置errno的值。
errno的值及意义
- EBADF:描述符无效
- ENOTSOCK:描述符对应的不是一个套接字
- EINVAL:描述符对应的是一个已命名的套接字
- EADDRNOTAVAIL:地址不可用
- EADDRINUSE:地址已经绑定了一个套接字
- EACCESS:(AF_UNIX特有)权限不够,无法创建文件系统中的路径名
- ENOTDIR、ENAMETOOLONG:(AF_UNIX特有)文件名不符合要求
3.创建套接字队列(服务器)
- #include <sys/socket.h>
- int listen(int socket, int backlog);
服务器调用listen创建一个队列来保存未来得及处理的请求。
参数socket为被服务器命名过的套接字描述符;backlog为设置的队列的长度,常用值为5.
listen调用成功时返回0,失败时返回-1并设置errno的值。
4.接受连接(服务器)
- #include <sys/socket.h>
- int accept(int socket, struct sockaddr *addr, size_t *addr_len);
服务器通过accept系统调用来等待客户端对该套接字的连接。
accept只有当有客户程序试图连接到由socket参数指定的套接字时才会返回。
accept将会创建一个新的套接字来与该客户进行通信,并且返回新套接字的描述符。
参数socket为被服务器命名过的套接字描述符;
参数addr指向的socketadd地址结构用来存放将要连接到的客户的地址,只有accept成功返回时才有效。如果不关心客户地址,可以将addr参数指定为空指针;
- int client_sockfd;
- struct sockaddr_in client_add;
- clinet_len = sizeof(client_add);
- client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_add, &client_len);
参数addr_len指定客户地址结构的长度。如果超过这个值,客户地址将被截断。在accept调用之前,需要将其设置为预期的地址长度,当accept调用返回之后,addr_len将被设置为客户地址的实际长度。
注:accept中的addr_len为指向size_t类型的指针,bind中的addr_len为size_t类型。
如果套接字队列中没有未处理的连接,accept将阻塞(程序将暂停)直到有客户建立连接请求。
可以通过对套接字描述符设置 O_NONBLOCK 标志来改变这一行为,使用的函数是 fcntl,如下:
- int flag = fcntl(socket, F_GETFL, 0);
- fcntl(socket, F_SETFL, O_NONBLOCK | flag);
当有未处理的请求时,accept返回一个新的套接字描述符。发生错误时,accept返回-1,并且设置errno的值。除了前面提到的之外,其他的错误有 EWOULDBLOCK 和 ENITR。前者是指定了O_NONBLOCK标志,但队列中没有未处理的请求;后者是当进程阻塞在accept调用时执行被中断。
5.请求连接(客户端)
- #include <sys/socket.h>
- int connect(int socket, const struct sockaddr *addr, size_t addr_len);
“擦,跟bind的参数类型一样一样一样滴。。”
客户端首先利用socket系统调用创建一个套接字,但不给它命名,然后利用connect系统调用建立起该未命名套接字与服务器监听套接字之间的连接。
参数socket为客户端程序创建的未命名套接字描述符;
参数addr指向的为服务器端的地址结构;
- struct sockaddr_in server_add;
- server_add.sin_family = AF_INET;
- serevr_add.sin_addr.s_addr = inet_addr("***.***.***.***");
- //服务器地址,无需htonl转换,因为inet_addr已定义为网络字节序
- server_add.sin_addr.sin_port = htons(port); //port,int型变量,与服务器端相同的端口号
- len = sizeof(server_add);
- connect(sockfd, (struct sockaddr *)&server_add, len); //可根据返回值判断连接状态
参数addr_len为addr指向的地址结构(即服务器地址)的长度。
connect调用成功时返回0,失败时返回-1并设置错误代码errno。
errno可能的值有:
- EBADF:参数socket的描述符无效
- EALREADY:该套接字上已经有一个正在进行中的连接
- ETIMEDOUT:请求超时
- ECONNREFUSED:连接请求被服务器拒绝
如果连接不能立刻建立,connect将会阻塞一段时间,如果超过某个超时时间,连接将被放弃,connect调用失败。
如果连接被一个信号中断,而该信号又得到了处理,connect依然会调用失败(erron=EINTR),但连接尝试交不会放弃,而是以异步方式继续建立,程序必须在此后进行检查以查看连接是否成功建立。
connect调用的阻塞特性可以通过设置该套接字描述符的O_NONBLOCK标志来改变。此时,如果连接不能立刻建立,connect将失败并把errno设置为EINPROGRESS,而连接将以异步方式继续进行。
注:本文部分内容摘自<>
阅读(11551) | 评论(0) | 转发(1) |