网络编程之socket套接字详解

目录:

  1. socket
  2. bind
  3. listen
  4. connect
  5. accept

1. socket:

(1)函数原型:

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

(2)参数:

在这里插入图片描述

socket函数用于创建一个套接字,这是在应用层调用的,所以应用程序需要在调用socket创建套接字时根据应用层的业务类型,指定它所希望采用的网络层、传输层所使用的的协议。

内核中套接字是一层一层进行抽象展示的,把共性的东西抽取出来,这样对外提供的接口可以尽量统一。

内核中把套接字的定义会抽象出来展示,如:struct sock -> struct inet_sock -> struct tcp_sock 从抽象到具体.

socket函数的这三个参数其实就是把抽象的socket具体化的条件,domain参数决定图中所示的第二层通信域type决定了第三层的通信模式protocol决定了第四层真正的通信协议

domain 参数的几个常用值:

AF_UNIX / AF_LOCAL : 	local communication, 本地通信
AF_INET : 				IPv4 网络通信
AF_INET6 : 				IPv6 网络通信

type参数的几个常用值:

SOCK_STREAM :	流套接字:提供序列化的、可靠的、双工的、基于连接的字节流式服务
SOCK_DGRAM :	数据报套接字:提供数据报式的服务(无连接、不可靠、有最大长度限制)
SOCK_RAW :  	原始套接字:提供原始套接字服务

此外,type还可以设置以下几种类型:

SOCK_NONBLOCK :		设置为非阻塞式IO,functl(O_NONBLOCK) 是一样的效果
SOCK_CLOEXEC :open(O_CLOEXEC)一样,open打开时关闭文件描述符,防止父进程泄漏打开的文件给子进程

protocol 参数

确定socket到底支持哪个协议,例如如果domain=AF_INET, type = SOCK_STREAM,那么内核就会自动设置Protocol = IPPROTO_TCP;
如果domain = AF_INET, type = SOCK_DGRAM, 则 protocol = IPPROTO_UDP

socket(AF_INET, SOCK_STREAM, 0);   //等价于 :
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

socket(AF_INET, SOCK_DGRAM, 0);   //等价于:
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

(3)返回值:

成功时返回sockfd套接字描述符,失败返回-1,并设置errno。

常见的errno:

EACCES: 没有权限创建sockfd套接字
EINVAL: 定的协议错误

2. bind:

(1)bind 函数原型:

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

使用举例:

struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET, "192.254.1.16", &(servaddr.sin_addr.s_addr));
servaddr.sin_port = htons(65535);

bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));

(2)bind 参数:

socket函数创建了一个sockfd套接字描述符,想要进行网络通信还要将套接字与端口号、IP地址绑定起来(端口号+IP地址用来描述TCP连接的一端),Unix中用套接字地址结构这样的一个结构体在内核与应用程序之间传递IP地址和端口号:

服务端(客户端)应用进程将自己想要绑定(连接)的IP地址和端口后写进套接字地址结构这样的结构体中,然后传递给内核;服务端内核在接收到新的客户端连接后将客户端的IP地址、端口号通过此结构体传递给应用进程。

另外,由于历史原因,因为套接字地址结构是在ANSI C之前定义的,当时函数还没有 (void *)这样的通配参数类型,所以类似于bind的函数如果想要接受 IPv4、IPv6等多种类型的地址结构结构体,就要有一个通配的结构体类型:因此而产生了通用套接字地址结构

IPv4 套接字地址结构:

#include <netinet/in.h>

struct sockadrr_in {
    
    
	uint8_t			sin_len;			//length of structure(16)
	
	sa_family_t 	sin_family;			//AF_INET
	in_port_t		sin_prot;			//`16` bit TCP or UDP port number
	struct in_addr 	sin_addr;			//`32` bit IPv4 Address
	
	char 			sin_zero[8];		//unused
};

struct in_addr {
    
    
	in_addr_t 		s_addr;
};

通用套接字地址结构:

#include <sys/socket.h>

struct sockaddr {
    
    
	uint8_t			sa_len;
	sa_family_t		sa_family;
	char			sa_data[14];
};

字节序转换函数:

主机字节序可能与网络字节序不相同,因此在传入地址、端口号等时需要先进行字节序转换:

#include <netinet/in.h>

unit16_t	htons(uint16_t val);	//将16 bit位的整数从主机序转换为网络序,返回值为网络序
uint32_t 	htonl(uint32_t val);

uint16_t	ntohs(uint16_t val);
uint32_t	ntohl(uint32_t val);

地址转换函数:

由于网络IP地址的表达方式有多种(ASCII字符串、网络字节序的二进制),因此需要转换。

#include <arpa/inet.h>

int inet_pton(int family, const char *strptr, void *addrptr);
	//成功则返回1,格式无效返回0,失败返回-1
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
	//成功则返回指向结果的指针,失败返回NULL

(3)bind 返回值:

成功返回0,失败返回-1,并设置errno

常见的errno:

EADDRINUSE: 	Address already in use,所以要绑定的地址已使用

3. listen:

(1)listen 函数原型:

#include <sys/socket.h>

int listen(int sockfd, int backlog);

(2)listen 参数:

backlog:

内核为一个监听套接字维护两个队列:(注意一定是监听套接字,服务端等待连接的套接字)

  1. SYN队列:
    在三次握手中,收到了客户端的第一个SYN,此时连接放入SYN队列中(未完成连接队列);
  2. ACCEPT队列:
    在三次握手中,收到了客户端的第三个ACK,此时连接从SYN队列中取出,放入到ACCEPT队列中。服务端调用accept,就是从ACCEPT队列中取出一个已完成连接。

backlog到底指的是哪个队列的大小?

在《TCP/IP详解(卷一)》中这样描述:

SYN队列的大小由内核参数net.ipv4.tcp_max_syn_backlog决定(默认1000);
ACCEPT队列取 min(backlog, net.core.someconn=128)的最小值来决定。

也就是说listen函数的backlog参数是用来配置ACCEPT全连接队列的,但是ACCEPT队列同时要受另一个内核参数的限制。

(3)listen 返回值:

成功返回0,失败返回-1,并设置errno

常见的errno:

EADDRINUSE: Another socket is already listening on the same port. 另一个套接字已经在监听这个端口
ENOTSOCK: The file descriptor sockfd does not refer to a socket. 传入的fd文件描述符不是套接字

4. connect:

(1)connect 函数原型:

#include <sys/socket.h>
#include <sys/types.h>

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

(2)connect 参数:

(3)connect 返回值:

成功返回0,失败返回-1,并设置errno

常见的errno:

EINPROGRESS: sockfd为非阻塞,且数据未就绪时connect返回(待补充)

ECONNREFUSED: 	No-one listening on the remote address. 所连接的IP地址上没有套接字监听

ETIMEOUT: 	Timeout while attempting connection. The server may be too busy to accept new connections. 连接超时(TCP三次握手超时,可能是服务器繁忙没有响应)

5. accept:

(1)accept 函数原型:

#include <sys/socket.h>

int accept(int listenfd, struct sockaddr *cliaddr, socklen_t *addrlen);

(2)accept 参数:

(3)accept 返回值:

成功返回非负描述符(connfd),失败返回-1,并设置errno

常见的errno:
(待补充)


参考内容:

socket函数的domain、type、protocol解析

猜你喜欢

转载自blog.csdn.net/ArtAndLife/article/details/111322670