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

news2024/9/22 23:33:50

一:什么是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 8888
 
int main()
{
 
   //客户端只需要一个套接字文件描述符,用于和服务器通信
   int serverSocket;
    
   //描述服务器的socket
   struct 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转换成网络字节序IP
   serverAddr.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的长度,以避免缓冲区溢出。
  //传出的是客户端地址结构体的实际长度。
  //出错返回-1
  
  clientSocket = 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.coloradmin.cn/o/959102.html

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

相关文章

linux并发服务器 —— 多进程并发 - 进程间的通信及实践(五)

进程间的通信 进程是一个独立的资源分配单元&#xff0c;不能在一个进程中直接访问另一个进程的资源&#xff1b; 进程间通信&#xff08;IPC&#xff09;的目的&#xff1a; 1. 数据传输 - A进程发送数据给B进程 2. 通知事件 - eg. 进程终止通知父进程 3. 资源共享 - 多个…

Middleware ❀ Kafka功能与使用详解

文章目录 1. 概述1.1. 消息队列1.2. 应用场景1.3. 工作模式1.4. 基础结构1.4.1. 结构组件1.4.2. 数据同步1.4.3. ACK机制1.4.4. 分区机制1.4.4.1. 使用Partition Key写入1.4.4.2. 轮询写入 - 默认规则1.4.4.3. 指定Partition写入 1.4.5. Offset偏移量1.4.5.1. 消息顺序性1.4.5.…

六级翻译备考

classical 经典的 Chinese literature 中国文学 朝代dynasty 统治 rule 社会稳定 steady society 治理有序 orderly governance 伟大的greatest 时代 times或者periods 被人们描绘成人类历史上伴随着治理有序&#xff0c;社会稳定的最伟大的时代之一 more and more越来越多 …

leetcode235. 二叉搜索树的最近公共祖先(java)

二叉搜索树的最近公共祖先 题目描述递归 剪枝代码演示&#xff1a; 上期经典 题目描述 难度 - 中等 LC235 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q…

结合OB Cloud区别于MySQL的4大特性,规划降本方案

任何一家企业想要获得持续性的发展与盈利&#xff0c;“降本增效”都是难以绕开的命题。但是“一刀切”的降本影响往往不太可控&#xff0c;成本的快速收缩往往会给业务带来低效运营和增长缓慢的风险。所以我们所说的降本&#xff0c;是指在成本降低的同时&#xff0c;效率不降…

【附安装包】Tecplot 360 EX2021安装教程

软件下载 软件&#xff1a;Tecplot 360版本&#xff1a;2021语言&#xff1a;英文大小&#xff1a;367.36M安装环境&#xff1a;Win11/Win10/Win8/Win7硬件要求&#xff1a;CPU2.5GHz 内存4G(或更高&#xff09;下载通道①百度网盘丨64位下载链接&#xff1a;https://pan.baid…

冠达管理:成交量突然放大意味着什么?

在股票商场中&#xff0c;成交量是股市中非常重要的目标之一。股票成交量是指在一定时间内股票买卖所成交的总股数。当成交量忽然扩大时&#xff0c;这意味着股票商场的很多买卖正在产生&#xff0c;这一般会引起出资者的注重。在本文中&#xff0c;我们将从多个视点来剖析成交…

理解FPGA中的亚稳态

一、前言 大家应该经常能听说到亚稳态这个词&#xff0c;亚稳态主要是指触发器的输出在一段时间内不能达到一个确定的状态&#xff0c;过了这段时间触发器的输出随机选择输出0/1&#xff0c;这是我们在设计时需要避免的。本文主要讲述了FPGA中的亚稳态问题&#xff0c;可以帮助…

JVM虚拟机对象探秘

对象的创建 Java是一门面向对象的编程语言&#xff0c;创建对象通常只是通过new关键字。 对象创建过程 当Java虚拟机遇到一条字节码new指令时&#xff0c;首先将去检查这个指令的参数是否能在常量池中定位到 一个类的符号引用&#xff0c;并且检查这个符号引用&#xff08;类…

uni-app 客服按钮可上下拖动动

项目需求&#xff1a; 因为悬浮客服有时候会遮挡住界面内容&#xff0c;故需要对悬浮的气泡弹窗做可拖动操作 movable-area&#xff1a;可拖动区域 movable-view&#xff1a;可移动的视图容器&#xff0c;在页面中可以拖拽滑动或双指缩放。 属性说明 属性名类型默认值说…

提高中小企业组网效率的关键要素与技术选项

如今的商业环境中&#xff0c;中小企业扮演着重要角色&#xff0c;它们通常是由创业者或小型团队组成&#xff0c;拥有有限的人力资源和财务能力。尽管规模较小&#xff0c;中小企业一样面临着与大型企业相似的竞争压力和业务组网需求。 在数字化时代&#xff0c;中小企业对于高…

MTK6761/MT6761安卓核心板4G安卓智能模块详细参数性能介绍

MTK6761 安卓核心板采用12nm制程四核Cortex-A53、最高主频2.0GHZ 处理器&#xff0c;板载内存为 1GB8GB(2GB16GB、3GB32GB、4GB64GB)&#xff0c;搭载Android 9.0操作系统。 MTK6761&#xff08;曦力 A22&#xff09;安卓核心板基本概述 MTK6761安卓核心板 是一款高性能低功耗…

GCash all in OB Cloud,打造菲律宾国民级钱包APP

GCash 创立于 2017 年&#xff0c;由菲律宾电信巨头 GlobeTelecom 推出&#xff0c;是菲律宾排名第一的移动钱包和该国首个双独角兽公司&#xff0c;主要为用户在智能手机上提供储蓄、贷款、保险和投资服务。截止 2022 年 6 月&#xff0c;GCash 注册用户数量达 6600 万&#x…

centos 7的超详细安装教程

打开虚拟机&#xff0c;创建一个新电脑 我们选择经典&#xff0c;然后选择下一步 我们选择稍后安装&#xff0c;我们在后面进行改设备 因为centos系统是linux系统的一个版本&#xff0c;所有我们选择linux&#xff0c;版本选择centos 7 64位&#xff0c;然后就是点击下一步 这一…

座舱3.0时代!产业涌现哪些新机会?

智能座舱一直是汽车智能化普及的领跑角色&#xff0c;目前已经逐步进入了软件定义座舱的新周期。 过去几年&#xff0c;中控多媒体系统、车载语音、OTA等单一功能的搭载率已经快速普及。其中&#xff0c;中控娱乐系统的前装渗透率已经超过90%。高工智能汽车研究院监测数据显示…

Vue/React 项目部署到服务器后,刷新页面出现404报错

问题描述&#xff1a;在本地启动项目一切正常&#xff0c;部署到服务器上线后出现BUG&#xff0c;项目刷新页面出现404。 起初以为是自己路由守卫或是token丢失问题&#xff0c;找了一圈终于解决了 产生原因&#xff1a;我们打开vue/react打包后生成的dist文件夹&#xff0c;可…

TS 入门

TS 入门 interface 约束作用数组的声明方式函数的定义联合类型、交叉类型、断言类型类的方面 interface 约束作用 数组的声明方式 函数的定义 联合类型、交叉类型、断言类型 类的方面 这是代码的地址&#xff1a; 代码的地址

N5182A矢量信号发生器

产品概述 是德科技N5182A(安捷伦)MXG射频矢量信号发生器具有快速频率、幅度和波形切换、带电子衰减器的高功率和高可靠性——所有这些都在两个机架单元(2RU)中。是德科技N5182A针对制造蜂窝通信和无线连接组件进行了优化。是德科技N5182A通过增加吞吐量、提高测试产量、最大化…

【JavaScript精通之道】掌握数据遍历:解锁现代化遍历方法,提升开发效率!

​ &#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! ​ 目录 &#x1f4da; 前言 &#x1f4d8; 1. reduce方法 &#x1f4d8; 2. forEach方法 &#x1f4d8; 3. map方法…

兔鲜儿 - 用户模块

目录 兔鲜儿 - 用户模块​ 会员中心页(我的)​ 静态结构​ 猜你喜欢分页加载 会员设置页 设置页分包和预下载 静态结构 退出登录 会员信息页 个人信息页准备工作 静态结构 获取会员信息​ 渲染会员信息 更新会员头像 更新表单信息​ 兔鲜儿 - 用户模块​ 在用户…