一、TCP 服务器的网络编程流程

TCP 服务器的网络编程流程 (搭配客户端流程),核心是用socket相关函数实现 "服务器监听→客户端连接→收发数据" 的逻辑。我用 "开奶茶店" 的类比来讲解,同时给代码加通俗注释:
服务器流程 :像 "开奶茶店"------ 先准备店铺(socket)→挂招牌(bind绑定 IP + 端口)→开门等客(listen监听)→接待顾客(accept建立连接)→接单 / 出餐(recv收数据 /send发数据)→关门(close)。
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
// 1. 创建套接字:相当于"开奶茶店前,先租个店面(申请网络通信的'通道')"
// AF_INET=用IPv4,SOCK_STREAM=用TCP协议,0=默认协议
int sockfd = socket(AF_INET, SOCK_STREAM, 0); //tcp 创建套接字
if (sockfd == -1) // 如果开店失败(比如没权限)
{
exit(1); // 直接关门不干了
}
// 2. 准备"店铺地址信息":相当于"写清楚奶茶店的地址(IP)和门牌号(端口)"
struct sockaddr_in saddr,caddr; // 存地址的结构体
memset(&saddr,0,sizeof(saddr)); // 先清空地址信息
saddr.sin_family = AF_INET; // 用IPv4地址
saddr.sin_port = htons(6000); // 门牌号(端口):6000,htons是转成网络字节序
saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 店铺地址(IP):本地回环地址(自己电脑)
// 3. 绑定地址:相当于"把地址挂到店门口(让别人能找到这个店)"
// 把sockfd(店面)和saddr(地址)绑定
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if (res == -1 ) // 如果绑定失败(比如端口被占用)
{
printf("bind err\n"); // 打印"地址挂不上"
exit(1); // 关门
}
// 4. 监听:相当于"开门营业,最多同时接待5个排队的顾客"
listen(sockfd,5); // 第二个参数是"排队长度"
// 5. 循环接待顾客:一直开着店等客人
while( 1 )
{
int len = sizeof(caddr); // 存"顾客地址"的长度
// 接待顾客:相当于"从排队的人里接一个进来,建立单独的'服务通道'(c)"
// 这里会"阻塞"------没人来就一直等
int c = accept(sockfd,(struct sockaddr*)&caddr,&len); //可能阻塞
if (c < 0 ) // 如果接待失败
{
continue; // 不管他,等下一个顾客
}
printf("accept c=%d\n",c); // 打印"接待的顾客编号(c是新的套接字)"
// 6. 收顾客的点单:相当于"接顾客的订单"
char buff[128] = {0}; // 存订单的缓冲区
// recv从"顾客通道c"收数据,存在buff里,最多收127字节
int n = recv(c,buff,127,0);
printf("buff=%s\n",buff); // 打印"顾客点的单(收到的数据)"
// 7. 给顾客出餐:相当于"把奶茶给顾客"
send(c,"ok",2,0); // 给"顾客通道c"发"ok"(回复数据)
// 8. 送走顾客:相当于"这个顾客服务完了,关掉单独的通道"
close(c);
}
}
二、客户端流程
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main()
{
// 1. 创建套接字:相当于"顾客准备好'手机'(用来联系奶茶店)"
// AF_INET=用IPv4,SOCK_STREAM=用TCP协议(靠谱的连接)
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if ( sockfd == -1 ) // 如果"手机"坏了(创建失败)
{
exit(1); // 没法买奶茶了,走人
}
// 2. 准备"奶茶店的地址":相当于"查奶茶店的地址(IP)和门牌号(端口)"
struct sockaddr_in saddr; // 存奶茶店地址的结构体
memset(&saddr,0,sizeof(saddr)); // 清空地址
saddr.sin_family = AF_INET; // 用IPv4地址
saddr.sin_port = htons(6000); // 奶茶店的门牌号(端口):6000
saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 奶茶店地址(IP):本地回环(和服务器在同一台电脑)
// 3. 连接奶茶店:相当于"顾客走到奶茶店门口,敲门进店"
int n = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if ( n == -1 ) // 如果敲门没人开(连接失败,比如服务器没开)
{
printf("connect err\n"); // 打印"连不上奶茶店"
exit(1); // 走人
}
// 4. 点单:相当于"顾客告诉店员要喝什么"
char buff[128] = {0}; // 存点单内容的缓冲区
fgets(buff,128,stdin); // 从键盘输入点单内容(比如输入"一杯珍珠奶茶")
// 把点单内容通过"手机(sockfd)"发给奶茶店
send(sockfd,buff,strlen(buff)-1,0); // strlen(buff)-1是去掉输入的换行符
// 5. 拿奶茶:相当于"接收店员给的奶茶"
memset(buff,0,sizeof(saddr)); // 清空缓冲区,准备接奶茶
recv(sockfd,buff,127,0); // 从"手机"收服务器的回复(比如"ok")
printf("buff=%s\n",buff); // 打印收到的回复
// 6. 离开:相当于"喝完奶茶,离开店铺"
close(sockfd); // 关掉"手机"(断开连接)
exit(0);
}
客户端核心流程(对应 "买奶茶")
socket:准备 "联系工具"(对应手机);- 填地址:查 "奶茶店的 IP + 端口";
connect:走到奶茶店门口(建立连接);send:告诉店员点什么(发数据);recv:拿到店员给的奶茶(收数据);close:喝完走人(断开连接)。
和服务器的对应关系
- 客户端的
connect,对应服务器的accept(一个 "敲门",一个 "开门接待"); - 客户端的
send/recv,对应服务器的recv/send(双方互相发 / 收数据)。
三、测试
