第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > C语言 多线程实现TCP并发服务器

C语言 多线程实现TCP并发服务器

时间:2021-04-08 12:32:23

相关推荐

C语言 多线程实现TCP并发服务器

友链

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

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

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

构建服务端:

gcc server.c -o bin/server -lpthread

构建客户端:

gcc client.c -o bin/client -lpthread

运行效果:/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-/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的用法,参考:/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函数族的区分和记忆:/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;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。