当前位置: 首页 > news >正文

Linux实现进程之间Socket通信详解

一.Socket通信基本介绍

        Socket(套接字)是计算机网络中的一个重要概念,它指的是一种用于网络通信的编程接口。以下是关于Socket的详细解释:

1.定义与功能

        定义:Socket是应用层与传输层之间的接口,它提供了一种标准的通信方式,使得不同的程序能够在网络上进行数据交换。Socket可以被视为网络通信的端点,它在网络上标识了一个通信链路的两端,并提供了通信双方所需的接口和功能。

        功能:Socket允许应用程序通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。它允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。

2.工作原理

        Socket的工作原理可以简单概括为以下几个步骤,如下图所示:

        1.创建Socket:应用程序通过调用socket()函数创建一个Socket对象,并指定Socket的类型(如TCP或UDP)。

        2.绑定地址和端口(仅服务器端):服务器端通过调用bind()函数将Socket对象与一个特定的IP地址和端口号绑定,这个端口就是服务器的标识,用于在网络上与其他主机建立连接。

        3.监听连接(仅服务器端):服务器端通过调用listen()函数开始监听来自客户端的连接请求。

        4.连接请求(客户端):客户端通过调用connect()函数向服务器发送连接请求,指定服务器的IP地址和端口号。 

        5.接受连接(服务器端):当服务器端监听到来自客户端的连接请求后,通过调用accept()函数接受连接请求,并返回一个新的Socket对象,用于与客户端进行通信。

        6.数据传输:一旦建立连接,服务器和客户端就可以通过各自的Socket对象进行数据传输。它们通过读取和写入Socket对象上的数据流来发送和接收数据。

        7.关闭连接:当通信完成或者出现错误时,可以通过关闭Socket对象来结束连接,释放资源。

3.类型

        Socket主要分为两种类型:

        1.TCP Socket:基于TCP协议,提供面向连接的、可靠的、字节流的服务。在TCP协议中,建立连接通常需要进行三次握手,以保证数据的可靠传输。

        2.UDP Socket:基于UDP协议,提供无连接的、不可靠的、数据报的服务。UDP协议不保证数据的可靠性,但传输速度较快,适用于实时性要求较高的场景。

4.socket在本地进程间通讯应用       

        Socket(套接字)虽然主要用于不同主机之间的网络通信,但在同一主机上,进程间通信(IPC)也可以使用Socket,尤其是当需要模拟网络环境下的通信行为时。不过,在同一主机上进行进程间通信时,更常用的方式可能包括管道、消息队列、共享内存等。

二.相关函数

1.socket()函数

        在 Linux 下使用 <sys/socket.h> 头文件中 socket() 函数来创建套接字,原型为:

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

        a.af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,AF_INET6 表示 IPv6 地址。

        b.type 为数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)。

        c.protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

2.bind()函数

        socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理。类似地,客户端也要用 connect() 函数建立连接。函数的原型为:

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

        参数sock是套接字的文件描述符,addr是指向sockaddr结构的指针,其中包含了要绑定的地址和端口信息,addrlenaddr结构的大小。示例代码如下,将创建的套接字与IP地址127.0.0.1、端口 1234 绑定:

//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

3.connect()函数

        主动发起一个连接请求,原型如下:

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

        参数sockfd是套接字的文件描述符,addr是指向sockaddr结构的指针,其中包含了服务器的地址和端口信息,addrlenaddr结构的大小。

4.listen()函数

        对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求了。函数原型为:

int listen(int sock, int backlog); 

        参数sock是套接字的文件描述符,backlog是指定的最大连接队列的长度。被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。

        当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。

5.accept()函数

        当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。它的原型为:

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

         它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

        accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。

        listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

6.数据的接收和发送

        Linux 不区分套接字文件和普通文件,使用 write() 可以向套接字中写入数据,使用 read() 可以从套接字中读取数据。

        write函数的原型为:

ssize_t write(int fd, const void *buf, size_t nbytes);

        fd 为要写入的文件的描述符,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。write() 函数会将缓冲区 buf 中的 nbytes 个字节写入文件 fd,成功则返回写入的字节数,失败则返回 -1。

        read函数的原型为:

ssize_t read(int fd, void *buf, size_t nbytes);

        fd 为要读取的文件的描述符,buf 为要读取的数据的缓冲区地址,nbytes 为要读取的数据的字节数。read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。

        recv()函数原型为:

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

        不论是客户端还是服务器都可以通过 revc()函数读取网络数据,它与 read()函数的功能是相似的。

        参数sockfd是套接字的文件描述符,buf是指向数据缓冲区的指针,len是要接收的数据长度,flags是一些控制选项,如MSG_DONTWAIT(非阻塞接收)、MSG_WAITALL(等待接收完整数据)等。

        send()函数原型为:

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

      参数sockfd是套接字的文件描述符,buf是指向数据缓冲区的指针,len是要发送的数据长度,flags是一些控制选项,如MSG_DONTWAIT(非阻塞发送)等。

7.close()函数

当不再需要套接字描述符时,可调用 close() 函数来关闭套接字,释放相应的资源。

int close(int fd);

三.TCP Socket通信

1.服务器端

        服务器端实现的功能有:创建TCP服务器,监听指定端口(8080),接受客户端连接,接收客户端消息并发送响应。代码如下所示:

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>int main() {int sockfd, newsockfd, portno;socklen_t clilen;struct sockaddr_in serv_addr, cli_addr;const int MAXDATASIZE = 1024;char buffer[MAXDATASIZE];// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {std::cerr << "Error opening socket." << std::endl;return 1;}// 设置服务器的地址和端口portno = 8080;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = INADDR_ANY;serv_addr.sin_port = htons(portno);// 绑定套接字到地址和端口if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {std::cerr << "Error on binding." << std::endl;return 1;}// 开始监听if (listen(sockfd, 5) < 0) {std::cerr << "Error on listen." << std::endl;return 1;}// 接受客户端连接clilen = sizeof(cli_addr);newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);if (newsockfd < 0) {std::cerr << "Error on accept." << std::endl;return 1;}// 接收消息memset(buffer, 0, MAXDATASIZE);ssize_t n = read(newsockfd, buffer, MAXDATASIZE - 1);if (n == -1) {std::cerr << "Error reading from socket." << std::endl;return 1;}buffer[n] = '\0';std::cout << "Message from client: " << buffer << std::endl;// 发送响应strcpy(buffer, "Message received.");write(newsockfd, buffer, strlen(buffer));// 关闭套接字close(newsockfd);close(sockfd);return 0;
}

        服务器端在main函数中的流程,如下所示:

  • 创建socket (AF_INET表示IPv4,SOCK_STREAM表示TCP)

  • 设置服务器地址结构(sockaddr_in)

  • 绑定socket到地址和端口

  • 开始监听连接

  • 接受客户端连接

  • 接收客户端消息

  • 发送响应

  • 关闭连接

2.客户端

        客户端实现的功能主要有:创建TCP客户端,连接到服务器(127.0.0.1:8080),发送消息到服务器,接收服务器响应。代码如下所示:

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>int main() {int sockfd, portno;struct sockaddr_in serv_addr;const char* message = "Hello, server!";// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {std::cerr << "Error opening socket." << std::endl;return 1;}// 设置服务器的地址和端口portno = 8080;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");serv_addr.sin_port = htons(portno);// 连接到服务器if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {std::cerr << "Error connecting." << std::endl;close(sockfd);return 1;}// 发送消息send(sockfd, message, strlen(message), 0);// 接收响应char buffer[1024];memset(buffer, 0, sizeof(buffer));recv(sockfd, buffer, sizeof(buffer) - 1, 0);std::cout << "Response from server: " << buffer << std::endl;// 关闭套接字close(sockfd);return 0;
}

        客户端在main函数中的流程,如下所示:

  • 创建socket

  • 设置服务器地址结构

  • 连接到服务器

  • 发送消息

  • 接收响应

  • 关闭连接

 3.总结及实现结果

        上述通信流程总结为以下步骤; 

  1. 首先运行服务器程序,它会:

    • 绑定到8080端口

    • 等待客户端连接

  2. 然后运行客户端程序,它会:

    • 连接到服务器的8080端口

    • 发送消息"Hello, server!"

    • 等待服务器响应

  3. 通信过程:

    • 服务器接收客户端消息并打印

    • 服务器发送固定响应"Message received."

    • 客户端接收并打印服务器响应

    • 双方关闭连接

        实现结果如下所示: 

http://www.dtcms.com/a/281893.html

相关文章:

  • 30 天自制 C++ 服务器--Day3
  • NO.6数据结构树|二叉树|满二叉树|完全二叉树|顺序存储|链式存储|先序|中序|后序|层序遍历
  • 【SpringBoot】实战-开发接口-用户-注册
  • 参数检验?非参数检验?
  • 【openbmc3】时间相关
  • 代码随想录算法训练营第五十一天|图论part2
  • 【FreeRTOS】03任务管理
  • 工业相机GigE数据接口的优势及应用
  • django安装、跨域、缓存、令牌、路由、中间件等配置
  • Jenkins全方位CI/CD实战指南
  • LabVIEW Occurrence功能
  • 嵌入式Linux(RV1126)系统定制中的编译与引导问题调试报告
  • 【RTSP从零实践】12、TCP传输H264格式RTP包(RTP_over_TCP)的RTSP服务器(附带源码)
  • 基于WebRTC技术实现一个在线课堂系统
  • el-input 回显怎么用符号¥和变量拼接展示?
  • Spring Boot 解决跨域问题
  • Spring Boot - Spring Boot 集成 MyBatis 分页实现 手写 SQL 分页
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(5):语法+单词
  • Buffer Pool
  • css 如何实现大屏4个占位 中屏2个 小屏幕1个
  • Samba服务器
  • Git版本控制完全指南:从入门到精通
  • 网络编程/Java面试/TCPUDP区别
  • 基于spring boot养老院老人健康监护平台设计与实现
  • SFT:大型语言模型专业化定制的核心技术体系——原理、创新与应用全景
  • docker run elasticsearch 报错
  • JAVA面试宝典 -《分布式ID生成器:Snowflake优化变种》
  • 详解SPFA算法-单源最短路径求解
  • C++ - 仿 RabbitMQ 实现消息队列--sqlite与gtest快速上手
  • 基于springboot+vue的酒店管理系统设计与实现