【Linux后端服务器开发】socket套接字

news2024/11/24 11:30:55

目录

一、socket 套接字概述

二、socket 函数接口

三、IP地址与端口号的网络格式

四、TCP协议的本地通信C语言示例


一、socket 套接字概述

socket 是什么?

socket 本质上是一个抽象的概念,它是一组用于网络通信的 API提供了一种统一的接口,使得应用程序可以通过网络进行通信。在不同的操作系统中,socket 的实现方式可能不同,但它们都遵循相同的规范和协议,可以实现跨平台的网络通信

socket 实现通信的原理是基于网络协议栈
当应用程序创建一个 socket 并指定协议族、类型和使用的协议后,操作系统会创建一个对应的套接字,并把它加入到协议栈中。
协议栈是一个由多个层次协议组成的网络协议体系结构,它负责对数据进行封装和解封装,并确保数据能够在网络上正确传输。

当应用程序通过 socket 发送数据时,操作系统会将数据传递给协议栈的上层协议,该协议会对数据进行封装并添加一些必要的信息,例如目标 IP地址和端口号等。然后将封装后的数据传递给下一层协议,直到数据最终被封装成一个网络包并通过网络发送到目标主机。

当目标主机收到网络包后,协议栈会对数据进行解封装,并将数据传递给操作系统中的套接字。如果该套接字是一个监听套接字,操作系统会创建一个新的套接字来处理连接请求,并将新的套接字加入到协议栈中。如果该套接字是一个已连接套接字,操作系统会将数据传递给应用程序处理。

总之,socket 实现通信的原理是基于网络协议栈,通过将数据封装成网络包并通过网络传输,实现了应用程序之间的通信。操作系统负责管理套接字和协议栈,确保数据能够正确传输。

在Linux中,socket是一种文件类型,伪文件,不占用存储空间,可进行IO操作,可间接看做文件描述符使用。

socket 通信流程图:

 

客户端与服务器工作的核心逻辑:

  1. 在客户端向服务器发送请求之前,服务器必须已经初始化完成。
  2. 客户端和服务器的初始化,都需要创建套接字socket,设置IP地址结构体信息。
  3. 服务端在设置完IP地址结构体信息之后,需要bind绑定套接字,通过listen将socket设置为监听状态
  4. 客户端通过connect向服务端发送连接请求,服务端通过accept接收客户端的连接请求,接收成功后获取新的套接字文件描述符。(TCP三次握手)
  5. 客户端发送数据——向文件描述符写入数据write,服务端接收数据——从文件描述符读出数据read,服务端回射数据write,客户端获取回射数据read。
  6. 客户端或服务端发送通信结束信号,close文件描述符,结束通信。(TCP四次挥手)

二、socket 函数接口

socket():创建套接字,返回一个可操作性的文件描述符

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

参数一:表示ip地址类型,常用的有两种

  • 其中AF_INET表示IPv4地址,比如127.0.0.1,这是一个本地 ip
  • 其中AF_INET6表示IPv6地址,比如2001:3CA1:10F:1A:121B:0:0:10

参数二:表示数据传输方式/套接字类型,常见两种

  • SOCK_DGRAM (数据报套接字/无连接的套接字,UDP)
  • SOCK_STREAM(流格式套接字/面向连接的套接字,TCP)

参数三:表示传输协议

  • 理论上前两个参数已经可以推演出采用哪种协议,可以将protocol 的值设为 0,系统自动推演出采用哪种协议

返回值:返回一个套接字(文件描述符fd)

bind():用于服务器,给sockfd套接字绑上本机地址和使用端口,确定了服务器的身份

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

参数一:套接字的fd(文件描述符),socket()函数的返回值

参数二:结构体 ip+port(端口)

参数三:结构体的字节长度

返回值:判断绑定成功失败

listen():用于服务器,使socket处于监听模式,监听时候有客户端连接,并放入队列(同时设置与服务器建立连接的上限)

int listen(int sockfd, int backlog);

参数一:bind绑定ip和端口的套接字

参数二:请求链接客户端队列的最大存放数目

返回值:判断监听成功失败

accept():用于服务器,接收一个客户端的连接请求,并返回连接客户端的套接字便于IO操作,如果没有客户连接会阻塞等待

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

参数一:服务器的套接字(也叫监听套接字),表明了自己的身份

参数二:传出参数,跟我建立连接的客户端的结构体(内含客户端ip+端口)

参数三:结构体长度的指针 &sizeof()

返回值:连接客户端的套接字

connect():用于客户端,向远端服务器发送连接请求

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

参数一:传入参数,客户端对服务器进行IO操作的文件描述符

参数二:绑定需要连接的服务器的结构体(需要初始化绑上ip和端口),表明目的

参数三:结构体的长度

三、IP地址与端口号的网络格式

 

IP地址

  • IP一般由32位整数组成,按每8位划分为4部分:255.255.255.255 该显示方式为字符串形式,而IP一般是以整数形式显示。
  • 整数IP地址 unsigned int IP_Addr = 1713350848 转化为二进制为:01100110-00011111-10101000-11000000 根据8位划分得到结果为102-31-168-192,由于网络字节倒序的问题,实际IP为192.168.31.102
  • 在网络通信中,我们输入的是字符串风格的IP地址字符串,这时需要我们将IP地址字符串转换成 uint32_t 的类型进行网络通信,建议直接用库函数 inet_addr(const char* ip) 进行转换。
  • 对于服务器而言,bind绑定套接字的时候,IP地址可用 htonl(INADDR_ANY) 进行任意地址绑定。
// 整数风格 uint32_t 转 字符串风格 ip:
// uint32_t ip;
// struct _ip {
//     unsigned char p1;
//     unsigned char p2;
//     unsigned char p3;
//     unsigned char p4;
// };
// std::string strip = to_string(((struct _ip*)&ip)->p1) + to_string(((struct _ip*)&ip)->p2) +
//                     to_string(((struct _ip*)&ip)->p3) + to_string(((struct _ip*)&ip)->p4);
//
// 字符串风格 转 整数风格:
// 整数风格的ip地址存储方式,占用空间更小,网络通信都使用 uint32_t 的ip格式
// 系统提供的转换方式:inet_addr(const char* ip)
// 任意地址绑定:htonl(INADDR_ANY)

端口号port:

  • 端口号port的数据类型是uint16_t,但是在网络通信中的类型是in_port_t,这其实是uint16_t的重命名,但我们还是需要将port端口号从主机格式转换为网络格式
  • 通过 htons(port) 将端口号信息绑定到 struct sockaddr_in 结构体中。

四、TCP协议的本地通信C语言示例

server.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int main(int args, char* argv[])
{
    if (args != 2)
    {
        printf("Usage:server port\n");
        exit(1);
    }
    uint16_t port = atoi(argv[1]);  // 启动server的时候指定端口号

    // 1. 创建套接字
    int s_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (s_socket < 0)
        exit(1);

    // 2. 绑定套接字
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = htonl(INADDR_ANY);  // Address to accept any incomint message ---> 任意地址绑定
    local.sin_port = htons(port);               // 主机转网络

    if (bind(s_socket, (struct sockaddr*)&local, sizeof(local)) < 0)
        exit(1);
    
    // 3. 监听
    if (listen(s_socket, 5) < 0)
        exit(1);

    // 4. 阻塞等待客户端的连接请求
    struct sockaddr_in peer;
    socklen_t peer_len = sizeof(peer);

    int new_sock = accept(s_socket, (struct sockaddr*)&peer, &peer_len);
    if (new_sock < 0)
        exit(1);

    // 5. 连接成功,面向字节流通信
    while (1)
    {
        char buf[1024];
        int data_len = read(new_sock, buf, sizeof(buf));
        if (data_len == 0)
            break;
        buf[data_len] = 0;
        printf("recv message: %s\n", buf);

        // 6. 发送回射信息(应答数据)
        char out_buf[1024];
        snprintf(out_buf, sizeof(out_buf), "已收到数据: %s\n", buf);
        write(new_sock, out_buf, sizeof(out_buf));
    }

    // 7. 结束连接
    close(s_socket);

    return 0;
}

client.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int args, char* argv[])
{
    if (args != 3)
    {
        printf("Usage: client server_ip server_port\n");
        exit(1);
    }
    char* s_ip = argv[1];
    uint16_t s_port = atoi(argv[2]);

    // 1. 创建套接字
    int c_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (c_socket < 0)
        exit(1);

    // client其实也需要bind绑定,不过这一步不需显式绑定(由OS随机指定)

    // 2. 发送连接请求
    struct sockaddr_in server;
    socklen_t s_len = sizeof(server);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(s_ip);
    server.sin_port = htons(s_port);

    if (connect(c_socket, (struct sockaddr*)&server, s_len) != 0)
    {
        printf("connect fail\n");
        exit(1);
    }
    
    // 3. 建立连接成功,面向字节流通信
    while (1)
    {
        char buf[1024];
        printf("Enter: ");
        gets(buf);
        write(c_socket, buf, sizeof(buf));

        // 4. 获取服务器的应答数据
        char recv_buf[1024];
        int data_len = read(c_socket, recv_buf, sizeof(recv_buf));
        if (data_len == 0)
            break;
        recv_buf[data_len] = 0;
        printf("%s", recv_buf);
    }

    // 5. 结束通信
    close(c_socket);

    return 0;
}

 

先启动服务器,再启动客户端,运行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/748036.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

宝塔部署前后端分离项目

✅作者简介&#xff1a;大家好&#xff0c;我是Cisyam&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Cisyam-Shark的博客 &#x1f49e;当前专栏&#xff1a; 项目部署 ✨特色专栏&…

【数据结构导论】第 5 章:图

目录 一、图的基本概念 &#xff08;1&#xff09;图的定义 &#xff08;2&#xff09;图的基本术语 &#xff08;3&#xff09;图的基本运算 二、图的存储结构 &#xff08;1&#xff09;邻接矩阵 ① 图的邻接矩阵 ② 带权图(网)的邻接矩阵 ③ 邻接矩阵的类型定…

【UE4】在控件蓝图上播放视频

UE版本&#xff1a;4.26 在上一篇文章中&#xff08;【UE】场景内播放视频、音频&#xff09;介绍了如何在场景中播放视频&#xff0c;本篇文章将介绍如何在UI上播放视频 效果 步骤 1. 首先在“Content”文件夹中新建一个名为“Movies”的文件夹 2. 在文件夹中随便添加一个.…

iManager for K8S 站点定制(以MongoDB为例)

作者&#xff1a;ls 目录 背景前期准备实现效果实现过程附录YAML中的属性配置占位符列表 背景 SuperMap iManager支持一键创建用户定制的站点&#xff0c;可将已添加的站点模板创建为站点环境&#xff0c;并通过站点使用应用。   定制站点与其他站点相同&#xff0c;在监管方…

学习PostgreSQL的优势

学习 PostgreSQL 可以为您打开许多就业机会。 PostgreSQL 是一种强大的关系型数据库管理系统&#xff0c;被广泛用于企业和组织中的数据管理和应用程序开发。 以下是一些学习 PostgreSQL 可能帮助您找到的工作领域&#xff1a; **1.数据库管理员&#xff1a;**作为 PostgreSQ…

负载均衡详解

负载均衡可以简单分为服务端负载均衡和客户端负载均衡这两种。 根据 OSI 模型&#xff0c;服务端负载均衡还可以分为&#xff1a; 二层负载均衡三层负载均衡四层负载均衡七层负载均衡 最常见的是四层和七层负载均衡 四层负载均衡 工作在 OSI 模型第四层&#xff0c;也就是传…

TIA博途中FC或FB块被多次调用后,监控单个块执行情况的具体方法

TIA博途中FC或FB块被多次调用后,监控单个块执行情况的具体方法 本文以简单的电机启保停程序为例进行说明: 如下图所示,首先添加一个“启保停”FC块,定义块的接口变量,并编写梯形图程序, 如下图所示,在PLC数据类型中添加一个motorControl数据类型,其中包含start、stop…

Python replace()函数使用详解,Python替换字符串

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 replace函数使用详解 1、不改变原字符串2、指定替换次数3、转义符4、替换列表、元…

opencv 基础图像操作-彩色图像

opencv 基础图像操作-彩色图像 彩色图像 相比二值图像和灰度图像&#xff0c;彩色图像是更常见的一类图像&#xff0c;它能表现更丰富的细节信息。 神经生理学实验发现&#xff0c;在视网膜上存在三种不同的颜色感受器&#xff0c;能够感受三种不同的颜色&#xff1a;红色、绿色…

边缘计算在智慧校园应用,实现校园智能化管理

随着科技的发展和互联网技术进步&#xff0c;校园管理正逐步实现数字化、智能化转型。边缘计算作为一种新兴技术&#xff0c;通过在离数据源较近的地方进行数据处理&#xff0c;实现了实时性分析与响应&#xff0c;为校园带来了更智能、安全的管理方式。 学生学习状态监控 AI动…

LCD1602屏幕简介(全网最详细教程)

目录 1.接线说明 2.LCD1602显示原理 3.LCD1602时序分析 4.LCD1602显示一个字符 5.LCD1602显示一行 1.接线说明 第1引脚&#xff1a;GND为电源地 第2引脚&#xff1a;VCC接5V电源正极 第3引脚&#xff1a;V0为液晶显示器对比度调整端&#xff0c;接正电源时对比度最弱&…

关于海外的Apple搜索广告

随着Apple平台成为大多数应用的服务支柱&#xff0c;我们需要比以往任何时候都更加关注iOS搜索广告&#xff0c;从而成功与用户建立联系。Apple Search Ads能够通过搜索为我们的应用带来流量&#xff0c;让用户在App Store中输入相关关键词时能够高效、简单地发现应用。 Apple …

超详细的学习笔记:CSS浮动(附代码示例)

笔记参考b站网课&#xff1a;【前端开发入门教程&#xff0c;web前端零基础html5 css3前端项目视频教程】https://www.bilibili.com/video/BV1Kg411T7t9?p124&vd_source06e5549bf018e111f4275c259292d0da 目录 一、结构伪类选择器 二、伪元素 三、标准流 四、浮动 1、…

WEB APIs day3 (1)

一、表单全选反选案例 <!DOCTYPE html><html><head lang"en"><meta charset"UTF-8"><title>全选反选案例</title><style>* {margin: 0;padding: 0;}table {border-collapse: collapse;border-spacing: 0;border…

【ROS2】仿真入门

一、说明 在机器人项目中,仿真是一个具有多种用途的重要方面。首先,您可以测试希望机器人执行的行为代码。其次,您可以使用仿真来测试不同类型的硬件,例如距离传感器、相机或 3D 点云传感器,看看哪种效果最好。第三,可视化模拟的相同软件可以与真正的机器人实时使用,在机…

基于51单片机和proteus的智能垃圾桶系统

此系统是基于51单片机和proteus的仿真设计&#xff0c;功能如下&#xff1a; 1. LCD1602实时显示系统状态。 2. 超声波测距模拟检测人体靠近垃圾桶 3. 舵机模拟开启或关闭垃圾桶桶盖。 4. 垃圾桶满溢后报警指示。 5. LED指示人体状态满溢状态及系统状态。 功能框图如下&am…

BTP Integration Suite学习笔记 - (Unit2) Developing with SAP Integration Suite

BTP Integration Suite学习笔记 - (Unit1) Developing with SAP Integration Suite 这章内容比较大而空 Unit2 iPaaS介绍 2.1 SAP的集成策略 这张图应该不陌生&#xff0c;很多地方都可以看到&#xff0c;SAP对于智能企业的集成策略。它有四个原则&#xff1a; Predefined in…

剑指 offer 动态规划算法题:斐波那契数列(青蛙普通跳台阶)

题目描述&#xff1a;写一个函数&#xff0c;输入 n &#xff0c;求斐波那契&#xff08;Fibonacci&#xff09;数列的第 n 项&#xff08;即 F(N)&#xff09;。斐波那契数列的定义如下&#xff1a;F(0) 0, F(1) 1&#xff0c;F(N) F(N - 1) F(N - 2), 其中 N > 1.斐…

wordpress 导航栏 调用

环境&#xff1a;wordpress6、twentytwentyone模板 一、wp-content/themes/twentytwentyone/functions.php 添加以下代码&#xff1a; 1、注册 (左边是别名&#xff0c;右边是名称。别名会用在导航栏的调用上&#xff0c;名称则显示在菜单后台页面上&#xff1a;外观->菜单…

解密Sketch文件打开秘籍:简单两步操作!

虽然Figma&#xff0c;sketch,xd都很好用&#xff0c;但是设计师在设计工作流中经常会遇到无法在这三者软件中自由导入导出的情况。但是只要我们转变一下思路&#xff0c;因为这三种软件都支持导入sketch格式,所以我们只要将文件格式转成sketch&#xff0c;就能自由的在不同软件…