①socket()系统调用声明
int socket(int domain,int type,int protocol);
domain告诉系统使用哪个底层的协议族:
PF_INET:IPV4
PF_INET6:IPV6
type参数指定服务类型
SOCK_STREAM服务(流服务)–TCP
SOCK_UGRAM服务(数据报)服务–UDP
protocal参数是在前两个参数构成的协议集合下,再选择一个具体的协议,几乎所有情况下我们把它设置为0,表示默认协议
------------------------------------------------------------
②bind()系统调用的声明
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);
bind成功时候返回0,失败返回-1,并设置errno
最常见的两种errno是EACCES 和 EADDRINUSE
EASSES:被绑定的地址是受保护的地址,仅超级用户可以访问。比如普通用户将一个socket绑定到知名服务端口(端口为0-1023)上时,bing将返回ESACCES错误。
EADDRINUSE:被绑定的端口正在使用中,比如将一个socket绑定到一个TIME_WAIT状态的socket地址。
--------------------------------------------------------------
③listen()系统调用声明:
int listen(int sockfd,int backlog);
backlog参数提示内核监听队列的最大长度,监听队列的长度如果超过backlog,则服务器将不再受理新的客户连接,客户端也将受到ECONNREFUSED错误信息
在LINUX内核2.2版本之前,backlog参数指的是处于所有半连接(SYN_RCVD)和完全连接状态(ESTABLISHED)的socket的上限,但自从内核2.2之后它只表示处于完全连接的socket的上限,处于半连接状态的socket上限则由/proc/sys/net/i[v4/tcp_max_syn_backlog内核参数指定。backlog的典型值为5
假设backlog值设置为5,在程序运行后会发现会有>=5的连接连接上,也就是处于ESTABLISHED状态,但不会很多,一般是backlog+1的值,而其他的连接则会处于SYN_RCVD状态,在不同的系统上会有不同 的值,不过监听队列中完整的连接上限通常比backlog值大
listen成功返回0,失败返回-1,并设置errno。
---------------------------------------------------------------
④accept()系统调用
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
accept成功时返回一个新的连接socket,该socket唯一的标识了这个连接,服务器可以通过对该socket的读写完成与客户端的通信
考虑如下情况:
如果监听队列中处于ESTABLISHED状态的连接对应的客户端出现了网络异常(比如)掉线,或者提前退出,那么服务器对这个连接执行的accept调用是否会成功呢?我们可以用如下代码测试一下。
#include <sys/socket.h>
#include <netinet.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[])
{
if(argc<=2)
{
printf("usage:%s ip_address port_number\n",basename(argv[0]));
}
const char *ip=argv[1];
int port=atoi(argv[2]);
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port=htons(port);
int sock=socket(AF_INET,SOCK_STREAM,0);
assert(sock>=0);
int ret=bind(sock,(struct sockaddr *)&address,sizeof(address));
assert(ret!=-1);
ret=listen(sock,5);
assert(ret!=-1);
/*暂停20s以等待客户端连接和相关操作(掉线或者退出完成)*/
sleep(20);
struct sockaddr_in client;
socklen_t client_addrlength=sizeof(client);
int connfd=accept(sock,(struct sockaddr*)&client,&client_addrlength);
if(connfd<0)
{
printf("errno is :%d\n",errno);
}
else
{
/* connect success ,print ip & port of client */
char remote[INET_ADDRSTRLEN];
printf("connectef with ip:%s and port %d\n",inet_ntop(AF_INET,&client.sin_addr,remote,INET_ADDRSTRLEN),ntohs(client.sin_port));
close(connfd);
}
close(sock);
return 0;
}
!!!在客户端连接上后,20s内断开连接,我们会发现accept正常返回!!!
结论:accept调用对于客户端的网络断开与否毫不知情
我们重复此过程,不过这次我们不是断开客户端的网络,而是在建立连接后直接退出客户端程序,实验结构accept调用同样正常返回。
结论:由此可见,accept只是从监听队列中取出连接,而不论连接处于何种状态,更不关心任何网络状况的变化。
---------------------------------------------------------------
⑤close()系统调用
int close(int fd);
**fd是待关闭的socket,不过,close系统调用并非总是立即关闭一个连接,而是将fd的 引用计数 减1,只有当fd的引用计数为0时,在真正的关闭,在多进程程序中,一次fork系统调用默认将使父进程中打开的socket的引用计数加1,因此我们必须在父进程和子进程中都执行close才能将连接关闭
如果无论如何都要立即终止连接(而不是将引用计数-1)可以使用如下的shutdown系统调用。
int shutdown(int sockfd,int howto);
sockfd是指待关闭的sockfd。
howto参数指的是shutdonw的行为,
它可以取下表中的行为某个值
可选值 | 含义 |
---|---|
SHUT_RD | 关闭sockfd上读的这一半 |
SHUT_WR | 关闭sockfd上写的这一半 |
SHUT_RDWR | 同时关闭sockfd上的读和写 |
shutdown系统调用的优点:可以关闭sockfd上的读或者写或者都关闭,而close只能将读和写都关闭
shutdown成功返回0,失败返回-1,并设置errno。