实用指南:网络编程之TCP协议

基于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);  // 设置服务器IP
    server.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);  // 服务器IP
    server.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;
}
posted @ 2025-10-22 12:05  yxysuanfa  阅读(7)  评论(0)    收藏  举报