5、bind
函数结构:
int bind(SOCKET s,
const struct sockaddr FAR * name,
int namelen)
参数解析:
SOCKET 要绑定的套间字
name 绑定的地址
namelen 地址长度
扩展:
sockaddr:用来指定IP地址和服务端口信息
struct sockaddr_in
{
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
}
struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long S_addr;
} S_un;
#define s_addr S_un.S_addr
/* can be used for most tcp & ip code */
#define s_host S_un.S_un_b.s_b2
/* host on imp */
#define s_net S_un.S_un_b.s_b1
/* network */
#define s_imp S_un.S_un_w.s_w2
/* imp */
#define s_impno S_un.S_un_b.s_b4
/* imp # */
#define s_lh S_un.S_un_b.s_b3
/* logical host */
};
绑定地址都为本地的IP地址,绑定接听过的端口再次绑定出错,可以有setsockopt把socket属性改成SO_REUSEADDR就可以了
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, ..),将在下面的条例中进一步讲解
6、listen
函数结构:
int listen(SOCKET s,
int backlog)
参数解析:
s: 监听的套间字
backlog: 最大监听连接数(最大长度128)
工作原理:
backlog是侦听队列的长度,在内核函数中,首先对backlog作检查,如果大于128,则强制使其等于128。接下来要检查结构体struct sock的成员sk_state,即当前socket的状态,如果不为TCP_LISTEN,则开始启动端口侦听。启动端口侦听首先要为结构体struct inet_connection_sock(它是struc sock的扩展,表示一个面向连接的socket)的成员icsk_accept_queue分配内存,icsk_accept_queue的类型是struct request_sock_queue,定义如下:
struct request_sock_queue {
struct request_sock *rskq_accept_head;
struct request_sock *rskq_accept_tail;
rwlock_t syn_wait_lock;
u8 rskq_defer_accept;
struct listen_sock *listen_opt;
};
tcp socket在侦听的时候,那些来自其它主机的tcp socket的连接请求一旦被接受(完成三次握手协议),便会建立一个request_sock,建立与请求socket之间的一个tcp连接。该request_sock会被放在一个先进先出的队列中,等待accept系统调用的处理。但上面的结构体中好像并没有可以存放request_sock的地方,下面是结构体struct listen_sock的定义:
struct listen_sock {
u8 max_qlen_log;
int qlen;
int qlen_young;
int clock_hand;
u32 hash_rnd;
u32 nr_table_entries;
struct request_sock *syn_table[0];
};
新建立的request_sock就存放在syn_table中。这是一个哈希数组,总共有nr_table_entries项。实际上在分配内存时,分配的大小是TCP_SYNQ_HSIZE(512)项。成员nr_table_entries的值是512。成员max_qlen_log以2的对数的形式表示request_sock队列的最大值。哈希表有512项,但队列的最大值的取值是1024。即max_qlen_log的值为10。qlen是队列的当前长度。hash_rnd是一个随机数,计算哈希值用,结构体struct request_sock_queue中的rskq_accept_head和rskq_accept_tail分别指向request_sock队列的队列头和队列尾。
为struct inet_connection_sock分配完内存后,继续处理,结构体struct sock有两个成员sk_ack_backlog和sk_max_ack_backlog。sk_ack_backlog表示该侦听socket上,当前连向该socket,但是还没有完成三次握手协议的socket的数量,即还在连接过程中的socket的数量。初始值为0,sk_max_ack_backlog为该数量的最大值,也就是listen系统调用的第二个参数,即侦听队列的长度,它的真正含义是:侦听socket能处理的最大并发连接请求数,其最大取值为128。
到这里,把socket的状态改为TCP_LISTEN,进入侦听状态。然后独占端口,使socket进入mytcp_hashinfo哈希表集中的listening_hash表。侦听建立完成。
由于侦听socket始终在系统中进行侦听工作,所以在进程结束时,还必须显式结束侦听,进行相应的清理工作
7、accept/WASAccept
函数结构:
SOCKET accept(SOCKET s,
struct sockaddr FAR* addr,
int FAR * len)
函数作用:
接收客户端socket的连接
参数解析:
s: 监听的套间字
addr:建立连接的socket地址
len:地址长度
返回值: 新的socket套间字,新的连接socket
工作原理:
accept系统调用从listen系统调用接受的连接的队列中(struct inet_connection_sock->isck_accept_queue.listen_sock->syn_table)取一个连接,并建立一个新的socket,把这个连接(struct request_sock)赋给这个新的sokcet,此后,该连接就由这个新的socket负责与对端进行通讯。
SOCKET WASAccept(SOCKET s,
struct sockaddr FAR* addr,
LPINT addrlen,
LPCONDITIONPROC lpfnCondition,
DWORD dwCallbackData)
与accept函数相比多了,lpfncondition 函数指针,既是一个回调函数,个人认为是来处理接受到的SOCKET是否需要
int CALLBACK ConditionFunc(LPWSABUF lpCallerID,
LPWSABUF lpCallerData,
LPQOS lpSQOS,
LPQOS lpGQOS,
LPWSABUF lpCalleeID,
LPWSABUF lpCalleeData,
GROUP FAR * g,
DWORD dwCallbackData
)
返回值:CF_ACCEPT CF_REJECT
8、connect/WSAConnect
函数结构:
int connect(SOCKET s,
const struct sockaddr FAR* name,
int namelen)
参数解析:
s:连接绑定的socket
name:连接的地址
namelen:地址长度
原理:
WSAConnect(SOCKET s,
const struct sockaddr FAR* name,
int namelen,
LPWSABUF lpCallerData,
LPWSABUF lpCalleeData,
LPQOS lpSQOS,
LPQOS lpGQOS)
lpCallerData:指向向服务器发出的请求连接数据
lpCalleeData:指向服务器向客户机返回连接时的数据
9、send/WSASend
函数结构:
int send(SOCKET s,
const char FAR * buf,
int len,
int flags)
参数解析:
s: 发送地址
buf:发送数据
len:发送数据长度
flags:MSG_DONTROUTE(Specifies that the data should not be subject to routing. A Windows Sockets service provider can choose to ignore this flag.),
MSG_OOB(Sends OOB data (stream-style socket such as SOCK_STREAM only.)
原理:
当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的 长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于
s的发送缓冲区的长度,那么send先检查协议 是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者
s的发送缓冲中没有数据,那么 send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于
剩余 空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据
copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;
如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,
但是此时这些数据并不一定马上被传到连接的另一端。如 果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。
(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回
SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
WSASend(SOCKET s,
LPWSBU lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
KOWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE
)
lpBuffers:发送的结构指针
dwBufferCount:结构数
lpNumberOfBytesSent:写入的字节数
lpOverlapped 、lpCompletionROUTINE 用户重叠IO操作
10、recv/WSARecv
函数结构:
int recv(SOCKET s,
char FAR * buf,
int len,
int flags)
参数解析:
s : 接收信息来源的socket
buf:数据
len:数据长度
flags:标志(一般为0)
原理:
当应用程序调用recv函数时,recv先等待s的发送缓冲 中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时
出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数 据或者数据被协议成功发送完毕后,recv先
检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到 协议把数据
接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf
的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的
接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;
如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,
进程对该信号的默认处理是进程终止。
int WSARecv(SOCKET s,
LPWSBU lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecv,
DWORD dwFlags,
KOWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE)
与WSASend刚好相反
WSARecvDisconnect(SOCKET s, LPWASBUF lpInboundDisconnectData)
10、shutdown
函数结构:
int shutdown(SOCKET s, int how)
参数解析
s: 关闭的socket
how:SD_RECEIVE 表示不允许接受数据,对底层协议没有影响
SD_SEND 表示不允许发送数据
SD_BOTH 两者皆是
对于TCP来说全部抛弃重新连接,而UDP任然接收
11、closesocket
函数结构
int closesocket(SOCKET s)
函数作用:
释放套间字描述