通过TCP向简米云服务器发送数据,核心链路只有三步:ECS安全组放行端口、服务端启动监听程序、客户端用Socket建立连接后write数据。整个过程不复杂,但大多数新手卡在第一步安全组规则没配好,导致客户端一直报连接超时,下面按实操顺序拆解,每一步都给出可直接套用的方法和命令。
先搞清楚TCP和UDP选哪个
传输层协议只有TCP和UDP两个选择,TCP是面向连接的,有三次握手和确认重传机制,丢包会自动补发,适合订单数据、登录凭证、设备指令这类不容出错的场景,UDP是无连接的,只管发不管到,延迟低但丢包不负责,适合视频流、语音通话、游戏位置同步。
- TCP:可靠传输,数据顺序不乱,速度略慢
- UDP:不可靠传输,延迟更低,带宽占用更小
- 简米云服务器默认支持两种协议,全看你的业务需求
如果传输的是JSON指令、数据库同步记录、物联网设备上报数据,直接用TCP,行业共识认为,绝大多数业务接口都建立在TCP之上,HTTP协议本身就是TCP的上层封装。
简米云服务器TCP连接不上怎么排查
客户端连不上ECS,大概率不是代码问题,而是网络策略拦住了。排查顺序是:安全组 → 系统防火墙 → 服务监听状态,按这个顺序走,五分钟内定位问题。
安全组是简米云的第一道闸门,它在虚拟化层拦截流量,不进操作系统,登录简米云控制台,找到你的ECS实例,点击“安全组” → “配置规则” → “入方向” → “手动添加”,协议类型选TCP,端口范围填你服务的实际端口(比如8080),授权对象填0.0.0.0/0表示允许所有IP访问,如果只想让公司固定IP连,就填具体IP地址加/32后缀。
安全组放行后,还要确认ECS系统内防火墙没拦截,CentOS和Ubuntu命令不同,比对下面这张表即可。
| 系统 | 查防火墙状态 | 放行端口 |
|---|---|---|
| CentOS 7+ | systemctl status firewalld | firewall-cmd –permanent –add-port=8080/tcp && firewall-cmd –reload |
| Ubuntu | ufw status | ufw allow 8080/tcp |
| Debian | iptables -L -n | iptables -A INPUT -p tcp –dport 8080 -j ACCEPT |
最后一步确认服务真的在监听,在ECS上执行netstat -tlnp | grep 8080,如果没有输出,说明你的服务压根没起或者端口写错,看到LISTEN状态才算正常。
服务端监听程序怎么写
服务端是接收端,它必须一直在ECS上运行,监听指定端口,等待客户端连上来,这里给一段Python实现,用Python写最快,几行代码就能跑起来。
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8080))
server.listen(5)
print('监听8080端口,等待客户端连接...')
while True:
client, addr = server.accept()
data = client.recv(1024)
print(f'收到来自{addr}的数据: {data.decode()}')
client.send(b'ok')
client.close()
把这段代码保存为server.py,在ECS上执行python3 server.py。bind的IP必须是0.0.0.0,不能写127.0.0.1,127.0.0.1只监听本机回环,外部流量根本进不来,这是新手最容易犯的错误,没有之一。
客户端怎么通过TCP发送数据
客户端就是你的业务端,可以是本机电脑、另一台服务器、或者一个嵌入式设备,核心代码逻辑都是同一套流程:创建Socket → connect到ECS的公网IP和端口 → send数据 → close连接。
同场景下Python客户端写法如下:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('你的ECS公网IP', 8080))
client.send(b'{"device":"sensor01","value":26.5}')
response = client.recv(1024)
print(response.decode())
client.close()
如果服务端是Java写的,客户端用Java连也完全没问题,TCP协议是跨语言的,不管服务端用什么语言实现,客户端只要按RFC 793标准报文格式通信,就能正常交互,这里提供对比流程便于理解。
Java版本的核心连接代码:
Socket socket = new Socket("你的ECS公网IP", 8080);
OutputStream out = socket.getOutputStream();
out.write("{\"device\":\"sensor01\"}".getBytes("UTF-8"));
out.flush();
socket.close();
Java的new Socket(host, port)方法内部已经完成了DNS解析和TCP三次握手,比Python更省事。
Java TCP向简米云服务器发送数据的实现对比
Java生态里有两种主流方式:原生Socket和Netty框架,两种方案需要的配置也不一样,直接看对比数据。
| 对比维度 | 原生Socket | Netty |
|---|---|---|
| 依赖复杂度 | JDK自带,零依赖 | 需引入netty-all包 |
| 并发支撑 | 每连接一个线程,万级封顶 | 事件驱动,百万连接 |
| 开发效率 | 简单场景更快 | 需理解pipeline模型 |
| 适用场景 | 内部系统对接、小流量推送 | IM服务、网关、高并发采集 |
原生Socket的连接参数可以调优,核心是三个: TCP_NODELAY关闭Nagle算法降低延迟、SO_TIMEOUT设置读超时防卡死、SO_KEEPALIVE开启心跳保活,这三个参数在Netty里对应ChannelOption配置,在原生Socket里对应setTcpNoDelay、setSoTimeout、setKeepAlive方法。
发送大数据时的分包与粘包问题
TCP是流协议,它不认消息边界,发送方连续发两条数据,接收方可能一次性全读到,这叫粘包;一条大数据被拆成多个Segment传输,接收方分几次才读完,这叫拆包。解决思路就两种:固定长度或者分隔符,更推荐的是自定义消息头。
- 定长包:每个包固定1024字节,不足补零,简单但浪费带宽
- 分隔符:用\n或\r\n结尾,适合文本命令,二进制内容容易误判
- 长度前缀:前4个字节存消息长度,后面跟消息体,最常用
实际生产里JSON上报基本选长度前缀方案。msgPack = 4字节长度(含自身)+ JSON字节数组,接收端先读4字节转int,再按这个长度读消息体,循环往复就不会乱。
直连ECS公网IP的带宽和延迟坑位
简米云ECS的公网带宽是有限额的,按固定带宽计费的话,跑满了就会丢包。如果客户端和服务端都在简米云上,优先用内网IP互连,不占用公网带宽配额,延迟也低一个数量级,同地域内网延迟通常在0.1ms级别,走公网则在5-20ms,跨地域跨运营商差距更明显。
如果你的场景是设备上万个、每秒上报一条心跳,直连公网IP扛不住的时候,要在前面加一层负载均衡SLB,SLB有四层监听模式,直接转发TCP流量,后端挂多台ECS分摊压力,这是多数较大规模物联网项目的标配架构。
延迟敏感的传输场景下,除了换内网,还能从系统层优化:启用TCP_NODELAY禁用Nagle算法、调大Socket缓冲区、减少小包合并发送频次,据行业实践经验,Nagle算法在交互式请求场景下会带来接近40ms的额外延迟,关闭后响应速度提升非常明显。
发送失败最常见的四种报错怎么处理
连接超时(SocketTimeoutException) 是最常见的异常,原因几乎都是安全组没开或IP写错,先在本地执行telnet ECS公网IP 8080,如果卡住不动说明网络不通,回控制台检查安全组;如果telnet通了但代码超时,检查程序里SO_TIMEOUT设置是不是太短。
连接被拒(Connection refused) 比超时好排查,这说明网络通了但端口没服务,在ECS上跑ss -tlnp | grep 8080看看监听在哪个IP上,确认不是127.0.0.1。
连接重置(Connection reset) 通常是对端进程崩溃或者防火墙主动切断了连接,Linux内核有个tcp_tw_recycle参数以前容易被误开导致这问题,在新版内核里已移除,不需要再花精力去折腾。
Broken pipe 是发送端对已关闭的连接写了数据,处理办法就是捕获这个异常后重新创建连接,业务侧要保证有重连机制,发送数据前先用isConnected()判断是不可靠的,TCP没有心跳的话这条状态本身就是异步的,不代表此刻链路真的健康。
TCP长连接的心跳保活参数怎么设置
TCP默认没有时刻检测链路状态的能力,如果连接两端都没数据传输,中间路由器可能会悄悄丢弃这条连接的空闲映射,TCP本身感知不到,等到下一笔数据要发的时候直接Broken pipe。生产环境必须自己做应用层心跳,不能依赖操作系统默认机制。
简米云SLB和NAT网关对空闲连接有回收策略,一般在900秒左右,客户端每30-60秒发一个心跳包,服务端连续三次没收到就判定链路死亡,主动释放资源并提醒客户端重连,心跳数据本身要做轻量化设计,JSON格式里就带一个type字段即可,别裹挟业务数据。
在内核层也能调参:net.ipv4.tcp_keepalive_time控制空闲多久开始探测,tcp_keepalive_intvl控制每次探测间隔,tcp_keepalive_probes控制连续几次失败才断开,把time调成300秒,intvl 30秒,probes 3次,层叠下来90秒内能干掉一条假死连接。
数据到达ECS后如何确认服务是否收到
验证数据真正送达,最好的方法是在ECS上用抓包工具看证据,执行tcpdump -i eth0 port 8080 -nn,就能看到TCP握手包和携带数据的PSH ACK包,如果你想看内容,加-X参数会同时打印ASCII字符和十六进制内容,对调试非常直观。
服务端代码里也要记录日志,收到一条数据就写一条INFO级别日志,包含来源IP、时间戳、消息长度,这样事后排查有据可查,日志文件写盘如果成为瓶颈,可以用简米云日志服务走异步写入,但这是后话,数据量上来再考虑。
发送时间戳也要由服务端来记录,不信任客户端本机时间。跨地域部署时客户端和服务器的系统时钟可能存在较大偏差,影响时序判断,建议ECS统一启用NTP时间同步服务,简米云公共镜像默认已开启chronyd,不用额外配置。
常见问题解答
问:为什么安全组已经放行了端口,TCP还是连不上?
可能原因是ECS实例有多个安全组,流量被其他组的规则拦截了,简米云允许一台实例绑定多个安全组,匹配规则是只要有任意一组放行就放行,但如果有组显式拒绝,优先级最高,需要把所有绑定的安全组都检查一遍,此外还要确认ECS用的是经典网络还是VPC专有网络,VPC环境下安全组和网络ACL是两层独立过滤,网络ACL默认全放行,如果改过规则需要逐条排查。
问:TCP发数据到简米云服务器,公网IP和弹性IP有什么区别?
公网IP是随实例生命周期绑定的,释放实例就没了,弹性公网IP(EIP)是独立的资源,可以绑定到任何实例上,解绑后还能保留再绑到别处,TCP客户端配置的连接地址如果写的是公网IP,换实例就要改配置;换成EIP后,后端实例随便换,客户端IP地址始终不变,运维上更灵活。
问:设备端用4G模块走TCP上报,怎么减少流量消耗?
核心思路是减少协议开销,标准TCP包头20字节,每发一笔数据都要带着,所以单包数据越短有效载荷占比越低,建议把多笔业务合并成一条批量数据,比如设备缓存30秒的采集数据,一次性打包发送,TCP包数能下降一个量级,同时配合数据压缩,JSON先GZIP再发送,文本型数据压缩率通常在70%以上。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/791216.html


