Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。
本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。
connect
connect() 遵循 POSIX.1 - 2008
1.库
标准 c 库,libc, -lc
2.头文件
<sys/socket.h>
3.接口定义
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
4.接口描述
connect() 系统调用在 sockfd 指定的 socket 上连接 addr 指定的地址,addrlen 参数指定了 addr 的大小,addr 地址格式取决于 socket 的地址空间,可以参考 socket(2)。
如果 socket 是 SOCK_DGRAM 类型,那么 addr 是发送报文的默认地址,也是唯一接收报文的地址。如果 socket 类型是 SOCK_STREAM 或者 SOCK_SEQPACKET,那么这个调用就是尝试和绑定了 addr 地址的 socket 建立连接。
一些协议套接字(比如 UNIX 流套接字)只能成功连接一次。
一些协议套接字(比如 UNIX TCP 套接字和网络数据报套接字)可以多次 connect() 来修改连接。
一些协议套接字(比如 UNIX TCP 套接字和网络数据报套接字)可以通过将 sockaddr 的 sa_family 设置为 AF_UNSPEC 来消除连接,之后 socket 就可以连接到其他地址了。(AF_UNSPEC 在 Linux 2.2 之后支持)。
5.返回值
如果连接或者绑定成功,那么返回 0。
发生错误时,返回 -1,并设置errno 来指示错误类型。
错误值定义如下(这里指示普通 socket 的错误,还可能存在 domain-specific 错误码):
EACCES | UNIX 域套接字通过路径名唯一标识,并且是套接字文件是没有写权限的,路径中任何一级的搜索权限也是没有的,可以参考 path_resolution(7) |
EACCES/EPERM | 用户尝试连接到一个广播地址,却没有设置套接字的广播标记,或者请求被防火墙规则拦截了 |
EACCES | 如果开启了 SELinux 策略,也可能会导致连接被拒绝(比如策略规定 HTTP 代理只能连接到 HTTP 服务器关联的端口,而 HTTP 代理却连接了其他端口) |
EADDRINUSE | 本地地址已经在用了 |
EADDRNOTAVAIL | (网络域套接字)sockfd 指定的套接字没有绑定到地址,并且在尝试将其绑定到临时端口时,临时端口用尽了 |
EAFNOSUPPORT | 地址家族不正确 |
EAGAIN | 对于非阻塞的 UNIX 域套接字,套接字是非阻塞的,连接无法立即完成。对于其他套接字家族,这个错误标识路由缓存没有足够的条目了 |
EALREADY | 套接字是非阻塞的,并且之前的连接尝试还没有完成 |
EBADF | sockfd 不是一个打开的文件描述符 |
ECONNREFUSED | connect() 操作的流套接字发现没有人在监听对应的远程地址 |
EFAULT | 套接字结构地址超出用户地址空间 |
EINPROGRESS | 套接字是非阻塞的,连接不能立即完成。(UNIX 域套接字会返回 EAGAIN)。可以通过 select(2) 或者 poll(2) 查看套接字的可写事件,来确定连接完成。select(2) 指示可写后,使用 getsockopt(2) 来读取 SOL_SOCKET 级的 SO_ERROR 选项,来确定连接完全成功(SO_ERROR 为 0)或者未成功(SO_ERROR 为这里列出来的普通错误)。 |
EINTR | 系统调用被信号打断 |
EISCONN | 套接字已经连接 |
ENETUNREACH | 网络不可达 |
ENOTSOCK | 文件描述符并没有指向一个套接字 |
EPROTOTYPE | 该套接字不支持指定的通信协议。这个错误可能在出现在连接一个 UNIX 域报文套接字到一个流套接字 |
ETIMEDOUT | 连接超时。可能是服务器太忙了以至于无法接收新的连接。注意:当服务器开启 syncookies 时,IP 套接字的超时可能会非常长。 |
6.注意
如果 connect() 失败,那么套接字的状态是未知的。一个易于移植的程序应该关闭该套接字应该再创建一个新套接字,重新连接。
7.代码
这里我们展示下 select() 的用法示例,来将最近几篇内容串起来:
#include <stdio.h>#include <stdlib.h>#include <sys/ioctl.h>#include <sys/socket.h>#include <sys/time.h>#include <netinet/in.h>#include <errno.h>#define SERVER_PORT 12345#define TRUE 1#define FALSE 0main (int argc, char *argv[]){ int i, len, rc, on = 1; int listen_sd, max_sd, new_sd; int desc_ready, end_server = FALSE; int close_conn; char buffer[80]; struct sockaddr_in6 addr; struct timeval timeout; struct fd_set master_set, working_set; /*************************************************************/ /* Create an AF_INET6 stream socket to receive incoming */ /* connections on */ /*************************************************************/ listen_sd = socket(AF_INET6, SOCK_STREAM, 0); if (listen_sd < 0) { perror("socket() failed"); exit(-1); } /*************************************************************/ /* Allow socket descriptor to be reuseable */ /*************************************************************/ rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if (rc < 0) { perror("setsockopt() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Set socket to be nonblocking. All of the sockets for */ /* the incoming connections will also be nonblocking since */ /* they will inherit that state from the listening socket. */ /*************************************************************/ rc = ioctl(listen_sd, FIONBIO, (char *)&on); if (rc < 0) { perror("ioctl() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Bind the socket */ /*************************************************************/ memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any)); addr.sin6_port = htons(SERVER_PORT); rc = bind(listen_sd, (struct sockaddr *)&addr, sizeof(addr)); if (rc < 0) { perror("bind() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Set the listen back log */ /*************************************************************/ rc = listen(listen_sd, 32); if (rc < 0) { perror("listen() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Initialize the master fd_set */ /*************************************************************/ FD_ZERO(&master_set); max_sd = listen_sd; FD_SET(listen_sd, &master_set); /*************************************************************/ /* Initialize the timeval struct to 3 minutes. If no */ /* activity after 3 minutes this program will end. */ /*************************************************************/ timeout.tv_sec = 3 * 60; timeout.tv_usec = 0; /*************************************************************/ /* Loop waiting for incoming connects or for incoming data */ /* on any of the connected sockets. */ /*************************************************************/ do { /**********************************************************/ /* Copy the master fd_set over to the working fd_set. */ /**********************************************************/ memcpy(&working_set, &master_set, sizeof(master_set)); /**********************************************************/ /* Call select() and wait 3 minutes for it to complete. */ /**********************************************************/ printf("Waiting on select()...\n"); rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout); /**********************************************************/ /* Check to see if the select call failed. */ /**********************************************************/ if (rc < 0) { perror(" select() failed"); break; } /**********************************************************/ /* Check to see if the 3 minute time out expired. */ /**********************************************************/ if (rc == 0) { printf(" select() timed out. End program.\n"); break; } /**********************************************************/ /* One or more descriptors are readable. Need to */ /* determine which ones they are. */ /**********************************************************/ desc_ready = rc; for (i=0; i <= max_sd && desc_ready > 0; ++i) { /*******************************************************/ /* Check to see if this descriptor is ready */ /*******************************************************/ if (FD_ISSET(i, &working_set)) { /****************************************************/ /* A descriptor was found that was readable - one */ /* less has to be looked for. This is being done */ /* so that we can stop looking at the working set */ /* once we have found all of the descriptors that */ /* were ready. */ /****************************************************/ desc_ready -= 1; /****************************************************/ /* Check to see if this is the listening socket */ /****************************************************/ if (i == listen_sd) { printf(" Listening socket is readable\n"); /*************************************************/ /* Accept all incoming connections that are */ /* queued up on the listening socket before we */ /* loop back and call select again. */ /*************************************************/ do { /**********************************************/ /* Accept each incoming connection. If */ /* accept fails with EWOULDBLOCK, then we */ /* have accepted all of them. Any other */ /* failure on accept will cause us to end the */ /* server. */ /**********************************************/ new_sd = accept(listen_sd, NULL, NULL); if (new_sd < 0) { if (errno != EWOULDBLOCK) { perror(" accept() failed"); end_server = TRUE; } break; } /**********************************************/ /* Add the new incoming connection to the */ /* master read set */ /**********************************************/ printf(" New incoming connection - %d\n", new_sd); FD_SET(new_sd, &master_set); if (new_sd > max_sd) max_sd = new_sd; /**********************************************/ /* Loop back up and accept another incoming */ /* connection */ /**********************************************/ } while (new_sd != -1); } /****************************************************/ /* This is not the listening socket, therefore an */ /* existing connection must be readable */ /****************************************************/ else { printf(" Descriptor %d is readable\n", i); close_conn = FALSE; /*************************************************/ /* Receive all incoming data on this socket */ /* before we loop back and call select again. */ /*************************************************/ do { /**********************************************/ /* Receive data on this connection until the */ /* recv fails with EWOULDBLOCK. If any other */ /* failure occurs, we will close the */ /* connection. */ /**********************************************/ rc = recv(i, buffer, sizeof(buffer), 0); if (rc < 0) { if (errno != EWOULDBLOCK) { perror(" recv() failed"); close_conn = TRUE; } break; } /**********************************************/ /* Check to see if the connection has been */ /* closed by the client */ /**********************************************/ if (rc == 0) { printf(" Connection closed\n"); close_conn = TRUE; break; } /**********************************************/ /* Data was received */ /**********************************************/ len = rc; printf(" %d bytes received\n", len); /**********************************************/ /* Echo the data back to the client */ /**********************************************/ rc = send(i, buffer, len, 0); if (rc < 0) { perror(" send() failed"); close_conn = TRUE; break; } } while (TRUE); /*************************************************/ /* If the close_conn flag was turned on, we need */ /* to clean up this active connection. This */ /* clean up process includes removing the */ /* descriptor from the master set and */ /* determining the new maximum descriptor value */ /* based on the bits that are still turned on in */ /* the master set. */ /*************************************************/ if (close_conn) { close(i); FD_CLR(i, &master_set); if (i == max_sd) { while (FD_ISSET(max_sd, &master_set) == FALSE) max_sd -= 1; } } } /* End of existing connection is readable */ } /* End of if (FD_ISSET(i, &working_set)) */ } /* End of loop through selectable descriptors */ } while (end_server == FALSE); /*************************************************************/ /* Clean up all of the sockets that are open */ /*************************************************************/ for (i=0; i <= max_sd; ++i) { if (FD_ISSET(i, &master_set)) close(i); }}
下一篇 【计算机网络】网络编程接口 Socket API 解读(6)