服务器和客户端socket通信的本质是建立网络连接,实现跨机器的数据交换,掌握创建套接字、绑定端口、监听连接、收发数据这四步就能快速入门。
socket通信,也叫套接字通信,是网络编程的核心,不管你用哪种语言,底层原理都相通,下面从流程到实战,帮你理清关键点。
服务器和客户端Socket通信流程详解
建立一次socket通信,服务端和客户端各走一套固定流程,服务端先启动,等待客户端连接,客户端知道服务端的IP和端口后,主动发起连接。
建立连接的三次握手
客户端调用connect时,双方在内核层面自动完成三次握手,第一次,客户端发SYN包;第二次,服务端回SYN+ACK;第三次,客户端发ACK,握手完成后,accept返回,连接建立,这个过程中,服务端的listen队列长度很重要,如果设置太小,高并发下容易丢连接。
数据传输与关闭连接的四次挥手
连接建立后,双方用send和recv读写数据,TCP保证数据按序到达,不丢不重,关闭连接时,主动关闭方发FIN,被动方回ACK,然后被动方也发FIN,主动方再回ACK,共四次挥手,如果一方突然断电,另一方会走超时重传机制,最终断开。
实际开发中,你需要关注服务端是否一直阻塞在accept或recv上,这直接影响并发能力,后面专门讲阻塞与非阻塞模式。
TCP与UDP Socket通信区别对比
选择TCP还是UDP,是socket编程里最常见的决策,两者的核心差异在可靠性和速度上。
| 对比项 | TCP | UDP |
|---|---|---|
| 连接性 | 面向连接,先建立连接 | 无连接,直接发数据 |
| 可靠性 | 可靠,有重传机制 | 不可靠,丢包不重传 |
| 有序性 | 保证数据顺序 | 不保证顺序 |
| 速度 | 相对慢,头部开销大 | 快,头部开销小 |
| 典型应用 | HTTP、FTP、SMTP | DNS、视频直播、在线游戏 |
适用场景选择
行业共识认为,对数据完整性要求高的场景,比如文件传输、邮件、网页浏览,必须用TCP,对实时性要求高,能容忍少量丢包的场景,比如视频会议、游戏同步,用UDP更合适,很多实时通信应用会在UDP上层自己实现重传和排序,就是要在速度和可靠性之间取平衡。
性能与可靠性权衡
UDP的传输效率高,但丢包、乱序、重复都可能发生,应用层需要额外处理,TCP自带拥塞控制和流量控制,适合网络波动较大的环境,如果你在开发一个物联网设备上报系统,数据量小但频繁,UDP配合自定义确认机制能显著降低服务器负载。
Socket通信阻塞与非阻塞模式解析
socket默认是阻塞模式,你调用recv时,如果数据没到,线程会卡在那里,什么都不做,直到数据到来或超时,这种模式编程简单,但线程资源浪费严重,一个连接一个线程,高并发下撑不住。
阻塞模式与同步模型
阻塞模式下,服务器通常为每个客户端分配一个独立线程,线程的数量受系统限制,当连接数超过几千时,上下文切换和内存开销会急剧上升,业内专家指出,阻塞模式只适合连接数少、要求开发速度快的场景,比如快速原型或内部工具。
非阻塞模式与多路复用
非阻塞模式让socket立即返回,不管有没有数据,你需要在循环里不断轮询所有socket,检查哪些可读可写,但纯轮询效率低,所以有了I/O多路复用,比如select、poll、epoll,epoll是Linux下的高性能方案,能同时监控数千个socket,只在有事件时通知应用,极大降低CPU占用,像Nginx、Redis、Node.js的底层都是这种模式,如果你在写高并发服务器,非阻塞加epoll是标配。
Linux系统下Socket通信实战
理论讲完,上手写一个简单的TCP echo服务器,用C语言,服务端接收客户端消息,原样返回。
完整C语言实例
服务端代码关键步骤:
- 创建socket:
int sfd = socket(AF_INET, SOCK_STREAM, 0); - 设置地址结构体,绑定端口:
bind(sfd, (struct sockaddr)&addr, len); - 开始监听:
listen(sfd, 128); - 循环接受连接:
cfd = accept(sfd, NULL, NULL); - 收发数据:
recv(cfd, buf, sizeof(buf), 0); send(cfd, buf, len, 0);
客户端代码:
- 创建socket,设置服务端地址,调用connect。
- 连接成功后,send和recv即可。
编译与测试步骤
在Linux终端执行:
gcc -o server server.c
gcc -o client client.c
./server
./client 127.0.0.1
注意绑定端口时要选大于1024的端口,避免权限问题,如果服务端启动报错Address already in use,说明端口被占用,换个端口或用setsockopt设置SO_REUSEADDR选项。
Java Socket通信教程与实例
Java的Socket和ServerSocket类封装了TCP连接的细节,比C语言更简洁。
传统Socket实现
服务端:ServerSocket server = new ServerSocket(8080); Socket socket = server.accept(); 之后用socket的输入输出流读写,客户端:Socket socket = new Socket("127.0.0.1", 8080); 读写方式相同,Java的阻塞模式在连接数少时够用,但高并发时需要用线程池或者NIO。
NIO与多路复用
Java NIO使用Channel和Selector,一个线程可以管理多个Channel,类似Linux的epoll,关键代码:
Selector selector = Selector.open(); channel.register(selector, SelectionKey.OP_READ); 然后在循环里调用selector.select()处理就绪事件,Netty框架就是基于NIO的封装,现在很多企业级项目都在用,你要做高性能通信,直接学Netty更高效。
Socket通信常见问题与解答
什么是socket通信中的粘包现象?如何解决?
TCP是流式协议,没有消息边界,多个send可能合并成一个包发送,导致接收方一次recv读到多条消息或半条消息,解决方式:在应用层定义消息边界,比如固定长度、长度头+消息体,或者使用特殊分隔符,一般推荐长度头方式,头部4字节表示后面消息的长度,这样接收方先读长度,再读指定字节数。
TCP的listen函数参数backlog是什么意思?
backlog指定已完成连接队列(ESTABLISHED状态)的最大长度,当客户端connect完成三次握手后,连接进入这个队列,等待服务端accept取出,如果队列满了,客户端会收到ECONNREFUSED错误,在Linux 2.2之后,backlog表示completed队列和incomplete队列之和,但实际应用中建议设置大一些,比如128或256,避免高并发时连接丢失。
为什么socket通信需要设置超时?
网络环境不可靠,如果对端宕机或网络断开,阻塞在recv的线程可能永远等不到数据,设置超时后,到了时间会返回错误,线程可以主动关闭连接或重试,Java中通过setSoTimeout(int timeout)实现,C语言可以用setsockopt设置SO_RCVTIMEO,超时时间根据业务场景定,一般3到5秒。
socket通信的知识点很扎实,从流程、协议选择、模式对比到实战编码,每一环都是网络编程的基石,无论你写Web服务器、游戏后端还是物联网网关,这些原理都会反复出现,掌握它们,后续的框架和工具学起来就会轻松很多。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/560617.html




