服务器开发 Socket 相关基础

news2024/11/24 18:57:48

Socket 三要素

1.通信的目的地址;
2.使用的端口号;
3.使用的传输层协议(如 TCP、UDP)

Socket 通信模型

在这里插入图片描述

服务端实现

#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>


#define SERVER_PORT 8089


using namespace std;

int main(int argc, char const *argv[])
{
    int sock;   // mailbox
    struct sockaddr_in server_addr;
    

    //Create a mailbox
    sock = socket(AF_INET, SOCK_STREAM, 0);


    // clear tags, write addresses and port
    bzero(&server_addr, sizeof(server_addr));

    server_addr.sin_family = AF_INET;                //选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址
    server_addr.sin_port = htons(SERVER_PORT);       //绑定端口号
                        

    // label affixed to the receiving mailbox
    bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
    
    listen(sock, 128);  //the number of clients

    // ready to mail
    cout<<"wait for client"<<endl;

    int done = 1;

    while(done){
        struct sockaddr_in client;
        int client_socket, len;
        char client_ip[64];
        char buf[256];

        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        client_socket = accept(sock, (struct sockaddr *)&client, &client_addr_len);

        // print the IP address amd port of client
        cout<<"client IP: "<<inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip))
            <<"  port: "<<ntohs(client.sin_port)<<endl;


        //read client->message
        len = read(client_socket, buf, sizeof(buf)-1);
        buf[len] = '\0';
        cout<<"receive:  "<< buf  <<"  len:"<<len <<endl;

        len = write(client_socket, buf, len);
        cout<<"write finished. len: "<<len<<endl;
        close(client_socket);
    }

    
    return 0;
}

客户端实现

#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>

using namespace std;

#define SERVER_PORT 8089
#define SERVER_IP "127.0.0.1"

int main(int argc, const char* argv[]) {
    int sockfd;
    const char *message;
    struct  sockaddr_in servaddr;
    int n;
    char buf[64];

    if(argc!=2){
        fputs("Usage: ./echo_client message \n", stderr);
        exit(1);
    }

    message = argv[1];

    cout<<"message: "<< message <<endl;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&servaddr, '\0', sizeof(struct sockaddr_in));

    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
    servaddr.sin_port = htons(SERVER_PORT);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    write(sockfd, message, strlen(message));

    n = read(sockfd, buf, sizeof(buf)-1);

    if (n > 0)
    {
        buf[n] = '\0';
        printf("receive: %s\n", buf);
    }
    else
    {
        perror("error!!!");
    }

    printf("finished.\n");
    close(sockfd);


    return 0;
}

在这里插入图片描述

socket 连接过程(TCP)

在这里插入图片描述

服务器端:
1.首先调用 socket() 函数,创建网络协议为 IPv4,以及传输协议为 TCP 的 Socket ,接着调用 bind() 函数,给这个 Socket 绑定一个 IP 地址和端口;

绑定端口的目的:当内核收到 TCP 报文,通过 TCP 头里面的端口号,来找到我们的应用程序,然后把数据传递给我们。
绑定 IP 地址的目的:一台机器是可以有多个网卡的,每个网卡都有对应的 IP 地址,当绑定一个网卡时,内核在收到该网卡上的包,才会发给我们;

2.绑定完 IP 地址和端口后,就可以调用 listen() 函数进行监听,此时对应 TCP 状态图中的 listen,如果我们要判定服务器中一个网络程序有没有启动,可以通过 netstat 命令查看对应的端口号是否有被监听。
3.服务端进入了监听状态后,通过调用 accept() 函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。

客户端:
客户端在创建好 Socket 后,调用 connect() 函数发起连接,该函数的参数要指明服务端的 IP 地址和端口号,然后 TCP 三次握手。

在 TCP 连接的过程中,服务器的内核实际上为每个 Socket 维护了两个队列:
一个是「还没完全建立」连接的队列,称为 TCP 半连接队列,这个队列都是没有完成三次握手的连接,此时服务端处于 syn_rcvd 的状态;
一个是「已经建立」连接的队列,称为 TCP 全连接队列,这个队列都是完成了三次握手的连接,此时服务端处于 established 状态;

当 TCP 全连接队列不为空后,服务端的 accept() 函数,就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket。

监听的 Socket 和真正用来传数据的 Socket 是两个:
一个叫作监听 Socket;
一个叫作已连接 Socket;

连接建立后,客户端和服务端就开始相互传输数据了,双方都可以通过 read()write() 函数来读写数据。
至此, TCP 协议的 Socket 程序的调用过程就结束了。

小林coding

套接字概念

Socket 在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。
既然是文件,那么可以使用文件描述符引用套接字。
Linux系统将其封装成文件的目的是为了统接口,使得读写套接字和读写文件的操作一致。
区别是文件主要应用于本地持久化数据的读写,而套接字多应用于网络进程间数据的传递

创建 Socket 的时候,可以指定网络层使用的是 IPv4 还是 IPv6,传输层使用的是 TCP 还是 UDP.

在TCP/IP协议中,“IP 地址+TCP或UDP端口号”唯-标识网络通讯中的一个进程。
“IP 地址+端口号”就对应一个 sockct,欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一 个连接。 因此可以用Sockst来描述网络连接的一对一 关系。
在这里插入图片描述
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。

Socket 编程基础

网络字节序(大小端)

网络传输中要多字节数据要进行主机/网络字节序转换的原因
网络字节序中,发送端发送的第一个字节是高位字节(取自内存的低地址),接收端收到后存入低位地址,由于主机的CPU架构不同,导致不同主机的字节序有所不同,因此对于多字节数据,在网络传输中需要进行主机/网络字节序的转换,接收端/发送端 才能正确的解析出多字节数据。

网络字节序(大端字节序)的定义:规定使用大端字节序(网络字节序)作为标准:接收端接收到的第一个字节是发送端的高位字节,存放到低位地址。
大端字节序的主机:按照从左往右的字节顺序将数据存储在内存中,低位字节存储在高位地址,高位字节存储在低位地址。
小端字节序的主机:按照从左往右的字节顺序将数据存储在内存中,低位字节存储在低位地址,高位字节存储在高位地址。

网络字节序
由于不同的主机架构,字节序有所不同,网络通信中,规定使用大端字节序(网络字节序)作为标准:接收端接收到的第一个字节是发送端的高位字节,存放到低位地址。因此需要发送端/接收端进行主机字节序和网络字节序的转换,以确保数据的正确传输和解析。

发送端:
发送的第一个字节是高位字节(取自内存中的低位地址),因此多字节数据在进行网络数据传输时,需要将主机字节序转为网络字节序。

接收端:
接收的第一个字节(高位字节)存放在低位地址。因此,需要将网络字节序转为主机字节序。

在这里插入图片描述

htonl()htons() /* 主机字节序转化为网络字节序 */
ntohl()ntohs() /* 网络字节序转化为主机字节序 */

sockaddr 数据结构

很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr 退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。

在这里插入图片描述
通用套接字地址格式:

/* POSIX.1g 规范规定了地址族为2字节的值.  */
typedef unsigned short int sa_family_t;
/* 描述通用套接字地址  */
struct sockaddr{
    sa_family_t sa_family;  /* 地址族.  16-bit*/
    char sa_data[14];   /* 具体的地址值 112-bit */
}; 

IPv4 套接字格式地址:

/* IPV4套接字地址,32bit值.  */
typedef uint32_t in_addr_t;
struct in_addr
{
	in_addr_t s_addr;
};
  
/* 描述IPV4的套接字地址格式  */
struct sockaddr_in
{
 	sa_family_t sin_family; 	/* 16-bit */
 	in_port_t sin_port;    		/* 端口口  16-bit*/
  	struct in_addr sin_addr;    /* Internet address. 32-bit */


  	/* 这里仅仅用作占位符,不做实际用处  */
  	unsigned char sin_zero[8];
};

IPv4的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,但是sock API的实现早于ANSI C标准化,那时还没有void *类型,因此这些像bind 、accept函数的参数都用 struct sockaddr *类型表示,在传递参数之前要强制类型转换一下:

struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));		/* initialize servaddr */

IPv6 套接字地址格式:

struct sockaddr_in6
{
	sa_family_t sin6_family; 	/* 16-bit */
	in_port_t sin6_port;  		/* 传输端口号 # 16-bit */
	uint32_t sin6_flowinfo; 	/* IPv6流控信息 32-bit*/
	struct in6_addr sin6_addr;  /* IPv6地址128-bit */
	uint32_t sin6_scope_id; 	/* IPv6域ID 32-bit */
};

整个结构体长度是 28 个字节,其中流控信息和域 ID 先不用管,这两个字段,一个在 glibc 的官网上根本没出现,另一个是当前未使用的字段。这里的地址族显然应该是 AF_INET6,端口同 IPv4 地址一样,关键的地址从 32 位升级到 128 位,这个数字就大到恐怖了,完全解决了寻址数字不够的问题。

本地套接字地址格式:

struct sockaddr_un {
    unsigned short sun_family; 	/* 固定为 AF_LOCAL */
    char sun_path[108];   		/* 路径名 */
};

各种套接字对比分析:
在这里插入图片描述

地址族字段详解
地址族字段,它表示使用什么样的方式对地址进行解释和保存。地址族在 glibc 里的定义非常多,常用的有以下几种:

AF_LOCAL:表示的是本地地址,对应的是 Unix 套接字,这种情况一般用于本地 socket 通信,很多情况下也可以写成AF_UNIXAF_FILE
AF_INET:因特网使用的 IPv4 地址;
AF_INET6:因特网使用的 IPv6 地址。

这里的 AF_ 表示的含义是 Address Family,但是很多情况下,我们也会看到以 PF_ 表示的宏,比如 PF_INETPF_INET6 等,实际上 PF_ 的意思是 Protocol Family,也就是协议族的意思。我们用 AF_xxx 这样的值来初始化 socket 地址,用 PF_xxx 这样的值来初始化 socket。我们在 <sys/socket.h> 头文件中可以清晰地看到,这两个值本身就是一一对应的。

/* 各种地址族的宏定义  */
#define AF_UNSPEC PF_UNSPEC
#define AF_LOCAL  PF_LOCAL
#define AF_UNIX   PF_UNIX
#define AF_FILE   PF_FILE
#define AF_INET   PF_INET
#define AF_AX25   PF_AX25
#define AF_IPX    PF_IPX
#define AF_APPLETALK  PF_APPLETALK
#define AF_NETROM PF_NETROM
#define AF_BRIDGE PF_BRIDGE
#define AF_ATMPVC PF_ATMPVC
#define AF_X25    PF_X25
#define AF_INET6  PF_INET6

IP 地址转换函数

#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);

该函数将一个点分十进制串转换为一个二进制的网络字节顺序的IP地址。如果src没有指向一个合法的点分十进制字符串,那么该函数返回0。成功返回1,失败返回-1。void *dst用于存放转换后的网络字节序的IP地址。

const char* inet_ntop(int af, const void *src, char *dst, socklen_t size);

该函数将一个二进制的网络字节顺序的IP地址转换为它对应的点分十进制的字符串,并把得到的以null结尾的字符串复制到dst。成功返回指向点分十进制的指针,失败返回NULL
其中af代表地址类型,const void *src是需要被转换的网络字节序的IP地址;char *dst用于存放转换后的字符串类型的IP地址;socklen_t size代表数组char *dst的长度。

af 取值可选为 AF_INETAF_INET6, 即对应 ipv4 和 ipv6 对应。
其中 inet_ptoninet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr.
因此函数接口是 void *addrptr

int inet_aton(const char *string, struct in_addr* addr);

输入参数string包含ASCII表示的IP地址, 输出参数addr是将要用新的IP地址更新的结构。如果输入地址不正确,则返回0;如果成功,返回非零;如果失败,返回-1

char *inet_ntoa(struct in_addr in);

该函数在内部申请了一块空间保存返回的点分十进制的IP地址。因为返回的结果放到静态存储区,所以不需要手动释放。

in_addr_t inet_addr(const char *str);

该函数可将点分十进制的IP地址转换为无符号长整型。

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

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

相关文章

【QT】pro文件里添加又删除LIBS不影响运行的原因

我发现个问题啊&#xff0c;如果运行项目&#xff0c;发现报错&#xff0c;缺少某dll&#xff0c;接着你在pro文件里加上win32:LIBS -lOpengl32&#xff08;举个例子&#xff09;&#xff0c;接着可以运行了&#xff0c;接着把这行删掉&#xff0c;再运行&#xff0c;仍然可以…

hive 数据库表常用操作及相关函数讲解

创建数据库并指定hdfs存储位置 create database myhive2 location ‘/myhive2’; 使用location关键字&#xff0c;可以指定数据库在HDFS的存储路径。 Hive的库在HDFS上就是一个以.db结尾的目录 默认存储在&#xff1a; /user/hive/warehouse内 当你为Hive表指定一个LOCATION时…

数据库(mysql)-连接嵌套查询-2

子查询 MySQL中的子查询&#xff08;Subquery&#xff09;是嵌套在其他SQL查询中的查询。子查询可以出现在SELECT、FROM或WHERE子句中&#xff0c;并用于返回将被用于外部查询的数据。子查询的结果可以是一个单一的值、一行、一列或多行多列的数据集。 单行单列查询 实例 #查…

如何提高旋转花键运行稳定性?

现代化精密仪器设备中&#xff0c;精密仪器的稳定工作性能对于生产效率和产品质量至关重要&#xff0c;运行效率和精度是常见问题。旋转花键作为机械传动系统中的重要组成部分&#xff0c;其稳定性也是直接影响到机械装配的质量和使用寿命&#xff0c;那么我们应该如何提升旋转…

【汇编语言实战】已知10个整数求最大值

C语言描述该程序流程&#xff1a; #include <stdio.h> int main() {int a[]{11,33,23,54,12,51,2,4,34,45};int maxa[0];for(int i1;i<9;i){if(a[i]>max){maxa[i];}}printf("%d",max); }汇编语言&#xff1a; include irvine32.inc .data arr dword 11…

STM32G030F6P6 HSE时钟不能使用无源晶振,只能使用有源晶振!

STM32G030F6P6 HSE时钟不能使用无源晶振&#xff0c;只能使用有源晶振。 参见STM32CubeMX配置 使能RCC中 BYPASS CLOCK SOURCE后只有一个 PC14引脚。 查手册中 5.2.1 HSE clock章节 部分引脚少的封装&#xff0c;HSE时钟只有 OSC-IN&#xff0c;因此只能使用有源晶振 查Data…

经典机器学习模型(八)梯度提升树GBDT详解

经典机器学习模型(八)梯度提升树GBDT详解 Boosting、Bagging和Stacking是集成学习(Ensemble Learning)的三种主要方法。 Boosting是一族可将弱学习器提升为强学习器的算法&#xff0c;不同于Bagging、Stacking方法&#xff0c;Boosting训练过程为串联方式&#xff0c;弱学习器…

2024中国航空航天暨无人机展览会8月在重庆举办

2024中国航空航天暨无人机展览会8月在重庆举办 邀请函 主办单位&#xff1a; 中国航空学会 重庆市南岸区人民政府 招商执行单位&#xff1a; 重庆港华展览有限公司 展会背景&#xff1a; 为更好的培养航空航天产业人才&#xff0c;汇聚航空教育产业创新科技&#xff0c;…

IO流的基础详解

文件【1】File类&#xff1a; 封装文件/目录的各种信息&#xff0c;对目录/文件进行操作&#xff0c;但是我们不可以获取到文件/目录中的内容。 【2】引入&#xff1a;IO流&#xff1a; I/O &#xff1a; Input/Output的缩写&#xff0c;用于处理设备之间的数据的传输。 【3】…

Terraform 扩展

Terraform 扩展 Terraform Meta-Arguments 元参数 count 创建相似的资源for_each 创建相似的资源depends_on 定义资源或者模块的依赖provider 定义provider选项lifecycle 资源的生命周期行为 参数使用范围备注countresource module适用于创建多个相似的资源&#xff0c;使用…

Redis 缓存穿透、缓存击穿、缓存雪崩区别和解决方案

缓存穿透 什么是缓存穿透&#xff1f; 缓存穿透说简单点就是大量请求的 key 是不合理的&#xff0c;根本不存在于缓存中&#xff0c;也不存在于数据库中 。这就导致这些请求直接到了数据库上&#xff0c;根本没有经过缓存这一层&#xff0c;对数据库造成了巨大的压力&#xf…

读书笔记之人生算法(7)

孤独、爆仓与迷信 跨越出身和运气&#xff0c;实现富足与自由&#xff0c;用概率思维做好决策 13 孤独 孤独&#xff1a;获得好姻缘的算法 姻缘是奇妙的东西&#xff0c;体现了世界的随机性&#xff1a;即使是最理性的人&#xff0c;也可能需要靠运气寻找另一半。 中国有句古话…

Hot100【十一】:最大子数组和

// 定义dp&#xff1a;以i结尾的最大子数组和 dp[i] max(dp[i-1] nums[i],nums[i]) class Solution {public int maxSubArray(int[] nums) {// 1. 不需要特殊处理// 2. 定义dpint[] dp new int[nums.length];dp[0] nums[0];int maxResult nums[0];// 3. dp递推for (int i …

onSaveInstanceState()与onRestoreInstanceState()

目录 1.二者作用 2.onSaveInstanceState调用时机 2.1 五种情况 前4种情况Activity生命周期&#xff1a; 2.2 注意事项&#xff1a;确定会被系统回收并销毁&#xff0c;不会调用此方法 两个例子 3.onRestoreInstanceState调用时机 3.1实例——屏幕切换生命周期 3.2 极端…

https访问http的minio 图片展示不出来

问题描述&#xff1a;请求到的图片地址单独访问能显示&#xff0c;但是在网页中展示不出来 原因&#xff1a;https中直接访问http是不行的&#xff0c;需要用nginx再转发一下 nginx配置如下&#xff08;注意&#xff1a;9000是minio默认端口&#xff0c;已经占用&#xff0c;…

RobotFramework教程2——第一个例子(hello world)

前言&#xff1a;安装好RF框架后&#xff0c;我们现在开始创建并执行我们的第一个测试用例&#xff0c;第一个例子当然是用入门经典“hello world”啦&#xff01;运行也会有坑&#xff0c;注意避开&#xff0c;最新的东西不一定是现阶段最好的&#xff0c;稳定可运行才关键&am…

halcon缺陷检测-印刷品检测(差异化模型),键盘字符缺陷检测

前言 在实际项目中&#xff0c;印刷品缺陷检测是缺陷检测中的难点项目。通常印刷品检测往往具备缺陷小&#xff0c;缺陷所在位置不固定&#xff0c;出现少印或者多印的情况。并且由于产线原因&#xff0c;大量的印刷品在视野中的大小并不是完全一致的&#xff0c;可能出现细微…

《高通量测序技术》分享,生物信息学生信流程的性能验证,以肿瘤NGS基因检测为例。

这是这本书&#xff0c;第四章第五节的内容&#xff0c;这一部分是以NGS检测肿瘤基因突变为例&#xff0c;描述了其原理和大概流程&#xff0c;这和以前我分享的病原宏基因组高通量测序性能确认方案可以互相补充&#xff0c;大家可以都看一下&#xff0c;但是想要真正的弄懂&am…

AI应用实战2:使用scikit-learn进行回归任务实战

代码仓库在gitlab&#xff0c;本博客对应于02文件夹。 1.问题分析 在此篇博客中我们来对回归任务进行实战演练&#xff0c;背景是直播带货平台的业绩预测。第一步&#xff0c;就是分析问题。 问题痛点&#xff1a; 在直播带货平台上&#xff0c;由于市场环境多变、用户行为复…

SHOPFA:APP定制开发的哪种二开项目容易交付,哪些不可以接?

在商城系统开发领域&#xff0c;定制开发与二次开发&#xff08;二开&#xff09;是两种截然不同的项目类型。它们之间的主要差异体现在项目起点、灵活性、成本、时间以及风险等多个方面。 一、项目起点 商城定制开发通常是从零开始&#xff0c;根据客户的实际需求&#xff0c…