Bootstrap

关于socket研究

RTEMS之socket研究



前言

本文主要介绍socket通信的相关函数以及运用


一、socket网络编程流程

在这里插入图片描述

二、socket接口

1、函数原型

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

2、参数详解

domain:
即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPv4)、AF_INET6(IPv6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

/*   sys/socket.h    */
/*
 * Address families.
 */
#define	AF_UNSPEC	0		/* unspecified */
#if __BSD_VISIBLE
#define	AF_LOCAL	AF_UNIX		/* local to host (pipes, portals) */
#endif
#define	AF_UNIX		1		/* standardized name for AF_LOCAL */
#define	AF_INET		2		/* internetwork: UDP, TCP, etc. */
#if __BSD_VISIBLE
#define	AF_IMPLINK	3		/* arpanet imp addresses */
#define	AF_PUP		4		/* pup protocols: e.g. BSP */
#define	AF_CHAOS	5		/* mit CHAOS protocols */
#define	AF_NETBIOS	6		/* SMB protocols */
#define	AF_ISO		7		/* ISO protocols */
#define	AF_OSI		AF_ISO
#define	AF_ECMA		8		/* European computer manufacturers */
#define	AF_DATAKIT	9		/* datakit protocols */
#define	AF_CCITT	10		/* CCITT protocols, X.25 etc */
#define	AF_SNA		11		/* IBM SNA */
#define AF_DECnet	12		/* DECnet */
#define AF_DLI		13		/* DEC Direct data link interface */
#define AF_LAT		14		/* LAT */
#define	AF_HYLINK	15		/* NSC Hyperchannel */
#define	AF_APPLETALK	16		/* Apple Talk */
#define	AF_ROUTE	17		/* Internal Routing Protocol */
#define	AF_LINK		18		/* Link layer interface */
#define	pseudo_AF_XTP	19		/* eXpress Transfer Protocol (no AF) */
#define	AF_COIP		20		/* connection-oriented IP, aka ST II */
#define	AF_CNT		21		/* Computer Network Technology */
#define pseudo_AF_RTIP	22		/* Help Identify RTIP packets */
#define	AF_IPX		23		/* Novell Internet Protocol */
#define	AF_SIP		24		/* Simple Internet Protocol */
#define	pseudo_AF_PIP	25		/* Help Identify PIP packets */
#define	AF_ISDN		26		/* Integrated Services Digital Network*/
#define	AF_E164		AF_ISDN		/* CCITT E.164 recommendation */
#define	pseudo_AF_KEY	27		/* Internal key-management function */
#endif
#define	AF_INET6	28		/* IPv6 */
#if __BSD_VISIBLE
#define	AF_NATM		29		/* native ATM access */
#define	AF_ATM		30		/* ATM */
#define pseudo_AF_HDRCMPLT 31		/* Used by BPF to not rewrite headers
					 * in interface output routine
					 */
#define	AF_NETGRAPH	32		/* Netgraph sockets */
#define	AF_SLOW		33		/* 802.3ad slow protocol */
#define	AF_SCLUSTER	34		/* Sitara cluster protocol */
#define	AF_ARP		35
#define	AF_BLUETOOTH	36		/* Bluetooth sockets */
#define	AF_IEEE80211	37		/* IEEE 802.11 protocol */
#define	AF_INET_SDP	40		/* OFED Socket Direct Protocol ipv4 */
#define	AF_INET6_SDP	42		/* OFED Socket Direct Protocol ipv6 */
#define	AF_HYPERV	43		/* HyperV sockets */
#define	AF_MAX		43

type:
指定socket类型。常用的socket类型有,SOCK_STREAM(TCP流式套接字)、SOCK_DGRAM(UDP数据报式套接字)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。

/*       sys/socket_type.h     */
/* Types of sockets.  */
enum __socket_type
{
  SOCK_STREAM = 1,		/* Sequenced, reliable, connection-based
				   byte streams.  */
#define SOCK_STREAM SOCK_STREAM
  SOCK_DGRAM = 2,		/* Connectionless, unreliable datagrams
				   of fixed maximum length.  */
#define SOCK_DGRAM SOCK_DGRAM
  SOCK_RAW = 3,			/* Raw protocol interface.  */
#define SOCK_RAW SOCK_RAW
  SOCK_RDM = 4,			/* Reliably-delivered messages.  */
#define SOCK_RDM SOCK_RDM
  SOCK_SEQPACKET = 5,		/* Sequenced, reliable, connection-based,
				   datagrams of fixed maximum length.  */
#define SOCK_SEQPACKET SOCK_SEQPACKET
  SOCK_DCCP = 6,		/* Datagram Congestion Control Protocol.  */
#define SOCK_DCCP SOCK_DCCP
  SOCK_PACKET = 10,		/* Linux specific way of getting packets
				   at the dev level.  For writing rarp and
				   other similar things on the user level. */
#define SOCK_PACKET SOCK_PACKET

  /* Flags to be ORed into the type parameter of socket and socketpair and
     used for the flags parameter of paccept.  */

  SOCK_CLOEXEC = 02000000,	/* Atomically set close-on-exec flag for the
				   new descriptor(s).  */
#define SOCK_CLOEXEC SOCK_CLOEXEC
  SOCK_NONBLOCK = 00004000	/* Atomically mark descriptor(s) as
				   non-blocking.  */
#define SOCK_NONBLOCK SOCK_NONBLOCK
};

protocol:
就是指定协议。常用的协议有,IPPROTO_TCP、PPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。一般使用0,系统会根据第一个参数自动选择。

3.返回值

若无错误发生,socket()返回引用新套接口的描述字。否则的话,返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()获取相应错误代码。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议

二、sockaddr_in6结构体

1、函数原型

struct sockaddr_in6 {
       uint8_t           sin6_len; /* length of this struct */
       sa_family_t     sin6_family;    /* AF_INET6 */
       in_port_t sin6_port;       /* Transport layer port # */
       uint32_t  sin6_flowinfo; /* IP6 flow information */
       struct in6_addr      sin6_addr;      /* IP6 address */
       uint32_t  sin6_scope_id;       /* scope zone index */
};

2、参数详解

sin6_len
这个字段表示结构体的长度,类型是uint8_t。不过我记得在某些系统中,这个字段可能不存在,比如在Linux中可能没有,而在BSD系统中可能有。需要确认一下不同系统的差异,但用户提供的定义中有这个字段,所以可能是在某些特定的系统或版本中使用。

sin6_family
地址族,类型是sa_family_t,通常设置为AF_INET6,表示IPv6地址。

sin6_port
端口号,类型是in_port_t,使用网络字节顺序(大端序)。

sin6_flowinfo
IPv6流信息,用于QoS等,但目前实际使用较少,通常设为0。

sin6_addr
IPv6地址,类型是struct in6_addr,通常是一个128位的数组,用来存储IPv6地址。

sin6_scope_id
范围区域索引,用于处理链路本地地址等具有作用域限制的地址,比如fe80::开头的地址需要指定网络接口索引。

三、bind()函数

1、函数原型

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

2、参数详解

sockfd
即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字

addr
一个const struct sockaddr * 指针,指向要绑定给sockfd的协议地址。

struct sockaddr{
    sa_family_t  sin_family;   //地址族(Address Family),也就是地址类型
    char         sa_data[14];  //IP地址和端口号
};

sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,但没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值。正是由于通用结构体 sockaddr 使用不便,才针对不同的地址类型定义了不同的结构体。 如ipv6对应的是:

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   / * AF_INET6 * / 
    in_port_t       sin6_port;     / * port number * / 
    uint32_t        sin6_flowinfo; / * IPv6 flow information * / 
    struct in6_addr sin6_addr;     / * IPv6 address * / 
    uint32_t        sin6_scope_id; / * Scope ID (new in 2.4) * / 
};

addrlen:对应的是地址的长度。
通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

3、返回值

如无错误发生,则bind()返回0。否则的话,将返回-1,应用程序可通过WSAGetLastError()获取相应错误代码

四、listen()函数

1、函数原型

int listen(int sockfd, int backlog);

2、参数详解

第一个参数即为要监听的socket描述字,
第二个参数为相应socket可以排队的最大连接个数。
socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求

3、返回值

如无错误发生,listen()返回0。否则的话,返回-1,应用程序可通过WSAGetLastError()获取相应错误代码。

五、connect()函数

1、函数原型

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

2、参数详解

第一个参数即为客户端的socket描述字,
第二参数为服务器的socket地址,
第三个参数为socket地址的长度。
客户端通过调用connect函数来建立与TCP服务器的连接。

3、返回值

若无错误发生,则connect()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。对非阻塞套接口而言,若返回值为SOCKET_ERROR则应用程序调用WSAGetLastError()。如果它指出错误代码为WSAEWOULDBLOCK,则您的应用程序可以:
1、用select(),通过检查套接口是否可写,来确定连接请求是否完成。
2、如果您的应用程序使用基于消息的WSAAsyncSelect()来表示对连接事件的兴趣,则当连接操作完成后,您会收到一个FD_CONNECT消息。

六、accept()函数

1、函数原型

int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen);

2、参数详解

第一个参数为服务器的socket描述字,
第二个参数为指向struct sockaddr * 的指针,用于返回客户端的协议地址,
第三个参数为客户端协议地址的长度。如果accpet成功,那么其返回值是由内核自动生成的一个全新的描述字,代表与返回客户的TCP连接。

3、返回值

返回值:
如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。

七、recv()和send()函数

1、函数原型

ssize_t send(int sockfd, const void * buf, size_t len, int flags);
ssize_t recv(int sockfd, void * buf, size_t len, int flags);

2、参数详解

第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。或者是以下组合:
MSG_DONTROUTE:不查找表,是send函数使用的标志,这个标志告诉IP,目的主机在本地网络上,没有必要查找表,这个标志一般用在网络诊断和路由程序里面。
MSG_OOB:表示可以接收和发送带外数据。
MSG_PEEK:查看数据,并不从系统缓冲区移走数据。是recv函数使用的标志,表示只是从系统缓冲区中读取内容,而不清楚系统缓冲区的内容。这样在下次读取的时候,依然是一样的内容,一般在有个进程读写数据的时候使用这个标志。
MSG_WAITALL:等待所有数据,是recv函数的使用标志,表示等到所有的信息到达时才返回,使用这个标志的时候,recv返回一直阻塞,直到指定的条件满足时,或者是发生了错误。

八、setsockopt()函数

1、函数原型

int setsockopt(int sockFd, int level, int optname, const void * optval, socklen_t optlen);

2、参数详解

sockfd
将要被设置或者获取选项的套接字,AF_INET6或AF_INET

level
选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6,一般设成SOL_SOCKET以存取socket层。
SOL_SOCKET:通用套接字选项.
IPPROTO_IP:IP选项.IPv4套接口
IPPROTO_TCP:TCP选项.
IPPROTO_IPV6: IPv6套接口

optname
在这里插入图片描述
SO_BROADCAST
允许或禁止发送广播数据,当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。

SO_DEBUG
打开或关闭调试信息,当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置 SOCK_DBG(第10)位,或清SOCK_DBG位。

SO_DONTROUTE
打开或关闭路由查找功能,当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。

SO_KEEPALIVE
套接字保活,如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时 器,否则关闭保活定时器。对于所有协议,该操作都会根据option_value置或清 sock->sk->sk_flag中的 SOCK_KEEPOPEN位。

SO_LINGER
如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。该选项的参数(option_value)是一个linger结构:
struct linger {
int l_onoff;
int l_linger;
};
如果linger.l_onoff值为0(关闭),则清 sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为 linger.l_linger。

SO_OOBINLINE
紧急数据放入普通数据流,该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。

SO_RCVBUF
设置接收缓冲区的大小,接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)和256字节。该操作将sock->sk->sk_rcvbuf设置为val * 2。

SO_SNDTIMEO
设置发送超时时间,该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。

SO_RCVLOWAT
设置接收数据前的缓冲区内的最小字节数,在Linux中,缓冲区内的最小字节数是固定的,为1。即将sock->sk->sk_rcvlowat固定赋值为1。

SO_RCVTIMEO
设置接收超时时间,该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。

SO_SNDTIMEO
设置发送超时时间,该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。

SO_REUSEADDR(端口复用)
允许套接口和一个已在使用中的地址捆绑
四个功能:
1、允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。
2、允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
3、允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
4、允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。

SO_REUSEPORT
1、此选项允许完全重复捆绑,但仅在想捆绑相同IP地址和端口的套接口都指定了此套接口选项才行。
2、如果被捆绑的IP地址是一个多播地址,则SO_REUSEADDR和SO_REUSEPORT等效

optval
对于setsockopt(),指针,指向存放选项待设置的新值的缓冲区。获得或者是设置套接字选项.根据选项名称的数据类型进行转换。

optlen
optval缓冲区长度。

九、示例

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 12345
#define BUFFER_SIZE 1024
/**************************
 * 客户端实现
 **************************/
void tcp_client(const char *server_ip)
{
    int sock;
    struct sockaddr_in6 serv_addr;
    char buffer[BUFFER_SIZE] = {0};

    // 创建IPv6 TCP socket
    if ((sock = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin6_family = AF_INET6;
    serv_addr.sin6_port = htons(PORT);

    // 转换IPv6地址
    if (inet_pton(AF_INET6, server_ip, &serv_addr.sin6_addr) <= 0) {
        perror("invalid address");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 连接服务器
    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connection failed");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 发送和接收数据
    while (1) {
        printf("Enter message: ");
        fgets(buffer, BUFFER_SIZE, stdin);
        
        if (send(sock, buffer, strlen(buffer), 0) < 0) break;
        if (recv(sock, buffer, BUFFER_SIZE, 0) <= 0) break;
        
        printf("Server reply: %s", buffer);
        memset(buffer, 0, BUFFER_SIZE);
    }

    close(sock);
}

服务器端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 12345
#define BUFFER_SIZE 1024

/**************************
 * 服务器端实现
 **************************/
void tcp_server(void)
{
    int server_fd, client_fd;
    struct sockaddr_in6 server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE] = {0};
    char client_ip[INET6_ADDRSTRLEN];

    // 创建IPv6 TCP socket
    if ((server_fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin6_family = AF_INET6;
    server_addr.sin6_addr = in6addr_any; // 监听所有IPv6接口
    server_addr.sin6_port = htons(PORT);

    // 绑定socket
    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 开始监听
    if (listen(server_fd, 5) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d...\n", PORT);

    // 接受客户端连接
    if ((client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len)) < 0) {
        perror("accept failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 获取客户端IP地址
    inet_ntop(AF_INET6, &client_addr.sin6_addr, client_ip, INET6_ADDRSTRLEN);
    printf("Client connected from: %s\n", client_ip);

    // 接收并回显数据
    while (1) {
        ssize_t recv_len = recv(client_fd, buffer, BUFFER_SIZE, 0);
        if (recv_len <= 0) break;
        
        printf("Received: %s", buffer);
        send(client_fd, buffer, recv_len, 0);
        memset(buffer, 0, BUFFER_SIZE);
    }

    close(client_fd);
    close(server_fd);
}


总结

上述便是关于socket通信方面的研究。

;