网络套接字socket
跨主机传输要注意的问题
1字节序问题
大端:低地址处放高字节
小端:低地址处放低字节
主机字节序:host
网络字节序:network
解决:_ to _ _ :htons,htonl,ntohs,ntohl
字节序问题(Byte Order Issue),也称为端序问题(Endianness Issue),是指在不同计算机系统中数据的字节排列顺序的差异。这主要涉及到多字节数据类型(如整数、浮点数等)在内存中的存储顺序。字节序问题在数据传输和文件存储等领域尤为重要,尤其是在不同架构的计算机系统之间交换数据时。
字节序的类型
大端序(Big Endian)
在大端序系统中,多字节数据的最高有效字节(MSB)存储在最低的内存地址处,其余字节按照从高到低的顺序存储。
例如,对于16位整数 0x1234,大端序存储为:12 34。
小端序(Little Endian)
在小端序系统中,多字节数据的最低有效字节(LSB)存储在最低的内存地址处,其余字节按照从低到高的顺序存储。
例如,对于16位整数 0x1234,小端序存储为:34 12。
字节序的影响
数据兼容性:不同字节序的系统之间交换数据时,如果不进行适当的转换,可能会导致数据解释错误。
网络通信:网络协议通常采用大端序(如TCP/IP协议),因此小端序系统的计算机在发送和接收数据时需要进行字节序转换。
文件格式:不同系统的文件格式可能采用不同的字节序,导致文件在不同系统间共享时需要特别注意。
字节序转换函数
在 C 语言中,可以使用以下函数进行字节序转换:
htons 和 htonl
htons:将主机字节序的16位整数转换为网络字节序。
htonl:将主机字节序的32位整数转换为网络字节序。
ntohs 和 ntohl
ntohs:将网络字节序的16位整数转换为主机字节序。
ntohl:将网络字节序的32位整数转换为主机字节序。
2对齐
struct
{
int i;
float f;
char ch;
char ch1;
};
编译器自动对齐,会导致如上结构体所占内存大小稍大一点
不同寻址方式,16位地址空间,32位地址空间都不同
解决:不对齐
3类型长度问题
int
char
解决:int32_t,uint32_t,int64_t,int8_t,uint8_t
SOCKET是什么:
套接字(Socket)是一种通信端点的抽象概念,它允许不同主机上的进程进行数据交换。套接字是网络通信的基础,提供了一种在网络中发送和接收数据的方式。
套接字的基本概念
通信通道:套接字提供了一个双向的通信通道,允许数据在网络中的两个进程之间流动。
地址和端口:每个套接字都有一个唯一的地址和端口号,用于标识网络中的特定进程。
协议:套接字可以工作在不同的协议上,最常见的是 TCP(传输控制协议)和 UDP(用户数据报协议)。
Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。
三个参数分别
代表:
报式套接字
创建套接字
int socket(int domain, int type, int protocal)
成功时,返回新创建的套接字的文件描述符(一个非负整数)。
失败时,返回 -1 并设置 errno 以指示错误。
其中,domain 参数指定了套接字的协议族,type 参数指定了套接字的类型,protocol 参数指定了套接字所使用的具体协议。
domain:指定通信协议域,定义了套接字的通信范围。常见的协议域包括:
AF_INET:IPv4 Internet 协议域。
AF_INET6:IPv6 Internet 协议域。
AF_UNIX:Unix 本地通信协议域。
AF_LOCAL:与 AF_UNIX 相同,用于 Unix 域套接字。
type:指定套接字的类型,定义了套接字的通信方式。常见的套接字类型包括:
SOCK_STREAM:提供序列化的、可靠的、双向连接的字节流传输(TCP)。
SOCK_DGRAM:提供无连接的、不可靠的数据报传输(UDP)。
SOCK_RAW:提供原始套接字接口,用于访问较低层次的协议。
protocol:指定特定的协议,用于与 type 和 domain 配合使用。通常,你可以设置为 0,让系统选择默认协议。
对于 SOCK_STREAM,通常是 IPPROTO_TCP;对于 SOCK_DGRAM,通常是 IPPROTO_UDP。
套接字绑定
#include <sys/types.h>
#include <sys/socket.h>
int bind( (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
创建套接字后,需要将其与一个网络地址绑定,以便其他计算机可以访问该套接字。在 Linux系统下,可以使用 bind()系统调用绑定套接字和地址。
// bind port
// 创建一个 sockaddr_in 结构体类型的 servaddr 变量用于存储服务器的地址信息,并将其清零。
struct sockaddr_in bindaddr;
memset (&bindaddr, 0, sizeof(bindaddr); //将 sockaddr_in 结构体的各个成员变量初始化为 0
bindaddr.sin_family = AF_INET;//指定使用 IPv4 协议(AF_INET)
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地任意可用的 IP 地址(INADDR_ANY)
bindaddr.sin_port = htons(3000);//使用指定的端口号(port)
// 将套接字 listenfd 绑定到指定的地址 bindaddr上,bind() 函数返回值为 0 表示绑定成功
if(0 != bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr))) {
printf("bind error\n");
return -1;
}
```c
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong );
uint16_t htons(uint16_t hostshort);
其中,hostshort 和 hostlong 参数是本机字节序的端口号,函数返回值是网络字节序的端口号。
监听连接
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd 参数指定了需要监听的套接字描述符,backlog 参数指定了连接队列的长度,即等待接受的连接数。
例如可以使用以下代码开始监听连接.
// start listen
if (listen(listenfd, 2) != 0) {
printf("listem error\n");
return -1;
}
接受连接
#include <sys/socket.h>
int accept (int __fd, __SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len);
其中,sockfd 参数指定了需要接受连接的套接字描述符,addr 参数用于存储客户端的地址信息,addrlen 参数用于存储地址信息的长度。
接受连接请求
// 定义一个 sockaddr_in 结构体,用于存储客户端的 IP 地址和端口号
struct sockaddr_in clientaddr;
// 定义一个 socklen_t 类型的变量 clientaddrlen ,用于存储客户端地址结构体的长度
socklen_t clientaddrlen = sizeof(clientaddr);
// 调用 accept() 系统调用,接受客户端的连接请求,并返回一个新的套接字描述符 connfd,用于与客户端进行通信。
// accept() 函数会阻塞程序,直到有客户端连接到服务器端。
// listenfd 是服务端的监听套接字, clientaddr 是指向 sockaddr_in 结构体的指针,用于存储客户端的地址信息。
// clientaddrlen 是客户端地址结构体的长度,accept() 函数会将实际接受到的客户端地址长度存储到该变量中。
int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
if (clientfd >= 0) { // 判断 accept() 函数的返回值,判断客户端连接是否失败
// 处理接收数据
} else{
printf("accept error\n");
}
接收和发送数据
sendto 函数用于向特定的地址发送数据。
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:套接字文件描述符。
buf:指向要发送数据的缓冲区的指针。
len:要发送数据的长度。
flags:发送操作的标志,通常设置为 0。
dest_addr:指向 sockaddr 结构的指针,包含目标地址信息。
addrlen:dest_addr 结构的长度。
成功时,返回发送的字节数。
失败时,返回 -1 并设置 errno。
ssize_t len = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
recvfrom 函数用于从任何地址接收数据。
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
sockfd:套接字文件描述符。
buf:用于存储接收数据的缓冲区的指针。
len:缓冲区的长度。
flags:接收操作的标志,通常设置为 0。
src_addr:指向 sockaddr 结构的指针,用于存储发送方的地址信息。
addrlen:src_addr 结构的长度的指针。
成功时,返回接收的字节数。
失败时,返回 -1 并设置 errno。
ssize_t len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&cli_addr, &cli_len);
关闭套接字
关闭套接字是一个非常重要的步骤。
当套接字不再需要使用时,应该立即关闭以释放系统资源和避免资源浪费。
关闭套接字的步骤非常简单,只需要调用 close() 系统调用即可。
int close( (int sockfd );
socket其他函数
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
inet_pton 函数用于将网络地址的文本表示形式转换为网络字节顺序的二进制形式。
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
inet_ntop 函数用于将网络地址的二进制形式转换为可读的文本表示形式。
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
用于检索与套接字(socket)相关的选项。
这个函数允许程序查询当前套接字的配置,例如获取套接字的接收缓冲区大小、发送缓冲区大小、错误状态等。
sockfd:套接字文件描述符。
level:协议层级,例如 SOL_SOCKET、IPPROTO_TCP 或 IPPROTO_IP。
optname:要查询的选项名称,例如 SO_RCVBUF、SO_SNDBUF 或 SO_ERROR。
optval:指向一个缓冲区的指针,该缓冲区将存储选项的值。
optlen:指向一个变量的指针,该变量在调用前包含 optval 缓冲区的长度,在调用后包含存储在 optval 中的选项值的实际长度。
报式套接字与流式套接字
套接字(Socket)可以按照通信方式分为两大类:报式套接字(Datagram Sockets)和流式套接字(Stream Sockets)。这两种套接字分别对应不同的通信协议和应用场景。
流式套接字(Stream Sockets)
流式套接字基于面向连接的、可靠的通信协议,最典型的是 TCP(Transmission Control Protocol)。流式套接字提供的服务包括:
- 面向连接:在数据传输开始之前,必须建立一个连接。
- 可靠的:保证数据的可靠传输,包括数据的顺序和完整性。
- 有序的:保证数据按发送顺序到达。
- 双向的:支持全双工通信,即同时进行发送和接收。
- 字节流:数据被视为字节流,没有边界。
流式套接字通常用于需要可靠传输的应用,如网页浏览(HTTP)、文件传输(FTP)、邮件传输(SMTP)等。
报式套接字(Datagram Sockets)
报式套接字基于无连接的、不可靠的通信协议,最典型的是 UDP(User Datagram Protocol)。报式套接字提供的服务包括:
- 无连接:不需要建立连接,可以直接发送数据。
- 不可靠的:不保证数据的可靠传输,可能会丢失、重复或乱序。
- 尽最大努力交付:UDP 只提供有限的错误检测和纠正。
- 双向的:支持全双工通信。
- 数据报:数据被视为独立的数据报,每个数据报都有自己的目的地。
报式套接字通常用于对实时性要求高、可以容忍一定数据丢失的应用,如在线游戏、视频会议、DNS 查询等。
区别总结
- 连接性:流式套接字需要建立连接,而报式套接字不需要。
- 可靠性:流式套接字提供可靠的数据传输,报式套接字不保证数据的可靠性。
- 顺序性:流式套接字保证数据的顺序,报式套接字不保证。
- 效率:报式套接字通常比流式套接字更高效,因为它们避免了连接建立和维护的开销。
- 应用场景:流式套接字适用于需要可靠传输的场景,报式套接字适用于对实时性要求高的场景。
在选择套接字类型时,需要根据应用的具体需求和网络环境来决定使用流式套接字还是报式套接字。
多播实例
发送方
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("Opening datagram socket error");
exit(1);
} else {
printf("Opening the datagram socket...OK.\n");
}
struct sockaddr_in groupSock;
memset((char *) &groupSock, 0, sizeof(groupSock));
groupSock.sin_family = AF_INET;
groupSock.sin_addr.s_addr = inet_addr("224.1.1.1"); // 多播地址
groupSock.sin_port = htons(4321); // 多播端口
// 设置多播接口
struct in_addr localInterface;
localInterface.s_addr = inet_addr("203.106.93.94"); // 发送多播数据的接口地址
if (setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, sizeof(localInterface)) < 0) {
perror("Setting local interface error");
exit(1);
} else {
printf("Setting the local interface...OK\n");
}
char databuf[1024] = "Multicast test message lol!";
int datalen = sizeof(databuf);
if (sendto(sd, databuf, datalen, 0, (struct sockaddr *)&groupSock, sizeof(groupSock)) < 0) {
perror("Sending datagram message error");
} else {
printf("Sending datagram message...OK\n");
}
close(sd);
return 0;
}
接收方
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
int sd = socket(AF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
perror("Opening datagram socket error");
exit(1);
} else {
printf("Opening datagram socket....OK.\n");
}
struct sockaddr_in localSock;
memset((char *) &localSock, 0, sizeof(localSock));
localSock.sin_family = AF_INET;
localSock.sin_port = htons(49500);
localSock.sin_addr.s_addr = INADDR_ANY;
int reuse = 1;
if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0) {
perror("Setting SO_REUSEADDR error");
close(sd);
exit(1);
} else {
printf("Setting SO_REUSEADDR...OK.\n");
}
if (bind(sd, (struct sockaddr *)&localSock, sizeof(localSock))) {
perror("Binding datagram socket error");
close(sd);
exit(1);
} else {
printf("Binding datagram socket...OK.\n");
}
struct ip_mreq group;
group.imr_multiaddr.s_addr = inet_addr("227.0.0.25"); // 多播地址
group.imr_interface.s_addr = inet_addr("150.158.231.2"); // 本地接口地址
if (setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0) {
perror("Adding multicast group error");
close(sd);
exit(1);
} else {
printf("Adding multicast group...OK.\n");
}
char databuf[1500];
int datalen = sizeof(databuf);
if (read(sd, databuf, datalen) < 0) {
perror("Reading datagram message error");
close(sd);
exit(1);
} else {
printf("Reading datagram message...OK.\n");
printf("The message from multicast server is: %s\n", databuf);
}
return 0;
}
标签:addr,字节,sockaddr,int,笔记,Linux,接字,include,Socket
From: https://blog.csdn.net/swaggyelite/article/details/143634609