本篇文章的所有例子,基于RHEL6.5平台(linux kernal: 2.6.32-431.el6.i686)。
socket一词的起源:
在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:“命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”计算机历史博物馆补充道:“这比BSD的套接字接口定义早了大约12年。”
前面已经提到socket也可以认为是一种文件操作,符合"open->write/read->close”模式,则socket应该提供了这些操作对应的函数接口。下面已TCP为例,介绍常用的sockt接口函数。
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol);socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
· domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用IPv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
· type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET、SOCK_RDM等等。
· protocol:顾名思义,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:上面的type和protocol并不是可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。当我们调用socket创建一个socket时,返回的socket描述符存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
#include <sys/types.h> #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数sockfd:
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
typedef unsigned short sa_family_t; struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ //2字节 char sa_data[14]; /* 14 bytes of protocol address */ //14字节 }
/* Structure describing an Internet socket address. */ struct sockaddr_in { __SOCKADDR_COMMON (sin_); //2字节 in_port_t sin_port; /* Port number. */ //2字节 struct in_addr sin_addr; /* Internet address. */ //4字节 /* Pad to size of `struct sockaddr'. */ //8字节 unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; //上面的字符数组sin_zero[8]的存在是为了保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等 };其中in_port_t类型的具体定位为:
/* Type to represent a port. */ typedef uint16_t in_port_t;而struct in_addr其实就是32位IP地址。
/* Internet address. */ typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; /* address in network byte order */ };
int sockfd; struct sockaddr_in my_addr; sockfd = socket(AF_INET, SOCK_STREAM, 0); my_addr.sin_family = AF_INET; /* 主机字节序 */ my_addr.sin_port = htons(MYPORT); /* short, 网络字节序 */ my_addr.sin_addr.s_addr = inet_addr("192.168.0.1"); bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */ //memset(&my_addr.sin_zero, 0, 8); //强制转换sockaddr_in为sockaddr bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
/* Ditto, for IPv6. */ struct sockaddr_in6 { __SOCKADDR_COMMON (sin6_); //2字节 in_port_t sin6_port; /* Transport layer port # */ //2字节 uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* IPv6 scope-id */ };其中,struct in6_addr定义如下:
struct in6_addr { unsigned char s6_addr[16]; /* IPv6 address */ };
#define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; /* AF_UNIX */ char sun_path[UNIX_PATH_MAX]; /* pathname */ };
网络字节序:4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。
所以:在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian,否则会导致很多莫名其妙的问题。所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。
#include <sys/types.h> #include <sys/socket.h> int listen(int sockfd, int backlog);man手册中对这两个参数的解释如下:
a).backlog是等待连接队列的最大长度。例如,如果将backlog设置为10,则当有15个客户端连接请求的时候,前面10个连接请求就被放置在请求队列中,后面5个请求被拒绝。
b).当链接(connect)完成时,会从这个连接队列中移出一个(accept函数实现)。
c).当连接队列已满时,客户端会收到连接错误的返回值,或者如果底层网络支持重传的话,则服务端会忽略这个请求,依靠后续的重传来建立连接。
众所周知,TCP连接过程可以分为3次握手。服务端为了维护连接过程中的这些状态,需要开辟两个队列来存放这两个中间状态。一个叫“半连接队列”用来存放“SYN RECVD”状态的连接,一个叫“完整连接队列”用来存放“ESTABLISHED”状态的tcp连接。当处于“SYN RECVD”队列的连接,收到了客户端的ACK响应包的时候,这个连接会从“SYN RECVD”队列删除,并添加到”ESTABLISHED“队列的末尾,这两个队列大小的总和就是由backlog参数来决定的。当调用listen函数之后调用accept函数,其实是从“完整连接队列”里面取一个连接出来建立真正的tcp连接。
两个队列的总大小(backlog)在linux系统是有限制的,具体可以看cat /proc/sys/net/core/somaxconn,默认值是128。
另外,listen函数的man手册中,对backlog有下面的这段说明:
The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue
for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. When syncookies are enabled there is no logical maximum length and this setting is ignored. See tcp(7) for more information.
在linux2.2之前,backlog在TCP中的行为有所改变。现在它用来表示完成了3次握手操作,状态已经变为了ESTABLISHED的队列长度,等待accept函数调用它。而以前是用来表示尚未完成连接的个数,即处于SYN RECVD状态。
对于尚未完成3次握手的连接的队列长度,可以使用/proc/sys/net/ipv4/tcp_max_syn_backlog来进行设置。默认值是512。
当syncookies设置为enable时,则 不会存在这种逻辑上的最大长度的限制。backlog这个配置项会被忽略掉。
#include <sys/types.h> #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int flags = fcntl(sockfd, F_GETFL, 0); flags |= O_NONBLOCK; fcntl(sockfd, F_SETFL, flags); int result = connect(sockfd, saptr, salen); if (result == -1) { if (errno != EINPROGRESS) return fail; //else: The socket is non-blocking and the connection cannot be completed immediately } if (result == 0) return succ;步骤2:判断可读和可写
fd_set wfd; FD_ZERO(&wfd); FD_SET(fd, &wfd); if(select(sockfd+1, NULL, &wfd, NULL, timeoutptr) == -1) { close(fd); return fail; }
步骤3:使用getsockopt 函数检查错误
getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
在sockfd 都是可读和可写的情况下,我们使用 getsockopt 来检查连接是否出错。但这里有一个可移植性的问题。
如果发生错误,getsockopt 源自 Berkeley 的实现将在变量 error 中返回错误,getsockopt 本身返回0;然而 Solaris 却让 getsockopt 返回 -1,并把错误保存在 errno 变量中。所以在判断是否有错误的时候,要处理这两种情况。
int error=0, result=0; if (FD_ISSET(sockfd, &wfd)) { socklen_t length = sizeof(error); result = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &length); if (result < 0 || error) { close(sockfd); if (error) { errno = err; return fail; } } }
原文地址:http://blog.csdn.net/shltsh/article/details/46586711