基于TCP协议的网络编程
服务器端
1.创建socket;(socket)
2.绑定自己的地址信息;(bind)
3.监听客户端连接
listen
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int listen(int sockfd,int backlog);
函数功能: 监听客户端连接
函数参数:
sockfd: 监听套接字描述符
backlog:可排队连接的最大连接数
函数返回值: 成功返回0;
失败返回-1,错误码放在errno中
4.接收客户端的连接(accept)
accept
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int accept(int sockfd,struct sockaddr* addr,int *addlen);
函数功能: 等待接受客户端连接
函数参数:
sockfd: 监听套接字描述符
addr: [OUT]用于获取对方地址的结构体指针
addlen: [OUT]用于获取对方地址结构体长度的指针
函数返回值: 成功返回 通讯套接字描述符;
失败返回-1,错误码放在errno中
5.接收客户端的信息(recv)
recv
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int recv(int sockfd,void* buf,int size,int flags);
函数功能: 接收网络数据
函数参数:
sockfd: 通讯套接字描述符
buf: 内存缓冲区地址,用于存储接收到的数据
size: 内存缓冲区地址的长度
flags: 操作方式,一般写0
函数返回值: 成功返回 实际接收的字节数;
0: 表明通讯对方关闭连接或数据通讯
失败返回-1,错误码放在errno中
6.发送消息给客户端(send)
send
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int send(int sockfd,const void* buf,int size,int flags);
函数功能: 发送网络数据
函数参数:
sockfd: 通讯套接字描述符
buf: 内存缓冲区地址,用于存储待发送的数据
size: 待发送数据的长度
flags: 操作方式,一般写0
函数返回值: 成功返回 实际发送的字节数;
失败返回-1,错误码放在errno中
7.关闭套接字 (close)
客户端
1.创建socket;(socket)
2.连接服务器(connect)
connect
头文件: #include <sys/types.h>
#include <sys/socket.h>
函数原型: int connect(int sockfd,struct sockaddr* addr,int addlen);
函数功能: 等待接受客户端连接
函数参数:
sockfd: 监听套接字描述符
addr: 待连接的目标主机的地址结构体指针
addlen: 地址结构体长度
函数返回值: 成功返回 0;
失败返回-1,错误码放在errno中
3.接收服务端的信息(recv)
4.发送消息给服务端(send)
5.关闭套接字 (close)
示例:
TCP 服务器程序
#include "header.h"
int main(int argc, char** argv)
{if(argc < 3){fprintf(stderr, "Usage: %s servIP servPort\n", argv[0]);return -1;}// 1. 创建TCP监听套接字int sockl = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockl){perror("socket");return -1;}// 2. 设置服务器地址信息sin_t server = {AF_INET};inet_pton(AF_INET, argv[1], &server.sin_addr); // 设置服务器IPserver.sin_port = htons(atoi(argv[2])); // 设置服务器端口socklen_t len = sizeof(sin_t);// 3. 绑定套接字到指定地址if(-1 == bind(sockl, (sa_t*)&server, len)){perror("bind");close(sockl);return -1;}// 4. 开始监听连接请求if(-1 == listen(sockl, 5)) // backlog=5,等待队列长度{perror("listen");close(sockl);return -1;}printf("服务器启动在 %s:%s,等待客户端连接...\n", argv[1], argv[2]);// 5. 接受客户端连接sin_t client = {0};int sockc = accept(sockl, (sa_t*)&client, &len);if(sockc == -1) {perror("accept");close(sockl);return -1;}printf("[%s:%d]已连接\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));// 6. 向客户端发送欢迎消息const char* p = "welcome to server";if(send(sockc, p, strlen(p), 0) == -1) {perror("send");}// 7. 接收客户端消息char szbuf[64] = {0};ssize_t n = recv(sockc, szbuf, sizeof(szbuf)-1, 0);if(n != -1) {szbuf[n] = 0;printf("[%s:%d]发来消息:%s\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port), szbuf);} else {perror("recv");}// 8. 关闭套接字close(sockc);close(sockl);return 0;
}
TCP 客户端程序
#include "header.h"
int main(int argc, char** argv)
{if(argc < 3){fprintf(stderr, "Usage: %s servIP servPort\n", argv[0]);return -1;}// 1. 创建TCP套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(-1 == sockfd){perror("socket");return -1;}// 2. 设置服务器地址信息sin_t server = {AF_INET};inet_pton(AF_INET, argv[1], &server.sin_addr); // 服务器IPserver.sin_port = htons(atoi(argv[2])); // 服务器端口socklen_t len = sizeof(sin_t);// 3. 连接到服务器if(-1 == connect(sockfd, (sa_t*)&server, len)){perror("connect");close(sockfd);return -1;}printf("已连接到服务器 %s:%s\n", argv[1], argv[2]);// 4. 接收服务器欢迎消息char szbuf[64] = {0};ssize_t n = recv(sockfd, szbuf, sizeof(szbuf)-1, 0);if(n != -1) {szbuf[n] = 0;printf("[%s:%d]发来消息:%s\n", inet_ntoa(server.sin_addr), ntohs(server.sin_port), szbuf);} else {perror("recv");}// 5. 向服务器发送感谢消息const char* p = "thanks";if(send(sockfd, p, strlen(p), 0) == -1) {perror("send");}// 6. 关闭套接字close(sockfd);return 0;
}
TCP通信流程
服务器端流程:
socket() → bind() → listen() → accept() → send()/recv() → close()
客户端流程:
socket() → connect() → recv()/send() → close()
关键函数详解
服务器端函数:
1. listen()
int listen(int sockfd, int backlog);
将套接字置于监听状态
backlog
:等待连接队列的最大长度
2. accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
接受客户端连接
返回新的套接字用于与客户端通信
阻塞直到有客户端连接
客户端函数:
connect()
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
主动连接到服务器
建立TCP三次握手
数据传输函数:
send()
和 recv()
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
用于TCP连接的数据传输
相比UDP的
sendto()
/recvfrom()
更简单
编译和运行
编译服务器:
gcc -o tcp_server tcp_server.c
编译客户端:
gcc -o tcp_client tcp_client.c
运行示例:
终端1 - 启动服务器:
./tcp_server 0.0.0.0 8080
终端2 - 启动客户端:
./tcp_client 127.0.0.1 8080
预期输出:
服务器输出:
服务器启动在 0.0.0.0:8080,等待客户端连接...
[127.0.0.1:52345]已连接
[127.0.0.1:52345]发来消息:thanks
客户端输出:
已连接到服务器 127.0.0.1:8080
[127.0.0.1:8080]发来消息:welcome to server
TCP vs UDP 对比
特性 | TCP (流式套接字) | UDP (数据报套接字) |
---|---|---|
连接性 | 面向连接 | 无连接 |
可靠性 | 可靠传输 | 不可靠传输 |
顺序性 | 保证顺序 | 不保证顺序 |
函数 | connect() , accept() | sendto() , recvfrom() |
头部开销 | 较大 | 较小 |
适用场景 | 文件传输、Web | 视频流、DNS |
改进建议
1. 服务器支持多客户端
while(1) {sin_t client = {0};socklen_t len = sizeof(client);int sockc = accept(sockl, (sa_t*)&client, &len);if(sockc == -1) {perror("accept");continue;}// 创建新线程处理客户端pthread_t tid;pthread_create(&tid, NULL, handle_client, (void*)(long)sockc);pthread_detach(tid);
}
2. 添加错误处理
// 检查所有send/recv的返回值
ssize_t n = recv(sockc, szbuf, sizeof(szbuf)-1, 0);
if(n == -1) {perror("recv");close(sockc);continue;
} else if(n == 0) {printf("客户端断开连接\n");close(sockc);continue;
}
szbuf[n] = 0;
3. 设置超时
// 设置接收超时
struct timeval tv;
tv.tv_sec = 10; // 10秒超时
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
完整的改进版服务器
#include "header.h"
#include
int sockl; // 全局变量用于信号处理
void cleanup(int sig) {printf("\n服务器关闭...\n");close(sockl);exit(0);
}
int main(int argc, char** argv) {if(argc < 3) {fprintf(stderr, "Usage: %s servIP servPort\n", argv[0]);return -1;}signal(SIGINT, cleanup);sockl = socket(AF_INET, SOCK_STREAM, 0);// ... 绑定和监听代码 ...printf("服务器启动在 %s:%s (Ctrl+C退出)\n", argv[1], argv[2]);while(1) {sin_t client = {0};socklen_t len = sizeof(client);int sockc = accept(sockl, (sa_t*)&client, &len);if(sockc == -1) {if(errno == EINTR) break; // 被信号中断perror("accept");continue;}printf("[%s:%d] 已连接\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));// 处理客户端通信handle_client(sockc, client);}close(sockl);return 0;
}