【TCP/IP】我把大学四年私藏的TCP服务器并发处理&源码贡献出来了

关于TCP/IP这个知识点也是大仙在大学的时候死磕了很久才完善理解,在大学老师也基本只会讲原理知识完全不会教你怎么去敲代码,全靠自己一步一步敲出来,自学确实很难,能坚持下来就非常的不容易了!

实现原理

实现原理很简单,写出一个简单的TCP服务器后,其客户端处理方式采用线程化处理即可。
其中要注意的是多线程并发问题。

多线程处理客户端是把 connect_sockfd 传到线程,然后让线程处理。

TCP 服务端

简要步骤

  1. 创建 socket
  2. 配置地址数据。
  3. socket 绑定地址。
  4. 监听。( listen()
  5. 进入循环处理。
    1. accept() 获取一个连接进来客户端的 connect_socket
    2. 创建一条线程专门处理该链接,并把该 connect_socket 传给该线程。

注意

注意:

  1. 传递给客户端处理线程的是 connect_socket 的值。注意传递的技巧及运行设备的系统 bit 位数。
  2. 本程序采用一个全局原子整数来维护客户端的连接数。
  3. 其中 #include "atomic_lzm.h" 是个人实现的 linux 应用层的原子库,就只是简单的封装一下而已,利用的是 posix 互斥锁 API。有需要的直接去我 gitee 上拿。

TCP 客户端

简要步骤

  1. 创建 socket
  2. 配置地址数据。
  3. 建立 TCP 连接。
  4. 连接成功后进入数据交互。

注意

注意:

  1. 注意服务端的 IP 和 端口号。

实验结果

【TCP/IP】我把大学四年私藏的TCP服务器并发处理&源码贡献出来了

需要测试客户端限制的可以多开几个客户端即可。有兴趣的同学可以自己验证哈哈。

参考源码

服务端参考源码

/** @file         server.c
 *  @brief        简要说明
 *  @details      详细说明
 *  @author       lzm
 *  @date         2021-06-10 16:49:46
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *  @blog         https://www.cnblogs.com/lizhuming/
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <semaphore.h>
#include "atomic_lzm.h"

// 注意: 接收缓冲区对应对方的发送缓冲区小于等于就不会出现对方接收多次。

#define CLIENT_BUFF_MAX 1024 // 客户端接收缓冲区 size
#define MY_SERVER_PORT 8080 // 服务端监听端口
#define CLIENT_PENDING_NUM 10 // 内核未完成队列的客户端缓冲数
#define CLIENT_MAX_NUM 2 // 客户端连接数限制

// 定义一个无名信号量
//sem_t sem_client;

atomic_lzm_t g_client_num;

/**
 * @name   client_fun
 * @brief  线程函数,处理 client
 * @param  arg:资源
 * @retval 
 * @author lzm
 */
void *client_fun(void *arg)
{
    int recv_len = 0; // 接收长度
    char recv_buff[CLIENT_BUFF_MAX] = "";
    long connfd = (long)arg; // 已连接socket。 值传递(地址传递时注意多线程并发问题)

    // [1] 接收数据
    while((recv_len = recv(connfd, recv_buff, sizeof(recv_buff), 0)) > 0)
    {
        printf("[%ld]recv_buff:%s\r\n", connfd, recv_buff); // 打印数据
        send(connfd, "OK", 2, 0); // 返回 client
    }

    // [2] 关闭 socket
    printf("client closed!\r\n");
    close(connfd); // 关闭 socket
    //sem_post(&sem_client);
    atomic_lzm_sub(&g_client_num);
    printf("can link num is [%d]\n", CLIENT_MAX_NUM-atomic_lzm_get(&g_client_num));
    return NULL;
}

/**
 * @name   main
 * @brief  main函数,服务端处理
 * @param  
 * @retval 
 * @author lzm
 */
int main(int argc, char *argv[])
{
    int sockfd = 0; // 监听 socket
    long connfd = 0; // 已连接 socket 。long 避免使用 64bit 机器而出错
    int ret = 0; // 返回缓冲
    struct sockaddr_in local_server_addr; // 本地服务器地址
    unsigned short local_server_port = MY_SERVER_PORT; // 本地服务器监听端口
    pthread_t thread_client_id; // client 线程 id

    atomic_lzm_init(&g_client_num, 0);

    printf("TCP server started at port [%d]!\n", local_server_port);

    // [1] 创建套接字
    printf("create server socket!\n");
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        printf("%s-%s-%d:sockfd create faild!", __FILE__, __FUNCTION__, __LINE__);
        perror("socket create error");
        exit(-1);
    }

    // [2] 初始化地址数据
    printf("init server address!\n");
    bzero(&local_server_addr, sizeof(local_server_addr)); // 初始化服务器地址
    local_server_addr.sin_family = AF_INET;
    local_server_addr.sin_port = htons(local_server_port);
    local_server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // [3] 绑定
    printf("bing server socket and addr!\n");
    ret = bind(sockfd, (struct sockaddr*)&local_server_addr, sizeof(local_server_addr));
    if(ret != 0)
    {
        printf("%s-%s-%d:sockfd bind faild!", __FILE__, __FUNCTION__, __LINE__);
        perror("socket bind error");
        close(sockfd);
        exit(-1);
    }

    // [4] 监听
    printf("listen server socket!\n");
    ret = listen(sockfd, CLIENT_PENDING_NUM);
    if(ret != 0)
    {
        printf("%s-%s-%d:sockfd listen faild!", __FILE__, __FUNCTION__, __LINE__);
        perror("socket listen error");
        close(sockfd);
        exit(-1);
    }

    //sem_init(&sem_client, 0, CLIENT_MAX_NUM);

    printf("accept!\n");

    // [5] 处理 client
    while(1)
    {
        char client_ip[INET_ADDRSTRLEN] = ""; // use for save client ip
        struct sockaddr_in client_addr; // use for save client address
        socklen_t client_len = sizeof(client_addr); // 必须初始化

        //sem_wait(&sem_client);

        // [5][1] 获取一个已建立的连接
        connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len);
        if(connfd < 0)
        {
            printf("%s-%s-%d:sockfd accept faild!", __FILE__, __FUNCTION__, __LINE__);
            perror("accept error");
            continue;
        }

        if(atomic_lzm_get(&g_client_num) >= CLIENT_MAX_NUM)
        {
            close(connfd);
        }
        else
        {
            // [5][2] 处理客户端数据
            inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
            printf("clien ip = [%s], port = [%d]\n", client_ip, ntohs(client_addr.sin_port));

            if(connfd > 0)
            {
                // 线程处理客户端数据
                pthread_create(&thread_client_id, NULL, (void *)client_fun, (void *)connfd); // creat thread。注意 64bit 机器地址是8byte的
                pthread_detach(thread_client_id); // thread 分离。即时,线程回调函数结束时自动回收该线程资源
                atomic_lzm_add(&g_client_num);
            }
        }
        printf("can link num is [%d]\n", CLIENT_MAX_NUM-atomic_lzm_get(&g_client_num));
    }

    // [6] 关闭 server socket
    close(sockfd);

    return 0;
}

TCP 客户端源码

/** @file         client.c
 *  @brief        简要说明
 *  @details      详细说明
 *  @author       lzm
 *  @date         2021-06-10 09:49:46
 *  @version      v1.0
 *  @copyright    Copyright By lizhuming, All Rights Reserved
 *  @blog         https://www.cnblogs.com/lizhuming/
 **********************************************************
 *  @LOG 修改日志:
 **********************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>

#define sHOST "192.168.112.128" // 服务器端IP
#define sPORT 8080                  // 服务器进程端口号

#define BUFFER_SIZE (1*1024)   // 数据域大小

// 注意: 接收缓冲区对应对方的发送缓冲区小于等于就不会出现对方接收多次。


/**
  * @brief  读 线程
  * @param 
  * @retval 
  * @author
  */
void *client_read_fun(void *arg) 
{
    int recv_len = 0; // 接收长度
    char recv_buff[BUFFER_SIZE] = "";
    int sockfd = *(int *)arg; // 已连接的 socket

    while((recv_len = recv(sockfd, recv_buff, sizeof(recv_buff), 0)) > 0)
    {
        printf("[%d]recv_buff:%s\r\n", sockfd, recv_buff);
    }

    printf("exit read thread!");
    return NULL;
}

/**
  * @brief  主函数
  * @param 
  * @retval 
  * @author
  */
int main(void)
{
    int sockfd; // 套接字描述符
    int ret;    // 返回结果
    struct sockaddr_in server; // 套接字地址
    char data_buffer[BUFFER_SIZE];  // app 数据域
    pthread_t thread_read_id; // 读 线程 id

    /* [1] 创建套接字 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        printf("creat socket failed!\n");
        exit(1);
    }

    /* [2] 初始化地址 */
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(sPORT);
    server.sin_addr.s_addr = inet_addr(sHOST);

    /* [3] 建立TCP连接 */
    ret = connect(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr));
    if(ret == -1)
    {
        printf("connect server failed!\n");
        close(sockfd);
        exit(1);
    }

    printf("connect server success!");

    /* [4][1] 创建 读 线程 */
    //pthread_create(&thread_read_id, NULL, (void *)client_read_fun, (void *)&(sockfd));
    //pthread_detach(thread_read_id); // 线程分离

    /* [4][2] 进入循环 写 */
    while(1)
    {
        printf("please enter some text:");
        fgets(data_buffer, BUFFER_SIZE, stdin);

        // 输入end,退出循环
        if(strncmp(data_buffer, "end", 3) == 0)
            break;

        send(sockfd, data_buffer, sizeof(data_buffer), 0);

        bzero(data_buffer, sizeof(data_buffer));

        recv(sockfd, data_buffer, sizeof(data_buffer), 0);

        printf("data_buff:%s\r\n", data_buffer);

    }

    /* [5] 退出 */
    close(sockfd);
    exit(0);
}

总结

新2021整理收集的一些高频面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法等详细讲解,也有详细的学习规划图,面试题整理等

TCP/IP是指能够在多个不同网络间实现信息传输的协议簇,由网络层的IP协议和传输层的TCP协议组成。其定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。它是Internet最基本的协议,也是Internet国际互联网络的基础。

上一篇:Linux C实现简单,多进程,多线程服务器


下一篇:Linux编程学习笔记:网络编程 核心