前言

本笔记记录的是 单个服务端并发式处理多个客户端

下次有空在发个 单线程多个服务端并发式处理多种客户端。其实就是本笔记的一个改良版,用到select() / poll() / epoll()。

原文:https://www.cnblogs.com/lizhuming/p/14943969.html

实现原理

实现原理很简单,写出一个简单的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 和 端口号。

实验结果

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

参考源码

服务端参考源码

/** @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);
}

【TCP/IP】TCP服务器并发处理&源码的更多相关文章

  1. 【转】TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端)、UDP客户端

    [转]TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端).UDP客户端 目录 说明 TCP/UDP通信主要结构 管理多个Socket的解决方案 框架中TCP部分的使用 框架中UDP ...

  2. C#中国象棋+游戏大厅 服务器 + 客户端源码

    来源:www.ajerp.com/bbs C#中国象棋+游戏大厅 服务器 + 客户端源码 源码开源 C#版中国象棋(附游戏大厅) 基于前人大虾的修改版 主要用委托实现 服务器支持在线人数,大厅桌数的设 ...

  3. TCP最大报文段MSS源码分析

    概述 本文主要对MSS相关的几个字段结合源码流程进行分析: 字段含义 user_mss(tcp_options_received)–用户配置的mss,优先级最高: mss_clamp(tcp_opti ...

  4. TCP三次握手Linux源码解析

    TCP是面向连接的协议.面向连接的传输层协议在原点和重点之间建立了一条虚拟路径,同属于一个报文的所有报文段都沿着这条虚拟路径发送,为整个报文使用一条虚拟路径能够更容易地实施确认过程以及对损伤或者丢失报 ...

  5. TCP/UDP简易通信框架源码,支持轻松管理多个TCP服务端(客户端)、UDP客户端

    目录 说明 TCP/UDP通信主要结构 管理多个Socket的解决方案 框架中TCP部分的使用 框架中UDP部分的使用 框架源码结构 补充说明 源码地址 说明 之前有好几篇博客在讲TCP/UDP通信方 ...

  6. 在Ubuntu 12 服务器上源码安装 OpenERP 8.0

    原文:http://vivianyw.blog.163.com/blog/static/134547422201421112349489/ 1. 安装SSH: sudo apt-get install ...

  7. [TCP/IP] TCP在listen时的参数backlog的意义

    linux内核中会维护两个队列:  1)未完成队列:接收到一个SYN建立连接请求,处于SYN_RCVD状态  2)已完成队列:已完成TCP三次握手过程,处于ESTABLISHED状态  3)当有一个S ...

  8. Jenkins构建Git manager服务器的源码

    1,下载scm manager 源码库管理,下载后解压即可: 2,运行bin目录下的scm-server.bat文件,启动服务: 3,建议将scm-server放在git客户端的安装目录下,方便管理 ...

  9. 一个基于TCP/IP的服务器与客户端通讯的小项目(超详细版)

    1.目的:实现客户端向服务器发送数据 原理: 2.建立两个控制台应用,一个为服务器,用于接收数据.一个为客户端,用于发送数据. 关键类与对应方法: 1)类IPEndPoint: 1.是抽象类EndPo ...

随机推荐

  1. 手把手教你部署验证freeswitch(避免踩坑)

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 介绍:freeswitch可集成ASR(语音识别)和TTS(文本转语音)创建智能电话机器人和用户通话,可用于问卷调查,自动催缴等业务,电 ...

  2. Flink去重统计-基于自定义布隆过滤器

    一.背景说明 在Flink中对流数据进行去重计算是常有操作,如流量域对独立访客之类的统计,去重思路一般有三个: 基于Hashset来实现去重 数据存在内存,容量小,服务重启会丢失. 使用状态编程Val ...

  3. MzzTxx——博客目录

    准备阶段 团队介绍 需求分析 技术规格说明书 功能规格说明书 Alpha 阶段任务分配 团队贡献分分配方案 Scrum Meeting Alpha 2021.04.21 Scrum Meeting 0 ...

  4. 简单聊聊内存逃逸 | 剑指offer - golang

    问题 简单讲讲golang的内存逃逸吗? 解析 什么是内存逃逸 在程序中,每个函数块都会有自己的内存区域用来存自己的局部变量(内存占用少).返回地址.返回值之类的数据,这一块内存区域有特定的结构和寻址 ...

  5. ES6学习-1 啥是ES6啊

    我不准备刨根问底,找寻他的十八辈祖先,希望用最简单的方式能够说明白ES6是个啥,come on baby ES6的全称是ECMAScript 6.0,他于2015 年 6 月正式发布,它的目标是使得 ...

  6. netperf对比

    netperf -H 10.1.60.141 -t TCP_STREAM -l 60  -p 10082 netperf -H 10.1.60.141 -t UDP_STREAM -l 60  -p ...

  7. kvm虚拟化安装与部署(2)

    一.虚拟化VT开启确认 KVM 本身也有一些弱点,那就是相比裸金属虚拟化架构的 Xen . VMware ESX 和 HyperV , KVM 是运行在 Linux 内核之上的寄居式虚拟化架构,会消耗 ...

  8. 技术干货 | 关于 WKWebview 网络拦截,你想知道的都在这里

    原生 WKWebView 在独立于 app 进程之外的进程中执行网络请求,请求数据不经过主进程,因此在 WKWebView 上直接使用 NSURLProtocol 是无法拦截请求的. 但是由于 mPa ...

  9. Spring Boot自定义属性配置文件开启提示

    前言:有时候在Sping Boot项目中需要自定义属性.又想在配置文件(*.properties)中显示提示时. 测试环境:Sping Boot2x + Maven + Lombok 准备测试的配置类 ...

  10. Linux(CentOS7)下rpm安装MySQL8.0.16

    记录一下自己在 CentOS7 下 rpm 安装 MySQL8.0.16 的过程. 一.准备工作 1. 下载MySQL所需要的安装包 从 MySQL官网 下载,上传至 CentOS 系统 /usr/l ...