在开始写代码之前,补充一下关于我们在连接中常用到的 Socket :
Socket的位置

Socket 简介
1982 - Berkeley Software Distributions 操作系统引入了socket作为本地进程之间通信的接口;
1986 - Berkeley 扩展了socket 接口,使之支持UNIX 下的TCP/IP 通信;
现在很多应用 (FTP, Telnet, etc) 都依赖这一接口。
Socket:
* 是一个编程接口
* 是一种特殊的文件描述符 (everything in Unix is a file)
* 并不仅限于TCP/IP协议
* 面向连接 (Transmission Control Protocol - TCP/IP)
* 无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX)
为什么需要Socket?
普通的I/O操作过程:打开文件->读/写操作->关闭文件
TCP/IP协议被集成到操作系统的内核中,引入了新型的"I/O"操作:进行网络通信的两个进程在不同的机器上,如何连接?网络协议具有多样性,如何进行统一的操作 ------> 因此,此处需要一种通用的网络编程接口:Socket
Socket独立于具体协议的网络编程接口 ------> 在OSI模型中,主要位于会话层和传输层之间,BSD Socket(伯克利套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法,目前已经被广泛移植到各个平台。
Socket类型:
> 流式套接字(SOCK_STREAM)
> 提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
> 数据报套接字(SOCK_DGRAM)
> 提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
> 原始套接字(SOCK_RAW)
> 可以对较低层次协议如IP、ICMP直接访问。
int socket (int domain, int type, int protocol);
domain 是地址族
PF_INET // internet 协议
PF_UNIX // unix internal协议
PF_NS // Xerox NS协议
PF_IMPLINK // Interface Message协议
type // 套接字类型
SOCK_STREAM // 流式套接字
SOCK_DGRAM // 数据报套接字
SOCK_RAW // 原始套接字
protocol 参数通常置为 0

UDP协议
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
功能:
提供不同主机上的进程通信
特点:
1.发送数据之前不需要建立连接
2.不对数据包的顺序进行检查
3.没有错误检测和重传机制
4.相对于TCP速度更快
5.简单的请求、应答应用程序可使用UDP
6.广播/组播式通信必须使用UDP
适用情况:
1、发送小尺寸数据(如对DNS服务器进行IP地址查询时)
2、在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
3、适合于广播/组播式通信中。
4、MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
5、流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
1》框架
/**框架**/
服务器(server) 客户端(client)
1.创建数据报套接字:socket(); 1.创建数据报套接字:socket();
| |
2.绑定本地地址:bind(); 2.绑定本地地址:bind();//可忽略
| |
3.开始和客户端通信:sendto()/recvfrom(); 3.开始和服务器通信:sendto()/recvfrom();
| |
4.关闭文件描述符:close(); 4.关闭文件描述符:close();
2》UDP编程相关的API函数
1>sendto发送数据
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
/**********************************************************************
@brief: UDP中发送数据
@sockfd: 套接字文件描述符
@buf: 发送数据的容器
@len: buf的长度
@flags: 一般为0
@dest_addr:目的地的结构体指针
@addrlen: 结构体的长度
@retval: 成功:返回发送的字节数
失败:返回-1,并且设置全局错误码
**********************************************************************/
2>recvfrom接收数据
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
/**********************************************************************
@brief: UDP中接收数据
@sockfd: 套接字文件描述符
@buf: 接收数据的容器
@len: buf的长度
@flags: 一般为0
@dest_addr:数据源的结构体指针
@addrlen: 结构体的长度
@retval: 成功:返回接收的字节数
失败:返回-1,并且设置全局错误码
**********************************************************************/

📌 通信流程框架
服务器(server) 和 客户端(client) 进行 UDP 通信的基本流程如下:
服务器(server) | 客户端(client) |
---|---|
1、创建 UDP 套接字:socket() | 1、创建 UDP 套接字:socket() |
2、绑定本地地址:bind() | 2、绑定本地地址(可忽略) |
3、发送和接收数据:sendto() / recvfrom() | 3、发送和接收数据:sendto() / recvfrom() |
4、关闭套接字:close() | 4、关闭套接字:close() |
代码实现
✅ 服务器代码(server.c)
c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 8888
int main(void)
{
struct sockaddr_in sin,cin;
char buf[256];
//创建数据报套接字
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
//服务器的地址信息赋值
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);//自动获取本机可用IP
//绑定本地地址
if(bind(sock_fd, (const struct sockaddr *)&sin, sizeof(sin)) < 0)
{
perror("bind");
exit(1);
}
//与客户端通信
while(1)
{
bzero(buf,sizeof(buf));
socklen_t len = sizeof(cin);
if(recvfrom(sock_fd, buf, sizeof(buf), 0,(struct sockaddr *)&cin, &len) < 0)
{
perror("recvfrom");
exit(1);
}
printf("客户端(%s):%s\n",inet_ntoa(cin.sin_addr),buf);
if(!strncmp(buf,"quit",4))
break;
}
//关闭套接字文件描述符
close(sock_fd);
return 0;
}
✅ 客户端代码(client.c)
c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 8888
int main(void)
{
struct sockaddr_in sin;
char buf[256];
//创建数据报套接字
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
//服务器的地址信息声明
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr("192.168.30.223");
//与客户端通信
while(1)
{
bzero(buf,sizeof(buf));
printf("请输入数据...:\n");
fgets(buf,sizeof(buf),stdin);
if(sendto(sock_fd, buf, strlen(buf), 0,(struct sockaddr *)&sin, sizeof(sin)) < 0)
{
perror("sendto");
exit(1);
}
if(!strncmp(buf,"quit",4))
break;
}
//关闭套接字文件描述符
close(sock_fd);
return 0;
}
✅ Makefile
bash
#编译部分
CC = gcc
#SRC:当前目录中的.c文件名称
#wildcard:拓展通配符找到目录中所有.c文件,
# 返回成以空格隔开的形式,例如:1.c 2.c 3.c
SRC = ${wildcard *.c}
#OBJS:当前目录中的.c文件对应的同名可执行文件
#patsubst:替换通配符 %:匹配通配符 将.c文件替换成去除掉.c后缀的同名执行文件
OBJS = ${patsubst %.c,%,$(SRC)}
all:$(OBJS)
$(OBJS):%:%.c
$(CC) -o $@ $^ -lpthread
#清除部分
.PHONY:clean
clean:
$(RM) $(OBJS) .*.sw?
运行步骤:
1️⃣ 编译代码
bash
make
该make编译后会显示:
bash
gcc -o client client.c -lpthread
gcc -o server server.c -lpthread
2️⃣ 启动服务器
bash
./server
3️⃣ 启动客户端
bash
./client
4️⃣ 进行通信
- 客户端输入消息,按回车发送
- 服务器接收并打印消息
- 服务器回复"Message received!"
- 客户端打印服务器的响应
UDP流程
✅ 服务器端
socket(AF_INET, SOCK_DGRAM, 0)
:创建 UDP 套接字bind()
:绑定到本地 IP 和端口recvfrom()
:接收客户端消息sendto()
:向客户端发送响应close()
:关闭套接字
✅ 客户端
socket(AF_INET, SOCK_DGRAM, 0)
:创建 UDP 套接字sendto()
:向服务器发送数据recvfrom()
:接收服务器响应close()
:关闭套接字
📌 UDP 的特点
✅ 无连接:无需 connect()
,可发送数据到任意地址
✅ 速度快:适用于实时通信(如语音、视频流)
✅ 支持广播、多播:适用于物联网 、组播系统
✅ 不保证数据可靠性:数据可能丢失,需应用层处理
✅ 代码简单高效,适合 IoT、实时应用、游戏开发等
如果需要更可靠的 UDP 传输,可以在应用层实现:
🔹 ACK 确认机制 (发送后要求接收方回复 ACK)
🔹 超时重传 (未收到 ACK 时重新发送)
🔹 序列号(确保数据包顺序)
不过,如果需要可靠传输,还是考虑 TCP 代替 UDP。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!