TCP网络通信/线程池
来源:互联网 发布:用友u8数据备份 编辑:程序博客网 时间:2024/06/05 16:00
TCP网络通信
预备知识
- socket: 在TCP/IP协议中表示:IP地址+TCP端口号或UDP端口号唯一标识网络通讯中的一个进程,IP地址+端口号就称为socket。
- 网络字节序: 在学习C语言的时候,我们大家都应该知道大小端 的问题,在网络数据流中同样也有大小端之分,网络数据流的地址规定: 先发出的数据是低地址,后发出的地址是高地址,即网络数据流应采用大端字节序,低地址高字节。
#include<arpa/inet.h>uint32_t htonl(uint32_t hostlong);uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);uint16_t ntohl(uint16_t netshort);
以上4个函数是库中对网络字节序和主机字节序的转换,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
TCP套接字通信
在套接字通信当中,我们分为server端与client端首先从服务器端(server端)讨论:
一、首先生成一个套接字文件,在套接字文件中:
int socket(int domain, int type, int protocol);
doamin: 一般我们采用TPv4,所以全部填 AF_INET;
type我们正在这介绍两种:
SOCK_STREAM(流式服务,在TCP通信中使用)
SOCK_DGREAM(数据报服务,在UDP通信中使用)
二、 把服务器端与指定的socket绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
- 绑定的结构体是我们自己定义的一个本地的信息;
三、 把服务器设置为监听状态
int listen(int sockfd, int backlog)
四、服务器开始接受客户端传来的数据
int accept(itn sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 绑定的结构体是我们要接受的客户端的信息,最后一个参数可做输入输出性参数 ;
在服务器端完成以上4个步骤后大致就可以完成服务器端的端口设置;
下面介绍客户端(client)的接口:
一、创建sock文件
二、不需要绑定,也可以绑定;但是一般不建议绑定
三、客户端只需要确保和服务器端连接上就可以了,不需要别的步骤:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 绑定的结构体一定是服务器端的;
上面就是全部接口,下面进行代码的实现:
client端
不管对于是单用户,多进程,多线程中,客户端的代码不会变
#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/types.h>#include <arpa/inet.h>#include <netinet/in.h>#include <unistd.h>#include <string.h>static void usage(const char* proc){ printf("Usage: %s[local_ip] [local_port]\n", proc);}int main(int argc, char *argv[]){ if(argc != 3) { usage(argv[0]); return 1; } int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); return 1; } struct sockaddr_in server; server.sin_family = AF_INET; server.sin_addr.s_addr= inet_addr(argv[1]); server.sin_port = htons(atoi(argv[2])); socklen_t len = sizeof(struct sockaddr_in); if(connect(sock, (struct sockaddr *)&server, len) < 0) { perror("connect"); return 2; } printf("connect success...\n"); char buf[1024]; while(1) { printf("send###"); fflush(stdout); ssize_t _r = read(0, buf, sizeof(buf)-1); if(_r > 0) { buf[_r] = 0; write(sock, buf, strlen(buf)); } write(1, buf, strlen(buf)); } close(sock); return 0;}
server端:
单进程
#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/types.h>#include <arpa/inet.h>#include <netinet/in.h>#include <unistd.h>#include <string.h>#include <pthread.h>static void usage(const char* proc){ printf("Usage: %s[local_ip] [local_port]\n", proc);}int startup(const char *_ip, int _port){ int sock = socket(AF_INET, SOCK_STREAM, 0); //创建套接字文件 if(sock < 0) { perror("sock"); exit(2); } int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//解决server bind error的函数 struct sockaddr_in local; local.sin_family = AF_INET; local.sin_addr.s_addr = inet_addr(_ip); local.sin_port = htons(_port); if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0) //套接字文件与输入IP,port绑定 { perror("bind"); exit(3); } if(listen(sock, 10) < 0) //将服务器段设置为监听状态 { perror("listen"); exit(4); } return sock;}int main(int argc, char* argv[]){ //./tcp_server ip port if(argc != 3) { usage(argv[0]); return 1; } int listen_sock = startup(argv[1], atoi(argv[2])); printf("bind and listen success...\n"); while(1) { struct sockaddr_in client; socklen_t len = sizeof(client); int new_sock = accept(listen_sock, (struct sockaddr *)&client, &len);//创建一个新的将接收的sock分别工作 if(new_sock < 0) { perror("accept"); continue; } printf("get a client, ip:%s, port:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));// version 1 单用户, 没什么用了已经,练手来的 while(1) { char buf[1024]; ssize_t _r = read(new_sock, buf, sizeof(buf)-1); if(_r > 0) { buf[_r] = 0; printf("client#:%s",buf); } else if(_r == 0) { close(new_sock); printf("client is quit!\n"); break; }else { perror("read"); close(new_sock); break; } } } return 0;}
多进程
#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/types.h>#include <arpa/inet.h>#include <netinet/in.h>#include <unistd.h>#include <string.h>#include <pthread.h>static void usage(const char* proc){ printf("Usage: %s[local_ip] [local_port]\n", proc);}int startup(const char *_ip, int _port){ int sock = socket(AF_INET, SOCK_STREAM, 0); //创建套接字文件 if(sock < 0) { perror("sock"); exit(2); } int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//解决server bind error的函数 struct sockaddr_in local; local.sin_family = AF_INET; local.sin_addr.s_addr = inet_addr(_ip); local.sin_port = htons(_port); if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0) //套接字文件与输入IP,port绑定 { perror("bind"); exit(3); } if(listen(sock, 10) < 0) //将服务器段设置为监听状态 { perror("listen"); exit(4); } return sock;}int main(int argc, char* argv[]){ //./tcp_server ip port if(argc != 3) { usage(argv[0]); return 1; } int listen_sock = startup(argv[1], atoi(argv[2])); printf("bind and listen success...\n"); while(1) { struct sockaddr_in client; socklen_t len = sizeof(client); int new_sock = accept(listen_sock, (struct sockaddr *)&client, &len);//创建一个新的将接收的sock分别工作 if(new_sock < 0) { perror("accept"); continue; } printf("get a client, ip:%s, port:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));// version 2 多进程 pid_t id = fork(); if(id < 0) { close(new_sock); } if(id == 0) {//child close(listen_sock); if(fork() > 0) { exit(0); } while(1) { char buf[1024]; ssize_t _r = read(new_sock, buf, sizeof(buf)-1); if(_r > 0) { buf[_r] = 0; printf("client#:%s",buf); } else if(_r == 0) { close(new_sock); printf("client is quit!\n"); break; }else { perror("read"); close(new_sock); break; } } close(new_sock); } else //father { close(new_sock); } } return 0;}
多线程
#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <sys/types.h>#include <arpa/inet.h>#include <netinet/in.h>#include <unistd.h>#include <string.h>#include <pthread.h>static void usage(const char* proc){ printf("Usage: %s[local_ip] [local_port]\n", proc);}int startup(const char *_ip, int _port){ int sock = socket(AF_INET, SOCK_STREAM, 0); //创建套接字文件 if(sock < 0) { perror("sock"); exit(2); } int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//解决server bind error的函数 struct sockaddr_in local; local.sin_family = AF_INET; local.sin_addr.s_addr = inet_addr(_ip); local.sin_port = htons(_port); if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0) //套接字文件与输入IP,port绑定 { perror("bind"); exit(3); } if(listen(sock, 10) < 0) //将服务器段设置为监听状态 { perror("listen"); exit(4); } return sock;}//version3 的线程void* handler(void* arg){ int new_sock = (int)arg; while(1) { char buf[1024]; ssize_t _r = read(new_sock, buf, sizeof(buf)-1); if(_r > 0) { buf[_r] = 0; printf("client#:%s",buf); } else if(_r == 0) { close(new_sock); printf("client is quit!\n"); break; }else { perror("read"); close(new_sock); break; } }}int main(int argc, char* argv[]){ //./tcp_server ip port if(argc != 3) { usage(argv[0]); return 1; } int listen_sock = startup(argv[1], atoi(argv[2])); printf("bind and listen success...\n"); while(1) { struct sockaddr_in client; socklen_t len = sizeof(client); int new_sock = accept(listen_sock, (struct sockaddr *)&client, &len);//创建一个新的将接收的sock分别工作 if(new_sock < 0) { perror("accept"); continue; } printf("get a client, ip:%s, port:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));// version 3 多线程 pthread_t id; pthread_create(&id, NULL, handler, (void *)new_sock); 主线程向下执行,新线程执行handler pthread_detach(id); } return 0;}
server bind失败的原因
当我们先启动server端,后启动client端,然后Ctrl-C使server端终止,这时马上再运行server,节后就会出现:bind error;这时因为虽然server的应用程序终止了,但是TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口;当我们把client也终止掉以后,client会自动关闭自己的socket描述符,server的TCP连接也收到了client发送的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待2MSL的时间后才能回到CLOSED状态,应为我们先终止的是server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍不能再次监听同样的server端口。
其实,在server的TCP连接没有完全断开之前不允许重新监听是不合理的,因为,TCP的连接没有完全断开指的是connfd(127.0.0.1:8080)没有完全断开,而我们重新监听的lesten(0.0.0.0:8080),虽然端口号一样,但是IP不同,connfd对应的食欲某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个sock描述符,在server代码的socket()和bind()调用之间插入
int opt = 1;setsockopt(listenfd, SOL_SOCCKET, SO_REUSEADDR, &opt, sizeof(opt));
线程池
1.线程池基本原理
在传统服务器结构中, 常是 有一个总的 监听线程监听有没有新的用户连接服务器, 每当有一个新的 用户进入, 服务器就开启一个新的线程用户处理这 个用户的数据包。这个线程只服务于这个用户 , 当 用户与服务器端关闭连接以后, 服务器端销毁这个线程。然而频繁地开辟与销毁线程极大地占用了系统的资源。而且在大量用户的情况下, 系统为了开辟和销毁线程将浪费大量的时间和资源。线程池提供了一个解决外部大量用户与服务器有限资源的矛盾, 线程池和传统的一个用户对应一 个线程的处理方法不同, 它的基本思想就是在程序 开始时就在内存中开辟一些线程, 线程的数目是 固定的,他们独自形成一个类, 屏蔽了对外的操作, 而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时 , 不是新创建一个线程为其服务 , 而是从“池子”中选择一个空闲的线程为新的客户请求服务 ,服务完毕后 , 线程进入空闲线程池中。如果没有线程空闲 的 话, 就 将 数 据 包 暂 时 积 累 , 等 待 线 程 池 内 有 线 程空闲以后再进行处理。通过对多个任务重用已经存在的线程对象 , 降低了对线程对象创建和销毁的开销。当客户请求 时 , 线程对象 已 经 存 在 , 可 以 提 高 请 求 的响应时间 , 从而整体地提高了系统服务的表现。
2. 线程池的实现
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <pthread.h>#include <assert.h>/**线程池里所有运行和等待的任务都是一个CThread_worker*由于所有任务都在链表里,所以是一个链表结构*/typedef struct worker{ /*回调函数,任务运行时会调用此函数,注意也可声明成其它形式*/ void *(*process) (void *arg); void *arg;/*回调函数的参数*/ struct worker *next;} CThread_worker;/*线程池结构*/typedef struct{ pthread_mutex_t queue_lock; pthread_cond_t queue_ready; /*链表结构,线程池中所有等待任务*/ CThread_worker *queue_head; /*是否销毁线程池*/ int shutdown; pthread_t *threadid; /*线程池中允许的活动线程数目*/ int max_thread_num; /*当前等待队列的任务数目*/ int cur_queue_size;} CThread_pool;int pool_add_worker (void *(*process) (void *arg), void *arg);void *thread_routine (void *arg);static CThread_pool *pool = NULL;void pool_init (int max_thread_num){ pool = (CThread_pool *) malloc (sizeof (CThread_pool)); pthread_mutex_init (&(pool->queue_lock), NULL); pthread_cond_init (&(pool->queue_ready), NULL); pool->queue_head = NULL; pool->max_thread_num = max_thread_num; pool->cur_queue_size = 0; pool->shutdown = 0; pool->threadid =(pthread_t *) malloc (max_thread_num * sizeof (pthread_t)); int i = 0; for (i = 0; i < max_thread_num; i++) { pthread_create (&(pool->threadid[i]), NULL, thread_routine,NULL); }}/*向线程池中加入任务*/int pool_add_worker (void *(*process) (void *arg), void *arg){ /*构造一个新任务*/ CThread_worker *newworker = (CThread_worker *) malloc (sizeof (CThread_worker)); newworker->process = process; newworker->arg = arg; newworker->next = NULL;/*别忘置空*/ pthread_mutex_lock (&(pool->queue_lock)); /*将任务加入到等待队列中*/ CThread_worker *member = pool->queue_head; if (member != NULL) { while (member->next != NULL) member = member->next; member->next = newworker; } else { pool->queue_head = newworker; } assert (pool->queue_head != NULL); pool->cur_queue_size++; pthread_mutex_unlock (&(pool->queue_lock)); /*好了,等待队列中有任务了,唤醒一个等待线程; 注意如果所有线程都在忙碌,这句没有任何作用*/ pthread_cond_signal (&(pool->queue_ready)); return 0;}/*销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出*/int pool_destroy (){ if (pool->shutdown) return -1;/*防止两次调用*/ pool->shutdown = 1; /*唤醒所有等待线程,线程池要销毁了*/ pthread_cond_broadcast (&(pool->queue_ready)); /*阻塞等待线程退出,否则就成僵尸了*/ int i; for (i = 0; i < pool->max_thread_num; i++) pthread_join (pool->threadid[i], NULL); free (pool->threadid); /*销毁等待队列*/ CThread_worker *head = NULL; while (pool->queue_head != NULL) { head = pool->queue_head; pool->queue_head = pool->queue_head->next; free (head); } /*条件变量和互斥量也别忘了销毁*/ pthread_mutex_destroy(&(pool->queue_lock)); pthread_cond_destroy(&(pool->queue_ready)); free (pool); /*销毁后指针置空是个好习惯*/ pool=NULL; return 0;}void* thread_routine (void *arg){ printf ("starting thread 0x%x\n", pthread_self ()); while (1) { pthread_mutex_lock (&(pool->queue_lock)); /*如果等待队列为0并且不销毁线程池,则处于阻塞状态; 注意 pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/ while (pool->cur_queue_size == 0 && !pool->shutdown) { printf ("thread 0x%x is waiting\n", pthread_self ()); pthread_cond_wait (&(pool->queue_ready), &(pool->queue_lock)); } /*线程池要销毁了*/ if (pool->shutdown) { /*遇到break,continue,return等跳转语句,千万不要忘记先解锁*/ pthread_mutex_unlock (&(pool->queue_lock)); printf ("thread 0x%x will exit\n", pthread_self ()); pthread_exit (NULL); } printf ("thread 0x%x is starting to work\n", pthread_self ()); /*assert是调试的好帮手*/ assert (pool->cur_queue_size != 0); assert (pool->queue_head != NULL); /*等待队列长度减去1,并取出链表中的头元素*/ pool->cur_queue_size--; CThread_worker *worker = pool->queue_head; pool->queue_head = worker->next; pthread_mutex_unlock (&(pool->queue_lock)); /*调用回调函数,执行任务*/ (*(worker->process)) (worker->arg); free (worker); worker = NULL; } /*这一句应该是不可达的*/ pthread_exit (NULL);}void* myprocess (void *arg){ printf ("threadid is 0x%x, working on task %d\n", pthread_self (),*(int *) arg); sleep (1);/*休息一秒,延长任务的执行时间*/ return NULL;}int main (){ pool_init (3);/*线程池中最多三个活动线程*/ /*连续向池中投入10个任务*/ int *workingnum = (int *) malloc (sizeof (int) * 10); int i; for (i = 0; i < 10; i++) { workingnum[i] = i; pool_add_worker (myprocess, &workingnum[i]); } /*等待所有任务完成*/ sleep (5); /*销毁线程池*/ pool_destroy (); free (workingnum); return 0;}
- TCP网络通信/线程池
- Java 网络通信 — 使用线程池搭建TCP BIO通信服务器
- socket,线程池(TCP通信)
- socket网络通信(tcp)
- socket网络通信(tcp)
- TCP网络通信
- tcp 网络通信基本原理
- 网络通信3-TCP
- 网络通信--TCP
- 网络通信 Tcp
- 网络通信 - TCP
- C#:网络通信(TCP)
- 网络编程-Tcp通信
- 网络通信之TCP
- TCP网络通信
- TCP网络通信
- android tcp 网络通信
- 网络编程之线程池通信
- netty5与spring集成,实现配置启动--(一)
- 一个屌丝程序猿的人生(六十九)
- 二元查找树
- [JZOJ5165] 小W的动漫
- 湖南省第八届大学生计算机程序设计竞赛CSU--最短的名字
- TCP网络通信/线程池
- 【转载】SAP ABAP中自定义权限对象(AUTHORITY-CHECK)
- 自适应阈值的ransac平面拟合
- 外网远程桌面连接内网服务器教程(超详细)
- 浅谈C/C++中的typedef和#define
- 【转载】BAPI_GOODSMVT_CREATE FUNCITON FOR MIGO 各种移动类型 源代码参考
- python解析页面DOM树形成xpath列表,并计算DOM树的最大深度
- ==与equals()方法
- VMware虚拟机中如何配置静态IP