C语言----详解socket通信

一:什么是socket

        刚接触socket的同学想必也知道socket的中文名,套接字,与其说是中文名倒不如说这是什么玩意,我们先不要管中文名的实际意义,我们先来了解一下什么是socket。

        我们上网产生的数据都是经过协议栈一层一层的封装然后经网卡发送到网络,经网络发送到服务端,然后服务端又是一层一层的解封装拿到自己想要的数据。

        对于协议栈都是集成在操作系统里,我们并不需要关心TCP,UDP等这些协议是如何实现的,我们关心的是我们的应用程序的数据能不能正常的发送出去和接收服务端发回来的数据。这就需要一个桥梁,一端连接操作系统的协议栈,一端连接用户的应用数据。socket就是这个桥梁。

        那我们再来理解一下中文名套接字,看了一圈我最赞同的解释是:套接指的是套接管,就是将两根水管套接起来的管子,然后“字”是此连接的数据标识,即一个WORD,所以套接字就是一个标识连接的数据体。

        那有的同学有疑问WORD是啥,在linux等系统中“套接字”对应“socket word”,所以“字”也就是对应“word”,这个“word”可能指储存socket的数据标识,因为端口号是两字节,就是一个WORD

        下边的图就很具体,没有上面那么抽象

        对于套接字的解释就到这了,实在编不下去了

二:socket通信流程

        如TCP的连接流程一样,TCP建链需要三次握手,TCP拆链需要四次挥手,socket通信也有自己的一套流程。

对于客户端:

1,创建一个用于通信的套接字(fd)

2,连接服务器,需要指定连接的服务器的IP 和 端口

3,建立连接成功,客户端和服务器建立连接通道

        1>可以发送数据

        2>可以接收数据

4,通信结束,断开连接

对于服务端:

1,创建一个用于监听的套接字

        1>监听:监听有客户端的连接

        2>套接字:这个套接字其实就是一个文件描述符

2,将这个监听文件描述符和本地的IP和端口绑定(IP和端口就是服务器的地址信息)

        1>客户端连接服务器的时候使用的就是这个IP和端口

3,设置监听,监听的fd开始工作

4,阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字 (fd)

5,服务端和客户端通信

        1>接收数据

        2>发送数据

6,通信结束,断开连接

三:socket通信函数详解

1,socket()函数

int socket(int domain, int type, int protocol); - 功能:创建一个套接字 - 参数: - domain: 协议族 AF_INET : ipv4 AF_INET6 : ipv6 AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信) - type: 通信过程中使用的协议类型 SOCK_STREAM : 流式协议 SOCK_DGRAM : 报式协议 - protocol : 具体的一个协议。一般写0 - SOCK_STREAM : 流式协议默认使用 TCP - SOCK_DGRAM : 报式协议默认使用 UDP - 返回值: - 成功:返回文件描述符,操作的就是内核缓冲区。 - 失败:-1

注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

2,bind()函数

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命 名 - 功能:绑定,将fd 和本地的IP + 端口进行绑定 - 参数: - sockfd : 通过socket函数得到的文件描述符 - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息 - addrlen : 第二个参数结构体占的内存大小 

        bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

3,listen()函数

int listen(int sockfd, int backlog);      // /proc/sys/net/core/somaxconn - 功能:监听这个socket上的连接 - 参数: - sockfd : 通过socket()函数得到的文件描述符 - backlog : 未连接的和已经连接的和的最大值, 5 

        作为服务端需要时刻监听是否有客户端发来的数据,服务端就是调用listen()来监听建立的socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。

4,connect()函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);- 功能: 客户端连接服务器 - 参数: - sockfd : 用于通信的文件描述符 - addr : 客户端要连接的服务器的地址信息 - addrlen : 第二个参数的内存大小 - 返回值:成功 0, 失败 -1 

 客户端通过调用connect函数来建立与TCP服务器的连接

5,accept()函数

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); - 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接 - 参数: - sockfd : 用于监听的文件描述符 - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)- addrlen : 指定第二个参数的对应的内存大小 - 返回值: - 成功 :用于通信的文件描述符 - -1 : 失败 

        服务器侧在调用socket()、bind()、listen()之后,就会监听指定的socket地址了。客户端在调用socket()、connect()之后就建立了一条连接通道并发向服务端发送一个请求,服务器监听到这个请求之后,就会调用accept()函数取接收请求。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作

6,read()、write()函数

ssize_t write(int fd, const void *buf, size_t count);      // 写数据 
ssize_t read(int fd, void *buf, size_t count);                // 读数据

        read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。

write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节 数。失败时返回-1,并设置errno变量。在网络程序中,当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0,表示写了部分或者是 全部的数据。2)返回的值小于0,此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示 网络连接出现了问题(对方已经关闭了连接)。

        网络I/O操作不止是read()/write()函数,下面几组也是的

read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()

其申明如下:

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

7,close()函数

int close(int fd);

        close一个socket连接后会立即返回到调用进程。该描述字不能再由调用进程使用,也就是说不能再作为read或write的第一个参数

注意:close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求

四:socket通信实战

客户端:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>#define SERVER_PORT 8888int main()
{//客户端只需要一个套接字文件描述符,用于和服务器通信int serverSocket;//描述服务器的socketstruct sockaddr_in serverAddr;char sendbuf[200]; //存储 发送的信息 char recvbuf[200]; //存储 接收到的信息 int iDataNum;/*********************************************************************//*                          1-创建客户端套接字                        *//*********************************************************************/if((serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0){perror("socket");return 1;}serverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(SERVER_PORT);//指定服务器端的ip,本地测试:127.0.0.1//inet_addr()函数,将点分十进制IP转换成网络字节序IPserverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*********************************************************************//*                          2-连接服务端                              *//*********************************************************************/  if(connect(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0){perror("connect");return 1;}printf("连接到主机...\n");/*********************************************************************//*                          3-发送接收消息                            *//*********************************************************************/ while(1){printf("发送消息:");scanf("%s", sendbuf);printf("\n");send(serverSocket, sendbuf, strlen(sendbuf), 0); //向服务端发送消息if(strcmp(sendbuf, "quit") == 0) break;printf("读取消息:");recvbuf[0] = '\0';iDataNum = recv(serverSocket, recvbuf, 200, 0); //接收服务端发来的消息recvbuf[iDataNum] = '\0';printf("%s\n", recvbuf);}close(serverSocket);return 0;
}

服务端:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>#define SERVER_PORT 8888/*
监听后,一直处于accept阻塞状态,
直到有客户端连接,
当客户端如close后,断开与客户端的连接
*/int main()
{//调用socket函数返回的文件描述符int serverSocket;//声明两个套接字sockaddr_in结构体变量,分别表示客户端和服务器struct sockaddr_in server_addr;struct sockaddr_in clientAddr;int addr_len = sizeof(clientAddr);int clientSocket;char buffer[200]; //存储 发送和接收的信息 int iDataNum;/*********************************************************************//*                          1-创建服务端套接字                        *//*********************************************************************/if((serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0){perror("socket");return 1;}memset(&server_addr,0, sizeof(server_addr));//初始化服务器端的套接字,并用htons和htonl将端口和地址转成网络字节序server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);//ip可是是本服务器的ip,也可以用宏INADDR_ANY代替,代表0.0.0.0,表明所有地址server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//对于bind,accept之类的函数,里面套接字参数都是需要强制转换成(struct sockaddr *)//bind三个参数:服务器端的套接字的文件描述符/*********************************************************************//*                          2-服务端绑定监听的IP和por                  *//*********************************************************************/  if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("connect");return 1;}/*********************************************************************//*                          3-服务端开始监听                          *//*********************************************************************/ if(listen(serverSocket, 5) < 0)//开启监听 ,第二个参数是最大监听数{perror("listen");return 1;}/*********************************************************************//*                          4-接收发送消息                            *//*********************************************************************/  printf("监听端口: %d\n", SERVER_PORT);//调用accept函数后,会进入阻塞状态//accept返回一个套接字的文件描述符,这样服务器端便有两个套接字的文件描述符,//serverSocket和client。//serverSocket仍然继续在监听状态,client则负责接收和发送数据//clientAddr是一个传出参数,accept返回时,传出客户端的地址和端口号//addr_len是一个传入-传出参数,传入的是调用者提供的缓冲区的clientAddr的长度,以避免缓冲区溢出。//传出的是客户端地址结构体的实际长度。//出错返回-1clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&addr_len);if(clientSocket < 0){perror("accept");}printf("等待消息...\n");//inet_ntoa ip地址转换函数,将网络字节序IP转换为点分十进制IP//表达式:char *inet_ntoa (struct in_addr);printf("IP is %s\n", inet_ntoa(clientAddr.sin_addr)); //把来访问的客户端的IP地址打出来printf("Port is %d\n", htons(clientAddr.sin_port)); while(1){buffer[0] = '\0';iDataNum = recv(clientSocket, buffer, 1024, 0);if(iDataNum < 0){continue;}buffer[iDataNum] = '\0';if(strcmp(buffer, "quit") == 0) break;printf("收到消息: %s\n", buffer);printf("发送消息:");scanf("%s", buffer);send(clientSocket, buffer, strlen(buffer), 0); //服务端也向客户端发送消息 if(strcmp(buffer, "quit") == 0) break; //输入quit停止服务端程序 }close(clientSocket);close(serverSocket);return 0;}

 在Linux上用gcc编译:

gcc server.c -o server
gcc client.c -o client

先运行服务端:

再运行客户端:

客户端服务端收发消息:

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

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

相关文章

Ubuntu18.04安装cuDNN

注册账号 https://developer.nvidia.com/rdp/cudnn-archive 该网站下载安装包需要先进行注册。登录成功后&#xff0c;找到与CUDA对应的版本。 选择Linux版本进行下载。 下载后的格式为.tar.xz 解压 tar xvJf cudnn-linux-x86_64-8.9.3.28_cuda12-archive.tar.xz配置环境 su…

【广州华锐互动】AR远程连接专家进行协同管理,解放双手让协同更便捷

AR远程协同系统是一种基于AR技术&#xff0c;实现远程设备维修和技术支持的系统。该系统通过将虚拟信息叠加在现实世界中&#xff0c;实现对设备的全方位监控和管理&#xff0c;并可以通过AR眼镜等终端设备&#xff0c;实时查看设备的各项数据和信息&#xff0c;为设备维修提供…

Python小知识 - 1. Python装饰器(decorator)

Python装饰器&#xff08;decorator&#xff09; Python装饰器是一个很有用的功能&#xff0c;它可以让我们在不修改原有代码的情况下&#xff0c;为已有的函数或类添加额外的功能。 常见的使用场景有&#xff1a; a. 函数缓存&#xff1a;对于一些计算量较大的函数&#xff0c…

2023.9.2 关于 JVM 垃圾回收机制(GC)

目录 为什么要有垃圾回收机制? STW&#xff08;Stop The World&#xff09;问题 垃圾回收机制主要回收哪个内存区域? 垃圾对象判断算法 引用计数算法 可达性分析算法 垃圾对象回收算法 标记清除算法 复制算法 标记整理算法 分代算法 为什么要有垃圾回收机制? 自动…

Navicat16连接Oracle报错:Oracle library is not loaded

1、有时候我们在用navicat的时候连接oracle的时候&#xff0c;它会提示我们Oracle library is not loaded&#xff0c;这时候我们要首先验证本机上是否已安装oracle的客户端&#xff0c;如果已安装客户段&#xff0c;navicat中的oci.dll选择我们安装的客户段的oci.dll文件 2、…

MATLAB中编译器中的变量联系到Simulink

MATLAB中编译器中的变量联系到Simulink 现在编译器中创建变量&#xff0c;进行编译&#xff0c;使其生成在工作区。 然后在Simulink中国使用变量即可。

opencv入门-Opencv原理以及Opencv-Python安装

图像的表示 1&#xff0c;位数 计算机采用0/1编码的系统&#xff0c;数字图像也是0/1来记录信息&#xff0c;图像都是8位数图像&#xff0c;包含0~255灰度&#xff0c; 其中0代表最黑&#xff0c;1代表最白 3&#xff0c; 4&#xff0c;OpenCV部署方法 安装OpenCV之前…

Hadoop 集群小文件归档 HAR、小文件优化 Uber 模式

文章目录 小文件归档 HAR小文件优化 Uber 模式 小文件归档 HAR 小文件归档是指将大量小文件合并成较大的文件&#xff0c;从而减少存储开销、元数据管理的开销以及处理时的任务调度开销。 这里我们通过 Hadoop Archive (HAR) 来进行实现&#xff0c;它是一种归档格式&#xf…

使用Docker配置深度学习的运行环境

文章目录 推荐实验环境前言docker安装docker操作docker配置常见方法&#xff08;安装包、联网、程序管理器&#xff09;安装驱动的前提要求传统方法安装驱动程序程序管理器安装联网安装deb包安装 安装完成后的设置非传统方法安装-通过容器安装驱动的前提要求安装NVIDIA-Contain…

Scala集合继承体系图

Scala集合简介 1&#xff09; Scala 的集合有三大类&#xff1a;序列 Seq、集Set、映射 Map&#xff0c;所有的集合都扩展自 Iterable特质。 2&#xff09; 对于几乎所有的集合类&#xff0c;Scala 都同时提供了可变和不可变的版本&#xff0c;分别位于以下两个包 不可变集合…

Orangepi安装外设库 wiringPi

注意&#xff1a;mobaXterm传送文件要在SSH登陆环境下才可以。 同时电脑和orangepi都在同一个wifi下。

unittest框架的使用

先简单介绍一下unittest的核心组成部分&#xff1a; 测试夹具&#xff1a;Test Fixture 一般用于执行测试用例的准备或者清理工作&#xff0c;比如测试开始前的数据准备或者测试结束的数据清理等。通过setUp()、tearDown()、setUpClass()、tearDownClass()这四个钩子函数实现了…

tableau基础学习2:时间序列数据预处理与绘图

文章目录 数据预处理1. 原始数据2. 合并数据集2. 创建计算字段 绘图分析1. 趋势分析2. 计算字段趋势分析 这一部分&#xff0c;我们记录一些分析时序趋势的分析步骤 数据预处理 1. 原始数据 原始数据是excel表格&#xff0c;其中包含三个Sheet页&#xff0c; 这里我们选择两…

ModaHub魔搭社区专访百度智能云李莅:以后所有的数据库它都会原生地支持用向量?

ModaHub魔搭社区&#xff1a;您是否认为&#xff0c;以后所有的数据库它都会原生地支持用向量&#xff1f; 李莅&#xff1a;传统数据库广义上也分好几类&#xff1a;一类是关系型的&#xff0c;一类是 NoSQL 类的&#xff0c;还有一类是分析型的数据库。我觉得关系型的这种数据…

Super Resolve Dynamic Scene from Continuous Spike Streams论文笔记

摘要 近期&#xff0c;脉冲相机在记录高动态场景中展示了其优越的潜力。不像传统相机将一个曝光时间内的视觉信息进行压缩成像&#xff0c;脉冲相机连续地输出二的脉冲流来记录动态场景&#xff0c;因此拥有极高的时间分辨率。而现有的脉冲相机重建方法主要集中在重建和脉冲相…

服务器监控可视化

IT监控可视化是一种将IT监控数据以图形化的方式呈现给用户的技术&#xff0c;可以帮助用户更直观、更易懂地了解IT系统的运行状况。服务器监控可视化是其中的一个重要应用场景&#xff0c;可以将服务器的各种性能指标以图表、仪表盘等形式展示&#xff0c;以便管理员更好地了解…

HTTP协议初识·中篇

加上目录&#xff0c;会出现导向不正确的情况&#xff0c;可能是bug&#xff0c;目录一长就容易出错&#xff1f; 本篇主要讲解了&#xff1a; 网页分离(网页代码和.c文件分离) html链接跳转 网页添加图片 确认并返回资源类型 填写正文长度属性 添加表单 临时重定向 补充知识&a…

06-限流策略有哪些,滑动窗口算法和令牌桶区别,使用场景?【Java面试题总结】

限流策略有哪些&#xff0c;滑动窗口算法和令牌桶区别&#xff0c;使用场景&#xff1f; 常见的限流算法有固定窗口、滑动窗口、漏桶、令牌桶等。 6.1 固定窗口 概念&#xff1a;固定窗口&#xff08;又称计算器限流&#xff09;&#xff0c;对一段固定时间窗口内的请求进行…

通过ref 操作dom , 点击按钮后跳转到页面指定图片位置

滚动图片到视图 定义了一个名为 scrollToIndex 的函数&#xff0c;它接受一个参数 index。当按钮被点击时&#xff0c;这个函数会被调用&#xff0c;并根据传入的 index 值来滚动到对应的图片。 以 alt 来标记图片位置 alt“Tom” import { useRef } from "react";c…

国标视频云服务EasyGBS国标视频平台迁移服务器后无法启动的问题解决方法

国标视频云服务EasyGBS支持设备/平台通过国标GB28181协议注册接入&#xff0c;并能实现视频的实时监控直播、录像、检索与回看、语音对讲、云存储、告警、平台级联等功能。平台部署简单、可拓展性强&#xff0c;支持将接入的视频流进行全终端、全平台分发&#xff0c;分发的视频…