socket基础API(socket,bind,listen,accept,close)深入解析

①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是EACCESEADDRINUSE

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。

发布了46 篇原创文章 · 获赞 0 · 访问量 453

猜你喜欢

转载自blog.csdn.net/weixin_42226134/article/details/104302894