一. 简述

socket函数用于创建一个新的socket,也就是向系统申请一个socket资源。socket函数用户客户端和服务端。
函数声明:
int socket(int domain, int type, int protocol);
参数说明:
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等。流式socket(SOCK_STREAM)是一种面向连接的socket,针对于面向连接的TCP服务应用。数据报式socket(SOCK_DGRAM)是一种无连接的socket,对应于无连接的UDP服务应用。
protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
说了一大堆废话,第一个参数只能填AF_INET,第二个参数只能填SOCK_STREAM,第三个参数只能填0。
除非系统资料耗尽,socket函数一般不会返回失败。
返回值:成功则返回一个socket,失败返回-1,错误原因存于errno 中。

协议族

我们主要关心 AF_INET

  1. Name Purpose Man page
  2. AF_UNIX, AF_LOCAL Local communication unix(7)
  3. AF_INET IPv4 Internet protocols ip(7)
  4. AF_INET6 IPv6 Internet protocols ipv6(7)
  5. AF_IPX IPX - Novell protocols
  6. AF_NETLINK Kernel user interface device netlink(7)
  7. AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
  8. AF_AX25 Amateur radio AX.25 protocol
  9. AF_ATMPVC Access to raw ATM PVCs
  10. AF_APPLETALK AppleTalk ddp(7)
  11. AF_PACKET Low level packet interface packet(7)
  12. AF_ALG Interface to kernel crypto API

socket类型

  1. SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.
  2. SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
  3. SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each
  4. input system call.
  5. SOCK_RAW Provides raw network protocol access.
  6. SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
  7. SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).

主要是SOCK_STREAM,SOCK_DGRAM

指定协议

常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

我们填 0 , 就是用TCP协议

返回值

RETURN VALUE On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.

返回的是文件描述符
正常返回 3
gdb时返回 7

一个服务端能跑多少个socket

冲个2000
image.png
不过我在vscode的终端就可以跑完
image.png
这个跟 Linux对打开文件数量的限制 有关

image.png

其他文章

在内核中为什么要有struct socket结构体呢?
struct socket结构体的作用是什么?
下面这个图,我觉得可以回答以上两个问题。

由这个图可知,内核中的进程可以通过使用struct socket结构体来访问linux内核中的网络系统中的传输层、网络层、数据链路层。也可以说struct socket是内核中的进程与内核中的网路系统的桥梁。

  1. struct socket
  2. {
  3. socket_state state; // socket state
  4. short type ; // socket type
  5. unsigned long flags; // socket flags
  6. struct fasync_struct *fasync_list;
  7. wait_queue_head_t wait;
  8. struct file *file;
  9. struct sock *sock; // socket在网络层的表示;
  10. const struct proto_ops *ops;
  11. }
  12. struct socket结构体的类型
  13. enum sock_type
  14. {
  15. SOCK_STREAM = 1, // 用于与TCP层中的tcp协议数据的struct socket
  16. SOCK_DGRAM = 2, //用于与TCP层中的udp协议数据的struct socket
  17. SOCK_RAW = 3, // raw struct socket
  18. SOCK_RDM = 4, //可靠传输消息的struct socket
  19. SOCK_SEQPACKET = 5,// sequential packet socket
  20. SOCK_DCCP = 6,
  21. SOCK_PACKET = 10, //从dev level中获取数据包的socket
  22. };
  23. struct socket 中的flags字段取值:
  24. #define SOCK_ASYNC_NOSPACE 0
  25. #define SOCK_ASYNC_WAITDATA 1
  26. #define SOCK_NOSPACE 2
  27. #define SOCK_PASSCRED 3
  28. #define SOCK_PASSSEC 4


我们知道在TCP层中使用两个协议:tcp协议和udp协议。而在将TCP层中的数据往下传输时,要使用网络层的协议,而网络层的协议很多,不同的网络使用不同的网络层协议。我们常用的因特网中,网络层使用的是IPV4和IPV6协议。

所以在内核中的进程在使用struct socket提取内核网络系统中的数据时,不光要指明struct socket的类型(用于说明是提取TCP层中tcp协议负载的数据,还是udp层负载的数据),还要指明网络层的协议类型(网络层的协议用于负载TCP层中的数据)。
linux内核中的网络系统中的网络层的协议,在linux中被称为address family(地址簇,通常以AF_XXX表示)或protocol family(协议簇,通常以PF_XXX表示)。


socket详解 - 图4
socket详解 - 图5

1.创建一个struct socket结构体:
int sock_create(int family, int type, int protocol,
struct socket res);
int sock_create_kern(int family, int type, int protocol,
struct socket res);
EXPROT_SYMBOL(sock_create);
EXPROT_SYMBOL(sock_create_kern);
family : 指定协议簇的类型,其值为:PF_XXX或 AF_XXX
type : 指定要创建的struct socket结构体的类型;
protocol : 一般为0;
res : 中存放创建的struct socket结构体的地址;

  1. int sock_create(int family, int type, int protocol, struct socket **res)
  2. {
  3. return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
  4. }
  5. int sock_create_kern(int family, int type, int protocol, struct socket **res)
  6. {
  7. return __sock_create( &init_net, family, type, protocot, res, 1 );
  8. }
  9. 如果在内核中创建struct socket时,推荐使用sock_create_kern()函数;
  10. // 网络协议簇结构体
  11. struct net_proto_family
  12. {
  13. int family ; // 协议簇
  14. int (*create)(struct net *net, struct socket *sock, int protocol);
  15. struct module *owner;
  16. };
  17. 内核中的所有的网络协议的响应的网络协议簇结构体都存放在 net_families[]指针数组中;
  18. static struct net_proto_family *net_families[NPROTO];
  19. static int __sock_create(struct net *net, int family, int type, int protocol,
  20. struct socket **res, int kern )
  21. {
  22. struct socket *sock;
  23. struct net_proto_family *pf;
  24. sock = sock_alloc();//分配一个struct socket 结构体
  25. sock->type = type;
  26. pf = rcu_dereference(net_families[family]); //获取相应的网络协议簇结构体的地址;
  27. pf->create(net, sock, protocol); // 对struct socket结构体做相应的处理;
  28. *res = sock; // res中保存创建的struct socket结构体的地址;
  29. return 0;
  30. }
  31. struct socket_alloc
  32. {
  33. struct socket socket ;
  34. struct inode vfs_node ;
  35. }
  36. static inline struct socket *SOCKET_I(struct inode *inode)
  37. {
  38. return &contain_of(inode, struct socket_alloc, vfs->node)->socket;
  39. }
  40. static struct socket *sock_alloc(void)
  41. {
  42. struct inode *inode;
  43. struct socket *sock;
  44. inode = new_inode(sock_mnt->mnt_sb);//分配一个新的struct inode节点
  45. sock = SOCKET_I(inode);
  46. inode->i_mode = S_IFSOCK | S_IRWXUGO;//设置inode节点的权限
  47. inode->i_uid = current_fsuid(); // 设置节点的UID
  48. inode->i_gid = current_fsgid(); //设置节点的GID
  49. return sock;
  50. }

有以上的代码可知:linux内核在使用sock_create()、sock_create_kern()
进行struct socket结构体的创建时,其本质是分配了一个struct socket_alloc
结构体,而这个struct socket_alloc结构体中包含了struct socket 和struct
inode(struct inode结构体,是linux内核用来刻画一个存放在内存中的文件的,通过将struct inode 和 struct socket绑定在一起形成struct socket_alloc结构体,来表示内核中的网络文件)。然后对分配的struct socket结构体进行初始化,来定义内核中的网络文件的类型(family, type, protocol).

在linux网络系统中还有两个非常重要的套接字地址结构体:
struct sockaddr_in
struct sockaddr;

  1. typedef unsigned short sa_family_t;
  2. // Internet Address
  3. struct in_addr{
  4. __b32 s_addr;
  5. }
  6. //struct describing an Internet socket address
  7. //sockaddr_in 中存放端口号、网路层中的协议类型(ipv4,ipv6)等,网络层的IP地址;
  8. struct sockaddr_in
  9. {
  10. sa_family_t sin_family ; // Address family AF_XXX
  11. __be16 sin_port ; // 端口号
  12. struct in_addr sin_addr ; // Internet Address
  13. /*Pad to size of 'struct sockaddr'*/
  14. ...........
  15. };
  16. //套接字地址结构体。
  17. struct sockaddr
  18. {
  19. sa_family_t sa_family; // 存放网络层所使用的协议类型(AF_XXX 或 PF_XXX);
  20. char sa_data[14]; // 里面存放端口号、网络层地址等信息;
  21. }

从本质上来说,struct sockaddr与struct sockaddr_in是相同的。
但在,实际的使用过程中,struct sockaddr_in是 Internet环境下的套接字地址形式,而struct sockaddr是通过的套接字地址个形式。在linux内核中struct sockaddr使用的更多,目的是使linux内核代码更为通用。


2.将创建的套接字(struct socket)与套接字地址结构体(struct sockaddr or struct sockaddr_in)进行绑定:
int kernel_bind(struct socket sock, struct sockaddr addr,
int addrlen)

EXPROT_SYMBOL(kernel_bind);
sock : 为通过sock_create()或sock_create_kern()创建的套接字;
addr : 为套接字地址结构体;
addrlen:为套接字地址结构体的大小;

3.将一个套接字(struct socket)设置为监听状态:
int kernel_listen(struct socket sock, int backlog);
backlog :一般情况下设置为0;
EXPORT_SYMBOL(kernel_listen);

4.当把一个套接字设置为监听状态以后,使用这个套接字去监听其它的套接字;
int kernel_accept(struct socket
sock, struct socket new_sock,
int flags);
EXPORT_SYMBOL(kernel_accept);
sock : listening socket 处于监听状态的套接字;
new_sock : 被监听的套接字;
flags: struct socket中的flags字段的取值;

5.把一个套接字连接到另一个套接字地址结构体上:
int kernel_connect(struc socket sock, struct sockaddr addr,
int addrlen, int flags);
EXPORT_SYMBOL(kernel_connect);
sock : struct socket;
addr : 为另一个新的套接字地址结构体;
addrlen : 套接字地址结构体的大小;
flags :file-related flags associated with socket

6.把一个应用层中的数据发送给另一个设备中的进程:
int kernel_sendmsg(struct socket sock, struct msghdr msg,
struct kvec *vec, size_t num, size_t size)
EXPORT_SYMBOL(kernel_sendmsg);
sock : 为当前进程中的struct socket套接字;
msg : 用于接收来自应用层的数据包;
kvec : 中存放将要发送出去的数据;
num : 见代码;
size : 为将要发送的数据的长度;

  1. struct iovec
  2. {
  3. void __user *iov_base;
  4. __kernel_size_t iov_len;
  5. }
  6. struct msghdr
  7. {
  8. //用于存放目的进程所使用的套接字地址
  9. void *msg_name; // 用于存放目的进程的struct sockaddr_in
  10. int msg_namelen; // 目的进程的sizeof(struct sockaddr_in)
  11. //用于来自应用层的数据
  12. struct iovec *msg_iov ;// 指向一个struct iovec的数组,数组中的每个成员表示一个数据块
  13. __kernel_size_t msg_iovlen ; //数据块数,即struct iovec数组的大小
  14. //用于存放一些控制信息
  15. void *msg_control ;
  16. __kernel_size_t msg_controllen; //控制信息的长度;
  17. //
  18. int msg_flags;
  19. }
  1. struct kvec
  2. {
  3. void *iov_base; //用于存放来自应用层的数据;
  4. size_t iov_len; //来自应用层的数据的长度;
  5. }

struct msghdr中的flags字段的取值为:
socket详解 - 图6

int kernel_sendmsg(struct socket sock, struct msghdr msg,
struct kvec *vec, size_t num, size_t size)函数的实现为:
socket详解 - 图7

有kernel_sendmsg()的实现代码可知,struct kvec中的数据部分最终还是要放到struct msghdr之中去的。
kernel_sendmsg()的用法:
socket详解 - 图8
也可以使用下面这个函数来实现相同的功能:
int sock_sendmsg(struct socket sock, struct msghdr msg,
size_t size);
EXPORT_SYMBOL(sock_sendmsg);


7.接受来自另一个网络进程中的数据:
int kernel_recvmsg(struct socket sock, struct msghdr msg,
struct kvec *vec, size_t num, size_t size, int flags)
EXPORT_SYMBOL(kernel_recvmsg);
sock : 为接受进程的套接字;
msg : 用于存放接受到的数据;
vec : 用于指向本地进程中的缓存区;
num : 为数据块的块数;
size : 缓存区的大小;
flags: struct msghdr中的flags字段中的取值范围;
int kernel_recvmsg()的实现:
socket详解 - 图9
kernel_recvmsg()的用法:
socket详解 - 图10


8.关闭一个套接字:
void sock_release(struct socket *sock);
用于关闭一个套接字,并且如果一个它struct socket绑定到了一个struct
inode节点上的话,相应的struct inode也会被释放。