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

Linux学习笔记5:socket通信

1. 什么是 socket

socket(套接字)是 Linux 下网络通信的基石,它为不同主机(或同一主机)上的进程提供了一种双向通信的端点。可以把 socket 看作一根水管,一头连接你的程序,另一头连接远程程序或另一个本地进程,数据就在这根水管里流动。

在 Linux 中,socket 本质上是一个特殊的文件描述符,因此很多 I/O 操作(如readwriteclose)同样适用于 socket,这也是 Unix/Linux “一切皆文件”哲学的体现。

常用的 socket 类型有:

  • 流式套接字(SOCK_STREAM):基于 TCP,面向连接、可靠、有序的字节流。
  • 数据报套接字(SOCK_DGRAM):基于 UDP,无连接、不可靠、保留消息边界。
  • 原始套接字(SOCK_RAW):允许直接访问底层协议,常用于网络诊断工具。

在实际开发中,我们最常用的是 TCP 和 UDP 两种 socket。

2. socket 通信的基本流程

一个典型的 TCP 通信流程如下:

服务端客户端服务端客户端socket() 创建套接字bind() 绑定地址listen() 开始监听socket() 创建套接字connect() 请求连接accept() 接受连接write() 发送数据read() 接收数据close() 关闭连接close() 关闭连接

UDP 通信则要简单很多,不需要listenaccept,直接用sendtorecvfrom发送和接收数据。

3. 核心 API 详解

Linux 提供了一组标准的 socket 系统调用,所有函数都在<sys/socket.h>中声明。

3.1socket()—— 创建套接字

#include<sys/socket.h>intsocket(intdomain,inttype,intprotocol);
  • domain:地址族,IPv4 用AF_INET,本地通信用AF_UNIX
  • type:套接字类型,TCP 用SOCK_STREAM,UDP 用SOCK_DGRAM
  • protocol:通常填 0,让系统根据前两个参数自动选择协议。
  • 返回值:成功返回文件描述符,失败返回 -1 并设置errno

3.2bind()—— 绑定地址

intbind(intsockfd,conststructsockaddr*addr,socklen_taddrlen);

将创建的 socket 与一个本地地址(IP + 端口)绑定。服务端通常需要调用,客户端也可以绑定以指定源端口,但一般不这样做。

3.3listen()—— 开始监听(仅 TCP 服务端)

intlisten(intsockfd,intbacklog);

将主动套接字变为被动套接字,准备接受客户端的连接。backlog指定已完成三次握手但还未被accept的连接队列的最大长度。

3.4accept()—— 接受连接(仅 TCP 服务端)

intaccept(intsockfd,structsockaddr*addr,socklen_t*addrlen);

从已完成连接队列中取出一个连接,并返回一个新的文件描述符,用于与这个客户端进行通信。addr会带回客户端的地址信息。

3.5connect()—— 发起连接(仅 TCP 客户端)

intconnect(intsockfd,conststructsockaddr*addr,socklen_taddrlen);

客户端通过connect向服务端发起三次握手,建立连接。成功后即可在这条连接上收发数据。

3.6 数据收发

  • write()/read():与普通文件操作一样,适合简单的阻塞 I/O。
  • send()/recv():增加了一些flags参数,可以设置非阻塞、发送带外数据等。
  • sendto()/recvfrom():UDP 专用,需要在参数中指定对方的地址。

4. 一个完整的 TCP 回射服务端/客户端示例

下面用 C 实现一个简单的 TCP 回射(echo)服务器和客户端。服务器会原样返回客户端发来的任何数据,直到客户端关闭连接。

4.1 服务端代码

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<arpa/inet.h>#include<sys/socket.h>#definePORT8080#defineBUFFER_SIZE1024intmain(){intserver_fd,client_fd;structsockaddr_inaddress;intaddrlen=sizeof(address);charbuffer[BUFFER_SIZE]={0};// 1. 创建 socketif((server_fd=socket(AF_INET,SOCK_STREAM,0))==0){perror("socket failed");exit(EXIT_FAILURE);}// 2. 设置地址结构address.sin_family=AF_INET;address.sin_addr.s_addr=INADDR_ANY;// 监听所有接口address.sin_port=htons(PORT);// 端口号,host to network short// 3. 绑定地址if(bind(server_fd,(structsockaddr*)&address,sizeof(address))<0){perror("bind failed");exit(EXIT_FAILURE);}// 4. 开始监听if(listen(server_fd,3)<0){perror("listen failed");exit(EXIT_FAILURE);}printf("Server listening on port %d...\n",PORT);// 5. 接受连接if((client_fd=accept(server_fd,(structsockaddr*)&address,(socklen_t*)&addrlen))<0){perror("accept failed");exit(EXIT_FAILURE);}printf("Client connected.\n");// 6. 读取并回射ssize_tbytes_read;while((bytes_read=read(client_fd,buffer,BUFFER_SIZE))>0){write(client_fd,buffer,bytes_read);memset(buffer,0,BUFFER_SIZE);}printf("Client disconnected.\n");close(client_fd);close(server_fd);return0;}

4.2 客户端代码

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<arpa/inet.h>#include<sys/socket.h>#definePORT8080#defineBUFFER_SIZE1024intmain(){intsock=0;structsockaddr_inserv_addr;charbuffer[BUFFER_SIZE]={0};char*message="Hello from client";// 1. 创建 socketif((sock=socket(AF_INET,SOCK_STREAM,0))<0){perror("socket creation error");return-1;}// 2. 设置服务器地址serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(PORT);// 将 IP 地址从点分十进制转换为二进制if(inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr)<=0){perror("invalid address / address not supported");return-1;}// 3. 连接服务器if(connect(sock,(structsockaddr*)&serv_addr,sizeof(serv_addr))<0){perror("connection failed");return-1;}// 4. 发送数据并接收回显send(sock,message,strlen(message),0);printf("Sent: %s\n",message);read(sock,buffer,BUFFER_SIZE);printf("Echo: %s\n",buffer);close(sock);return0;}

4.3 编译与运行

先将两个代码分别保存为server.cclient.c,然后使用gcc编译:

gcc server.c-oserver gcc client.c-oclient

先启动服务端:

./server

再在另一个终端运行客户端:

./client

客户端会输出:

Sent: Hello from client Echo: Hello from client

这样一个最基础的 TCP 回射程序就跑通了。

5. UDP 通信示例

UDP 不需要建立连接,流程更简洁。下面给出一个简单的 UDP 回射示例。

5.1 服务端

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<arpa/inet.h>#include<sys/socket.h>#definePORT8080#defineBUFFER_SIZE1024intmain(){intsockfd;structsockaddr_inserv_addr,cli_addr;socklen_tlen=sizeof(cli_addr);charbuffer[BUFFER_SIZE];sockfd=socket(AF_INET,SOCK_DGRAM,0);serv_addr.sin_family=AF_INET;serv_addr.sin_addr.s_addr=INADDR_ANY;serv_addr.sin_port=htons(PORT);bind(sockfd,(structsockaddr*)&serv_addr,sizeof(serv_addr));printf("UDP server listening on port %d\n",PORT);ssize_tn=recvfrom(sockfd,buffer,BUFFER_SIZE,0,(structsockaddr*)&cli_addr,&len);buffer[n]='\0';printf("Received: %s\n",buffer);sendto(sockfd,buffer,n,0,(structsockaddr*)&cli_addr,len);close(sockfd);return0;}

5.2 客户端

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<arpa/inet.h>#include<sys/socket.h>#definePORT8080#defineBUFFER_SIZE1024intmain(){intsockfd;structsockaddr_inserv_addr;socklen_tlen=sizeof(serv_addr);charbuffer[BUFFER_SIZE];char*msg="Hello UDP";sockfd=socket(AF_INET,SOCK_DGRAM,0);serv_addr.sin_family=AF_INET;serv_addr.sin_port=htons(PORT);inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr);sendto(sockfd,msg,strlen(msg),0,(structsockaddr*)&serv_addr,len);printf("Sent: %s\n",msg);recvfrom(sockfd,buffer,BUFFER_SIZE,0,(structsockaddr*)&serv_addr,&len);printf("Echo: %s\n",buffer);close(sockfd);return0;}

6. 常见问题与调试技巧

  • 端口被占用:如果bind时报Address already in use,说明端口在上一次程序结束后仍处于TIME_WAIT状态,可设置SO_REUSEADDR选项解决。
  • 防火墙拦截:排查iptables或云安全组规则,确保开放对应端口。
  • 连接拒绝(Connection refused):说明服务端还没启动或端口号不对。
  • 阻塞问题:默认 I/O 是阻塞的,如果一方不发送数据,另一方会一直等待。可以通过selectpollepoll实现多路复用,或者将 socket 设为非阻塞模式。

调试工具推荐:

  • netstat -tlnp:查看监听端口。
  • ss -tlnp:比 netstat 更高效。
  • tcpdump/wireshark:抓包分析网络问题。
http://www.jsqmd.com/news/1090765/

相关文章:

  • 终极指南:如何在Windows上免费搭建AirPlay 2投屏服务器
  • 如何15分钟完成专业级黑苹果EFI配置:OpCore-Simplify让复杂变简单
  • 上海交大技术转移硕士项目特色-全国首个MTT五力模型实践与生态全解
  • edgeR/limma 必做的 5 组验证图和 2 个判断原则
  • 古琴琴底结构名称及由来​
  • MySQL 索引设计的最佳实践
  • 高级自定义技巧:MeEdu在线教育系统核心功能深度解析
  • 鸿蒙 ArkTS 实战:Knowledge Tree 从状态建模到交互闭环完整解析
  • TPA2025D1 D类音频功放评估板实战:从核心原理到PCB布局设计
  • Three.js 程序化地形生成教程
  • PageAdmin CMS建站系统承载千万级内容和高并发的架构讲解
  • 第17周周报
  • MSP430 Timer_B捕获比较与UART通信实战:从寄存器到低功耗频率计
  • QQ音乐解析终极指南:三步解锁全网音乐资源
  • 上海小程序定制开发公司,哪家售后服务比较靠谱?
  • YgoMaster终极PvP对战指南:如何轻松实现局域网游戏王对战
  • 2025总结
  • OmenSuperHub:惠普暗影精灵性能控制终极指南
  • 老中医如何用AI学好五运六气——天辛大师谈实用技巧
  • Claude收紧访问政策:50%持股红线怎么理解
  • 国产高速数字化仪PCIe-7964R FPGA板卡(250M/16bit:4AI+2AO)兼容LabVIEW FPGA软件开发
  • QuantConnect Lean算法交易引擎:5步打造你的第一个量化交易策略
  • 7th [math] 2026.06.28
  • 一图看懂cache直接映射(涉略全相联、组相联)
  • 计算机毕业设计之儿童PTC管理系统的设计与实现
  • 从零到一:Awesome-Dify-Workflow如何解决AI工作流开发难题
  • Windows应急响应实战:从PowerShell挖矿脚本追踪到矿池C2域名
  • 从生产者-消费者到软考真题:信号量与PV操作的核心原理与实战拆解
  • 我怎么用 Playwright MCP 做浏览器自动化测试
  • Visual Studio 上快速搭建 LittleVGL 模拟器开发环境