Linux套接字Socket

news/2024/9/28 18:05:29/

Linux套接字Socket

前提知识补充

  • 为不同机器上的两个进程之间提供通信机制

  • 主机字节序小端存储,网络字节序大端存储

  • 特点TCPUDP
    连接类型面向连接无连接
    可靠性
    有序性保证数据包按顺序到达不保证数据包顺序
    流量控制有滑动窗口机制
    拥塞控制有拥塞控制机制
    复杂性较高较低
    实时性较差较好
    适用场景文件传输、Web 应用、电子邮件等实时音视频、在线游戏、DNS 查询等

Socket

前情提示

inet_addr函数
  • 将点分十进制格式的 IPv4 地址字符串转换为网络字节序的 32 位无符号整数
接口代码
#include <arpa/inet.h>in_addr_t inet_addr(const char* ip_address);
参数解释
  • ip_address: 点分十进制IPV4
返回值
  • 网络字节序的 32 位无符号整数
实例代码
#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>int main() {// 定义IPV4地址const char *ip_address = "192.168.1.1";in_addr_t ip;// 将点分十进制格式的 IP 地址转换为 32 位无符号整数ip = inet_addr(ip_address);if (ip == INADDR_NONE) {perror("inet_addr");printf("无效的 IP 地址: %s\n", ip_address);return 1;}// 打印转换后的 IP 地址printf("IP 地址: %s\n", ip_address);printf("转换后的 32 位无符号整数: 0x%08X\n", (unsigned int)ip);return 0;
}
套接字结构定义
#include <netine/in.h>struct sockaddr_in
{// 地址族,对于IPV4地址应设置为 AF_INETsa_family_t sin_family;// 16位端口号,以大端字节序存储in_port_t sin_port;// 32位IPV4地址,以大端字节序存储,指明通信所使用的IP地址/*** struct in_addr {*       in_addr_t s_addr;  /* 32 位无符号整数,表示 IP 地址 */* };*/struct in_addr sin_addr;// 填充字段,一般设置为0unsigned char sin_zero[8]
}
字节序
  • 将16位和32位整数在主机字节序和网络字节序之间进行转换
接口代码
#include <netinet/in.h>// h:hostname -- 本地 
// to:to -- 转变
// n:network -- 网络
// l:32位
// s:16位
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned short int netlong);
unsigned short int ntohs(unsigned short int netshort);
gethostname函数
  • 获取主机名
接口代码
#include <unistd.h>int gethostname(char* name,int namelength);
参数解释
  • name: 自定义缓冲区
  • namelength: 自定义缓冲区大小
返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno 以指示错误原因
实例代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main()
{char buf[256];int res;res = gethostname(buf, sizeof(buf));if (res == 0)printf("本机地址为: %s\n", buf);return 0;
}
地址转换打印函数inet_ntoa
  • 将网络字节转换位可打印四点表示法格式的字符串
接口代码
#include <arpa/inet.h>char* inet_ntoa(struct in_addr in);
参数解释
  • in: 32位无符号整数
返回值
  • 成功时,返回一个指向静态分配的字符数组的指针,该数组包含点分十进制格式的 IP 地址字符串。
  • 如果输入无效,则返回 NULL
实例代码
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>int main()
{struct in_addr addre;addre.s_addr = inet_addr("192.168.1.1");char *buf;buf = inet_ntoa(addre);printf("四点表示法字符串为: %s\n", buf);return 0;
}

服务器端and客户端

TCP执行流程

在这里插入图片描述

UDP执行流程

在这里插入图片描述

socket(创建)
  • 创建通信连接句柄
接口代码
#include <sys/socket.h>int socket(int domain,int type,int protocol);
参数解释
  • domain:指定通信域(也称为地址族),决定了将使用的寻址方案。常见的值包括:

    • AF_INET:IPv4 网络协议。
    • AF_INET6:IPv6 网络协议。
    • AF_UNIXAF_LOCAL:本地通信(同一台机器上的进程间通信)。
  • type:指定套接字的类型,决定了通信方式。常见的值包括:

    • SOCK_STREAM:提供有序的、可靠的、基于连接的字节流服务,通常使用 TCP 协议。

    • SOCK_DGRAM:提供无连接的、不可靠的数据报服务,通常使用 UDP 协议。

    • SOCK_RAW:提供原始网络协议访问,通常用于底层网络编程。

  • protocol:指定具体的协议。大多数情况下,可以设置为 0。

返回值
  • 成功时,返回一个非负整数,即新创建的套接字的文件描述符。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。
实例代码
// 参考下方汇总
bind(绑定)
  • 把电脑上真正的网络地址与一个套接字标识符关联起来
接口代码
#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd,const struct sockaddr* address,size_t addrlen);
参数解释
  • sockfd: 要绑定的套接字描述符
  • address: 要绑定的地址信息
  • addrlen: address接口体大小
返回值
  • 成功时,返回 0
  • 失败时,返回 -1,并设置 errno 以指示错误原因
实例代码
// 参考下方汇总
listen(监听)
  • 绑定之后,在任何客户端系统可以连接到新建立的服务器端点之前,服务器必须设定为等待连接
接口代码
#include <sys/socket.h>int listen(int sockfd,int backlog);
参数解释
  • sockfd:要设置为监听状态的套接字描述符。
  • backlog:指定等待连接的最大队列长度。这是指尚未被 accept 接受但已经完成三次握手的连接请求的数量
返回值
  • 成功时,返回 0。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。

实例代码

// 参考下方汇总
accept(接受连接)
  • 当服务器收到客户端connect请求时,必须建立一个全新的套接字来处理这个特定的通信。第一个套接字只用来建立通信,第二个套接字由accept完成
接口代码
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd,struct sockaddr* address,size_t* add_len);
参数解释
  • sockfd: 处于监听状态的套接字
  • address: 当服务器调用 accept 函数接受一个新的连接请求时,address 会被填充为连接到服务器的客户端的地址信息
  • address: 指定address缓冲区大小,并在返回时更新实际存储长度
返回值
  • 成功时,返回一个新的套接字描述符,该描述符用于与客户端进行通信。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。
实例代码
// 参考下方汇总
connect(请求连接)
  • 客户程序通过一个未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器
接口代码
#include <sys/socket.h>int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
参数解释
  • sockfd: 指定的套接字将连接到参数address指定服务器套接字
  • addr: 目的地址,IPV4或IPV6
  • addrlen: addr结构体大小

返回值

  • 成功时,返回 0。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。
实例代码
// 参考下方汇总
recv(接收数据)
  • 从指定的文件描述符读取数据
接口代码
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd,void* buffer,size_t length,int flags);
参数解释
  • sockfd:要从中接收数据的套接字描述符。
  • buf:指向一个缓冲区的指针,用于存储接收到的数据。
  • len:缓冲区的大小(以字节为单位)。
  • flags:指定调用的行为选项。常见的标志包括:
    • 0:默认行为,阻塞直到有数据可读或发生错误。
    • MSG_PEEK:查看传入的数据而不从输入队列中移除。
    • MSG_DONTWAIT:非阻塞模式,如果当前没有数据可读,则立即返回 -1 并设置 errnoEAGAINEWOULDBLOCK
    • MSG_WAITALL:阻塞直到至少 len 字节的数据被接收,或者发生错误
返回值
  • 成功时,返回实际接收到的字节数(可能少于请求的 len)。
  • 如果连接关闭且所有数据已被读取,返回 0。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。
实例代码
// 参考下方汇总
send(发送数据)
  • 向指定套接字发送数据
接口代码
#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd,const void* buffer,size_length len,int flags);
参数解释
  • ockfd:要发送数据的套接字描述符。
  • buf:指向包含要发送数据的缓冲区的指针。
  • len:要发送的数据长度(以字节为单位)。
  • flags:指定调用的行为选项。常见的标志包括:
    • 0:默认行为,阻塞直到所有数据被发送或发生错误。
    • MSG_DONTWAIT:非阻塞模式,如果当前无法立即发送数据,则立即返回 -1 并设置 errnoEAGAINEWOULDBLOCK
    • MSG_NOSIGNAL:防止 SIGPIPE 信号,当对端已经关闭连接时,不会产生 SIGPIPE 信号。
    • MSG_OOB:发送带外数据(out-of-band data)。
返回值
  • 成功时,返回实际发送的字节数(可能少于请求的 len)。
  • 如果连接关闭且所有数据已被发送,返回 0。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。
实例代码
// 参考下方汇总
close(关闭)
  • 通过文件描述符关闭套接字
接口代码
#include <unistd.h>int close(int fd);
参数解释
  • fd:要关闭的文件描述符。
返回值
  • 成功时,返回 0。
  • 失败时,返回 -1,并设置 errno 以指示错误原因。
实例代码
// 参考下方汇总
recvfrom(接收)
  • UDP接收函数
接口代码
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>ssize_t revcfrom(int sockfd,void* message,size_t length,int flags,struct sockaddr* send_add,socklen_t* add_len);
参数解释
  • sockfd: Socket创建文件描述符
  • message: 存储接收数据
  • length: message缓冲区的最大长度
  • flags: 用于修改revcfrom的行为
    • MSG_PEEK:查看传入的数据,但不将其从输入队列中移除。
    • MSG_WAITALL:等待接收指定数量的字节,除非发生错误或信号中断。
    • MSG_DONTWAIT:使 recvfrom 在非阻塞模式下操作,如果没有数据可读,则立即返回 -1 并设置 errnoEAGAINEWOULDBLOCK
  • send_addr: 存储发送方的地址信息
    • 当为NULL时雨recv工作方式相同
  • add_len: send_addr的实际大小
返回值
  • 成功:返回接收到的字节数(可能小于 length)。
  • 失败:返回 -1,并设置 errno 以指示错误类型。
实例代码
// 参考下方汇总
sendto(发送)
  • UDP发送函数
接口代码
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *message, size_t length, int flags,const struct sockaddr *dest_addr, socklen_t dest_len);
参数解释
  • sockfd: Socket创建文件描述符
  • message: 要发送的信息
  • length: 要发送信息的长度
  • flags: 用于修改sendto
    • MSG_CONFIRM:请求确认。
    • MSG_DONTROUTE:不要路由,直接发送到本地网络。
    • MSG_EOR:记录结束。
    • MSG_OOB:带外数据。
    • MSG_NOSIGNAL:防止 SIGPIPE 信号。
  • dest_addr: 指向发送到目标地址的信息
  • dest_len: dest_addr结构体大小
返回值
  • 成功:返回发送的字节数(可能小于 length)。
  • 失败:返回 -1,并设置 errno 以指示错误类型。
实例代码
// 见下方汇总
综合实例–TCP连接

server-tcp.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>int port = 7777; // 端口号
int main()
{char buf[4096]; // 接收服务器端返回信息缓冲区int socketDes;int addLen; // 接收连接时地址信息长度// 创建套接字文件描述符socketDes = socket(AF_INET, SOCK_STREAM, 0);// 地址信息struct sockaddr_in sin;bzero(&sin, sizeof(sin));sin.sin_family = AF_INET;/*** INADDR_ANY 是一个常量,值为 0x00000000,表示绑定到所有可用的网络接口。* 这意味着服务器将监听所有可用的网络接口上的指定端口*/sin.sin_addr.s_addr = INADDR_ANY;sin.sin_port = htons(port); // 监听端口//  绑定int bindRes;bindRes = bind(socketDes, (struct sockaddr *)&sin, sizeof(sin));if (bindRes == -1){printf("bind--");exit(1);}// 监听int listenRes;listenRes = listen(socketDes, 20);if (listenRes == -1){perror("listen--");exit(1);}while (1){struct sockaddr_in pin;int temp_sock_descriptor;temp_sock_descriptor = accept(socketDes, (struct sockaddr *)&pin, &addLen);// 打印客户端信息printf("Connection accepted from %s:%d\n", inet_ntoa(pin.sin_addr), ntohs(pin.sin_port));if (temp_sock_descriptor == -1){perror("accept--");exit(1);}size_t len;len = recv(temp_sock_descriptor, buf, sizeof(buf), 0);if (len == -1){perror("recv--");exit(1);}printf("喜羊羊: %s\n", buf);// 示例:向客户端发送一条欢迎消息const char *sendMessage = "Welcome to the server!\n";if (strcmp(buf, "你好") == 0)sendMessage = "你也好";send(temp_sock_descriptor, sendMessage, strlen(sendMessage), 0); // 发送数据memset(buf, 0, 4096); // 清空数组close(temp_sock_descriptor); // 关闭套接字}return 0;
}

client-tcp.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>// 服务器地址
const char *hostname = "127.0.0.1";
int port = 7777;int main(int argc, char *argv[])
{char buf[4096];int socketDes;char *message;if (argc < 2){printf("发送信息不能为空\n");exit(1);}message = argv[1];// 创建套接字文件描述符socketDes = socket(AF_INET, SOCK_STREAM, 0);if (socketDes == -1){perror("socket--");exit(1);}// 处理对方IP数据struct hostent *server_host_name;server_host_name = gethostbyname(hostname);if (server_host_name == 0){perror("接受失败--");exit(1);}struct sockaddr_in pin;bzero(&pin, sizeof(pin));pin.sin_family = AF_INET;pin.sin_addr.s_addr = ((struct in_addr *)(server_host_name->h_addr))->s_addr;pin.sin_port = htons(port);// 请求连接int res;res = connect(socketDes, (void *)&pin, sizeof(pin));if (res == -1){perror("res--");exit(1);}// 发送数据ssize_t len;len = send(socketDes, message, sizeof(message), 0);// 接收数据ssize_t reveLen;reveLen = recv(socketDes, buf, sizeof(buf), 0);printf("灰太狼: %s\n", buf);// 关闭close(socketDes);exit(0);
}
综合实力–UDP连接

server-udp.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>int main(int argc, char *argv[])
{int socketDes;socketDes = socket(AF_INET, SOCK_DGRAM, 0);if (socketDes < 0){printf("文件描述符创建失败\n");exit(1);}struct sockaddr_in servAddr;struct sockaddr_in clientAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = htonl(INADDR_ANY);servAddr.sin_port = htons(7777);int rc = bind(socketDes, (struct sockaddr *)&servAddr, sizeof(servAddr));if (rc < 0){printf("绑定失败\n");exit(1);}printf("等待接收信息\n");char msg[50];int cliLen, n;while (1){memset(msg, 0x0, 50);cliLen = sizeof(clientAddr);n = recvfrom(socketDes, msg, 50, 0, (struct sockaddr *)&clientAddr, &cliLen);if (n < 0){printf("信息接受失败\n");continue;}printf("%s: from %s:UDP %u : %s\n", argv[0], inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), msg);int sendToRes;char *res = "你也ggg";sendToRes = sendto(socketDes, res, sizeof(res), 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr));if (sendToRes < 0){printf("返回消息失败\n");exit(1);}}exit(0);
}

client-udp.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>int port = 7777;int main(int argc, char *argv[])
{int socketDes;char buf[4096];struct sockaddr_in cliAddr, remoteServAddr;struct hostent *h;if (argc < 3){printf("请输入发送目标地址和发送信息\n");exit(1);}h = gethostbyname(argv[1]);if (h == NULL){printf("%s: 无法识别地址 %s\n", argv[0], argv[1]);exit(1);}remoteServAddr.sin_family = h->h_addrtype;memcpy((char *)&remoteServAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);remoteServAddr.sin_port = htons(port);// 建立套接字文件描述符socketDes = socket(AF_INET, SOCK_DGRAM, 0);if (socketDes < 0){printf("无法打开文件描述符\n");exit(1);}cliAddr.sin_family = AF_INET;// 客户端可以绑定到任何可用的网络接口cliAddr.sin_addr.s_addr = htonl(INADDR_ANY);// 系统自动分配一个可用的端口cliAddr.sin_port = htons(0);int bindRes;bindRes = bind(socketDes, (struct sockaddr *)&cliAddr, sizeof(cliAddr));if (bindRes < 0){printf("绑定失败\n");return 0;}int sendToRes;for (int i = 2; i < argc; i++){sendToRes = sendto(socketDes, argv[i], strlen(argv[i]) + 1, 0, (struct sockaddr *)&remoteServAddr, sizeof(remoteServAddr));if (sendToRes < 0){printf("发送失败\n");close(socketDes);exit(1);}}char msg[50];int cliLen = sizeof(remoteServAddr);int n = recvfrom(socketDes, msg, 50, 0, (struct sockaddr *)&remoteServAddr, &cliLen);if (n < 0){printf("接收信息失败\n");exit(1);}printf("接收到的数据为:%s\n", msg);exit(0);
}

http://www.ppmy.cn/news/1530970.html

相关文章

PostgreSQL 向量数据存储指南

引言 在当今的数字化时代&#xff0c;数据存储的方式和技术正变得越来越复杂和多样化。随着机器学习和数据科学的发展&#xff0c;向量数据的存储和管理变得尤为重要。本文将详细介绍如何使用 Java 和 PostgreSQL 数据库来存储向量数据&#xff0c;探索其应用场景、优势以及具…

消息中间件---Kafka

一、什么是Kafka&#xff1f; Kafka是一个分布式流处理平台,类似于消息队列或企业消息传递系统&#xff1b; 流处理事什么呢&#xff1f; 流处理就是数据处理工作流&#xff0c;本质上是一种计算机编程范例。流处理是对接收到的新数据事件的连续处理。‌它涉及对从生产者到消…

在Java中,有没有其他方式可以替代List<Map<String,Object>>来存储和处理数据?

在Java中&#xff0c;有多种方式可以替代List<Map<String, Object>>来存储和处理数据。选择哪种方式取决于你的具体需求&#xff0c;比如数据结构的复杂性、类型安全、性能要求等。以下是一些常见的替代方案&#xff1a; 自定义类&#xff08;POJOs&#xff09;&am…

使用shardingsphere实现mysql数据库分片

在大数据时代&#xff0c;随着业务数据量的不断增长&#xff0c;单一的数据库往往难以承载大规模的数据处理需求。数据库分片&#xff08;Sharding&#xff09;是一种有效的数据库扩展技术&#xff0c;通过将数据分布到多个数据库实例上&#xff0c;提高系统的性能和可扩展性。…

C++之哈希 --- 哈希的应用(位图布隆过滤器)

一、位图 1.1 位图的基本概念 在如今网络交通高度发达的时代&#xff0c;网购已经成为我们日常生活中的一部分。没当双11到来&#xff0c;各大平台都会迎来一次网购的高潮。这就会让服务器短时间内获得高达几十亿上百亿的数据&#xff0c;那我们该如何去处理这海量的数据呢&am…

js判断一个对象里有没有某个属性

1. 使用in操作符 in操作符可以用来检测属性是否存在于对象或其原型链中。 const obj {a: 1, b: 2}; if (a in obj) { console.log(属性a存在于obj中); } else { console.log(属性a不存在于obj中); } 2. 使用hasOwnProperty()方法 hasOwnProperty()方法用来检测一个…

字节打印流字符打印流

打印流不能读&#xff0c;只能写 打印流 分类:打印流一般是指:PrintStream&#xff0c;PrintWriter两个类 特点1:打印流只操作文件目的地&#xff0c;不操作数据源 特点2:特有的写出方法可以实现&#xff0c;数据原样写出 特点3:特有的写出方法&#xff0c;可以实现自动刷新…

华为HarmonyOS灵活高效的消息推送服务(Push Kit) - 1 简介

Push Kit&#xff08;推送服务&#xff09;是华为提供的消息推送平台&#xff0c;建立了从云端到终端的消息推送通道。所有HarmonyOS应用可通过集成Push Kit&#xff0c;实现向应用实时推送消息&#xff0c;使消息易见&#xff0c;构筑良好的用户关系&#xff0c;提升用户的感知…