友链

Ctrl+F搜索服务端代码客户端代码获取代码

服务端的线程数组有点类似于线程池,但不同的是,我们这里的实现并没有将线程重新回收到线程池中,而是不停的去创建

detach新创建出来的线程,在其完成任务(回调函数worker返回)之后会自动销毁,此时,我们只需要将其所在的结构体的fd成员的值设为-1,表示sockInfo结构体数组没有满就行了

构建服务端:
gcc server.c -o bin/server -lpthread

构建客户端:
gcc client.c -o bin/client -lpthread

运行效果:https://www.bilibili.com/video/BV19a411D75U/

同时有五个客户端和服务端建立连接,可以看到几乎是没有任何压力的

服务端代码

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>struct sockInfo
{int fd; // 通信的文件描述符struct sockaddr_in addr;pthread_t tid; // 线程号
};struct sockInfo sockinfos[128];// 这里的参数arg是sockInfo结构体指针
void* worker(void *arg)
{// 类型强转struct sockInfo *pinfo = (struct sockInfo *)arg;// ip字符串长度 3*4+3+1 = 16,之所以要多1,是因为还有一个\0// 下面是不给\0留空间的结果,编译无法通过// https://img-blog.csdnimg.cn/ee7ecf18a4474eaebe6359fdd5a3398a.pngchar cliIp[16];// pton和ntop函数也比较好记忆,和之前的ntohs族差不多,而且还少了数据类型,更容易记忆// p就是presentation,即表现、呈现,其实就是我们人类可读的显示方式// n是network,即网络形式,人类不可读// pton就是把形如x.x.x.x的IP字符串转换成c语言函数可以使用的网络地址形式// ntop反之// 这里我们将sockaddr_in结构体的in_addr成员结构体的s_addr成员// 即网络形式的IP地址转换成了人类可读的IP字符串// 这里由于cliIp为静态分配内存,因此可以使用sizeof计算出内存大小// 关于sizeof的用法,参考:https://blog.csdn.net/ma_de_hao_mei_le/article/details/125701408inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, cliIp, sizeof(cliIp));// 提取出端口号unsigned short cliPort = ntohs(pinfo->addr.sin_port);printf("client ip is : %s, prot is %d\n", cliIp, cliPort);// 接收客户端发来的数据char recvBuf[1024];while (1){int len = read(pinfo->fd, &recvBuf, sizeof(recvBuf));if (len == -1){perror("read");exit(-1);}else if (len > 0){printf("recv client : %s\n", recvBuf);}else if (len == 0){printf("client closed....\n");break;}write(pinfo->fd, recvBuf, strlen(recvBuf) + 1);}close(pinfo->fd);// 将该文件描述符标记为可用pinfo->fd = -1;return NULL;
}int main()
{// 创建socketint lfd = socket(PF_INET, SOCK_STREAM, 0);if (lfd == -1){perror("socket");exit(-1);}struct sockaddr_in saddr;saddr.sin_family = AF_INET;// 关于htons函数族的区分和记忆:https://blog.csdn.net/ma_de_hao_mei_le/article/details/125695622saddr.sin_port = htons(9999);// 相当于监听到0.0.0.0saddr.sin_addr.s_addr = INADDR_ANY;// 绑定int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));if (ret == -1){perror("bind");exit(-1);}// 监听ret = listen(lfd, 128);if (ret == -1){perror("listen");exit(-1);}// 初始化数据int max = sizeof(sockinfos) / sizeof(sockinfos[0]);for (int i = 0; i < max; i++){bzero(&sockinfos[i], sizeof(sockinfos[i]));sockinfos[i].fd = -1;sockinfos[i].tid = -1;}// 循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信while (1){printf("server is waiting on 0.0.0.0:9999 ...\n");struct sockaddr_in cliaddr;int len = sizeof(cliaddr);// 接受连接int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);// 自定义的一个包含子线程信息的结构体// 包括盖子线程连接的客户端的地址,线程ID以及所对应的文件描述符struct sockInfo *pinfo;for (int i = 0; i < max; i++){// 从这个数组中找到一个可以用的sockInfo元素// sockinfos是一个全局的sockInfo结构体数组,长度为128// 如果结构体中的fd成员值为-1,说明处于可用状态// 直接跳出循环,结束遍历即可if (sockinfos[i].fd == -1){pinfo = &sockinfos[i];break;}// 如果已经遍历完了,还是没有找到可用的,就进行循环,直到有可用的sockInfo出现if (i == max - 1){sleep(1);i--;}}// 对pinfo结构体进行填充,fd成员保存客户端的文件描述符pinfo->fd = cfd;// 将客户端的地址拷贝到addr成员,这里之所以用memcpy进行内存拷贝// 是因为cliaddr所指向的内存空间中的内容会在下一次的循环中被覆盖memcpy(&pinfo->addr, &cliaddr, len);// 创建子线程,将传出参数设置为成员tid的指针// 回调函数working负责实现线程处理操作// 使用thread_attr记录pthread_create创建出来的线程的属性,以便后面进行判断pthread_attr_t thread_attr;pthread_attr_init(&thread_attr);pthread_create(&pinfo->tid, &thread_attr, worker, pinfo);// 分离线程,这样我们就不用操心该线程的资源回收问题了// 根据文档,对已经detach的线程再次调用detach函数,将会导致未指定的行为// 因此最好的做法是在detach之前进行判断int detachstate;pthread_attr_getdetachstate(&thread_attr, &detachstate);pthread_attr_destroy(&thread_attr);if (PTHREAD_CREATE_DETACHED != detachstate)pthread_detach(pinfo->tid);}close(lfd);return 0;
}

客户端代码

// TCP通信的客户端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main() {// 1.创建套接字int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1) {perror("socket");exit(-1);}// 2.连接服务器端struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;// 这里由于我们是本地测试,直接写环回地址127.0.0.1即可inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);serveraddr.sin_port = htons(9999);int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));if(ret == -1) {perror("connect");exit(-1);}// 3. 通信char recvBuf[1024];int i = 0;while(1) {sprintf(recvBuf, "data : %d\n", i++);// 给服务器端发送数据write(fd, recvBuf, strlen(recvBuf)+1);int len = read(fd, recvBuf, sizeof(recvBuf));if(len == -1) {perror("read");exit(-1);} else if(len > 0) {printf("recv server : %s\n", recvBuf);} else if(len == 0) {// 表示服务器端断开连接printf("server closed...");break;}sleep(1);}// 关闭连接close(fd);return 0;
}

C语言 多线程实现TCP并发服务器相关推荐

  1. linux网络编程-多线程实现TCP并发服务器

    客户端跟服务端通信流程 服务端流程步骤 socket函数创建监听套接字lfd; bind函数将监听套接字绑定ip和端口: listen函数将服务器设置为被动监听状态,同时创建一条未完成连接队列(没走完 ...

  2. Linux网络编程——tcp并发服务器(多线程)

    https://blog.csdn.net/lianghe_work/article/details/46504243 tcp多线程并发服务器 多线程服务器是对多进程服务器的改进,由于多进程服务器在创 ...

  3. Linux网络编程——tcp并发服务器(多进程)

    https://blog.csdn.net/lianghe_work/article/details/46503895 一.tcp并发服务器概述 一个好的服务器,一般都是并发服务器(同一时刻可以响应多 ...

  4. 如何使用 Go 语言搭建企业级高并发服务器?

    每到节假日和过年,需要外出通行的人几乎都会遇到一个问题:抢火车票!当全国上亿人都在固定的时间段抢票,服务器动辄就要承受上百万级并发的情况时,你就会明白,一个支持高并发的服务器架构有多重要! 在后端程序 ...

  5. tcp并发服务器_在Go中构建并发TCP服务器

    tcp并发服务器 本文是Mihalis Tsoukalos的"围棋"系列的一部分. 阅读第1部分: 在Go中创建随机,安全的密码 . TCP和UDP服务器无处不在,通过TCP / ...

  6. go语言实现tcp并发服务器与客户端

    go语言实现tcp并发服务器端与客户端 server.go // nc 连接, 发送字母, 加收到转大写的字母 // 如果发送exit ,则会断开连接 package mainimport (&quo ...

  7. 1 linux下tcp并发服务器的几种设计的模式套路,Linux下几种并发服务器的实现模式(详解)...

    1>单线程或者单进程 相当于短链接,当accept之后,就开始数据的接收和数据的发送,不接受新的连接,即一个server,一个client 不存在并发. 2>循环服务器和并发服务器 1.循 ...

  8. Linux网络编程——tcp并发服务器(epoll实现)

    https://blog.csdn.net/lianghe_work/article/details/46551871 通过epoll实现tcp并发回执服务器(客户端给服务器发啥,服务器就给客户端回啥 ...

  9. C语言 TCP并发服务器

    友链 gcc 1.c -o 1 -lpthread gcc 2.c -o 2 -lpthread 服务端 // ..使用内存映射可以拷贝文件 /* 对原始文件进行内存映射 创建一个新文件 把新文件的数 ...

最新文章

  1. C++:07---this指针
  2. Spring Framework源码编译,开始Spring源码学习
  3. 计算机视觉算法岗面经,2019秋招资料
  4. 深入浅出Node.js(一):什么是Node.js
  5. mysql 循环插入记录
  6. 全网通是4g显示无服务器,4G+时代的全网通?可没有那么简单!
  7. BT656与BT1120的区别
  8. 20220906_C52单片机学习笔记 | LED闪烁
  9. 【Python爬虫】新手入门案例教学(一):爬取豆瓣电影排行有关信息
  10. https://blog.csdn.net/qq_43412289
  11. design pattern Builder 建造者设计模式
  12. RedHat / Centos   Linux 系统运维与管理实践技巧荟萃,持续更新
  13. linux 如何安装maven
  14. 生产订单组件新增 修改 删除
  15. IBM ServerGuide 9.21
  16. c语言编写邮箱注册登录的程序,c语言实现邮箱地址验证
  17. 简单的JS实现口风琴设计
  18. 转载:报表软件比较参考
  19. Python实战|js逆向steam社区
  20. 逆向分析系列——常见的脱壳工具

热门文章

  1. Android 最新版 Paypal 智能付款按钮 Paypal JavaScript SDK 集成:Smart Payment Buttons
  2. python中全组合函数(combinations)与全排列函数(permutations)
  3. 多项式计算(python)
  4. 合并两个有序链表(java算法)
  5. win7访问共享文件出现登录失败:禁用当前用户
  6. MATLAB学习——数据类型(结构体、数组、单元数组、map容器类型)
  7. 微信小程序课程的学习心得
  8. int 类型怎么判断空
  9. ENVI经验|基于多源遥感影像的红树林范围提取3-监督分类
  10. 电感为什么是电压超前90度——问题整理