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通信方面的研究。