1.单进程通信客户端代码
#include<stdio.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<unistd.h>
#include<strings.h>
#include<netinet/in.h>
#include<string.h>
void usage(const char *str)
{
printf("%s [IP][PORT] \n",str);
}
// ./clie 127.0.0.1 8000
int main(int argc, char *argv[])
{
if(argc != 3)
{
usage(argv[0]);
exit(1);
}
struct sockaddr_in serv_addr;
int sockfd;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd < 0)
{
perror("socket");
exit(1);
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argv[2]));
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
int ret = 0;
ret = connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(ret < 0)
{
perror("connect");
exit(2);
}
int n = 0;
char buf[1024] = { 0 };
while(1)
{
printf("client# ");
fflush(stdout);
int n = read(0,buf,sizeof(buf)-1);
buf[n-1] = '\0'; //换行符不读。
write(sockfd,buf,strlen(buf));
n = read(sockfd,buf,sizeof(buf)-1);
buf[n] = 0;
printf("server echo#:%s\n",buf);
}
return 0;
}
2.单进程通信服务端代码
#include<stdio.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<unistd.h>
#include<strings.h>
#include<netinet/in.h>
#include<string.h>
void usage(const char *str)
{
printf("%s [IP][PORT]\n",str);
}
//./serv 127.0.0.1 8000
int main(int argc ,char *argv[])
{
if(argc != 3)
{
usage(argv[0]);
exit(1);
}
struct sockaddr_in serv_addr,clie_addr;
int listenfd,connectfd;
int ret ;
socklen_t clie_addr_len;
listenfd = socket(AF_INET,SOCK_STREAM,0);
if(listenfd < 0)
{
perror("socket");
exit(2);
}
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons( atoi( argv[2] ) );
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
ret = bind(listenfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(ret < 0)
{
perror("bind");
exit(3);
}
ret = listen(listenfd,20);
if(ret < 0)
{
perror("listen");
exit(4);
}
while(1)
{
clie_addr_len = sizeof(clie_addr);
connectfd = accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);
if(connectfd < 0)
{
perror("accept");
exit(5);
}
printf("clie ip %s,clie port %d\n",inet_ntoa(clie_addr.sin_addr),ntohs(clie_addr.sin_port));
int n = 0;
char buf[1024] = { 0 } ;
while(1)
{
n = read(connectfd,buf,sizeof(buf)-1); //最多读sizeof(buf)-1,留下一个空间放\0
if(n ==0 )
{
printf("clie quit!\n");
break;
}
else if(n < 0)
{
exit(6);
}
else
{
buf[n] = 0;
printf("client say:%s\n",buf);
write(connectfd,buf,strlen(buf));
}
}
}
close(listenfd);
close(connectfd);
return 0;
}
3.多进程通信服务端代码
#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<netinet/in.h>
#include<stdlib.h>
static int startup(const char *_ip,int _port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
exit(2);
}
// int on = 1;
// int ret = setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
// if(ret < 0)
{
// perror("setsockopt");
// exit(5);
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(_port);
serv_addr.sin_addr.s_addr = inet_addr(_ip);
if(bind (sock,(struct sockaddr*)& serv_addr,sizeof(serv_addr) ) < 0 )
{
perror("bind");
exit(3);
}
if(listen(sock,10) < 0)
{
perror("listen");
exit(4);
}
return sock;
}
static void usage(const char *str)
{
printf("Usage:%s [IP] [PORT]\n",str);
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));
struct sockaddr_in clie_addr;
socklen_t len = sizeof(clie_addr);
while(1)
{
int sock = accept(listen_sock ,(struct sockaddr*)&clie_addr,&len );
if(sock > 0)
{
pid_t id = fork();
if(id == 0) //child
{
if(fork() > 0)//子进程退出,让孙子进程办事,孙子进程成为孤儿进程,被1号进程回收。
exit(0);
char buf[BUFSIZ];
while(1)
{
ssize_t s = read(sock,buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("client say#:%s\n",buf);
write(sock,buf,strlen(buf));
}
else if(s == 0)
{
printf("client quit!\n");
break;
}
else
break;
}
}
else//father
{
close(sock);
//if(fork() > 0)
// exit(0);
}
}
else
{
perror("accept");
}
}
return 0;
}
4.多线程通信服务端代码
#include<stdio.h>
#include<pthread.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<netinet/in.h>
#include<stdlib.h>
static void usage(const char *str)
{
printf("Usage:%s [IP][PORT]\n",str);
}
static int startup(const char *ip,int port)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
exit(1);
}
int op = 1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&op,sizeof(op)); //端口复用 为啥不加这句话端口也是可以复用的? 证明还是要这句话的。
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0)
{
perror("bind");
exit(3);
}
if(listen(sock,20) < 0)
{
perror("listen");
exit(2);
}
return sock;
}
void* new_pthread(void *arg)
{
int sock = (int )arg;
char buf[BUFSIZ];
// const char* msg = "HTTP/1.1 200 OK\r\n\r\n<html><h1> huang jia </h1></html>\r\n";
while(1)
{
ssize_t s = read(sock,buf,sizeof(buf)-1);
if(s > 0)
{
buf[s] = 0;
printf("client say:%s\n",buf);
write(sock,buf,strlen(buf));
// write(sock,msg,strlen(msg));
}
else if(s == 0)
{
printf("client quit !\n");
break;
}
else
{
break;
}
}
pthread_exit(NULL);
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
usage(argv[0]);
return 1;
}
int listen_sock = startup(argv[1],atoi(argv[2]));
int cfd;
struct sockaddr_in clie_addr;
while(1)
{
socklen_t len = sizeof(clie_addr);
cfd = accept(listen_sock, (struct sockaddr*)&clie_addr,&len);
if(cfd < 0)
{
perror("accept");
return 2;
}
printf("client ip:%s,port:%d\n",inet_ntoa(clie_addr.sin_addr),ntohs(clie_addr.sin_port) );
pthread_t tid;
pthread_create(&tid,NULL,new_pthread,(void*)cfd);
pthread_detach(tid);
}
return 0;
}
5.进程池和线程池
进程池是由服务器预先创建的一组子进程,这些子进程的数目在 3~10 个之间(当然这只是典型情况)。线程池中的线程数量应该和 CPU 数量差不多。
进程池中的所有子进程都运行着相同的代码,并具有相同的属性,比如优先级、 PGID 等。
当有新的任务来到时,主进程将通过某种方式选择进程池中的某一个子进程来为之服务。相比于动态创建子进程,选择一个已经存在的子进程的代价显得小得多。至于主进程选择哪个子进程来为新任务服务,则有两种方法:
1)主进程使用某种算法来主动选择子进程。最简单、最常用的算法是随机算法和 Round Robin (轮流算法)。
2)主进程和所有子进程通过一个共享的工作队列来同步,子进程都睡眠在该工作队列上。当有新的任务到来时,主进程将任务添加到工作队列中。这将唤醒正在等待任务的子进程,不过只有一个子进程将获得新任务的“接管权”,它可以从工作队列中取出任务并执行之,而其他子进程将继续睡眠在工作队列上。
当选择好子进程后,主进程还需要使用某种通知机制来告诉目标子进程有新任务需要处理,并传递必要的数据。最简单的方式是,在父进程和子进程之间预先建立好一条管道,然后通过管道来实现所有的进程间通信。在父线程和子线程之间传递数据就要简单得多,因为我们可以把这些数据定义为全局,那么它们本身就是被所有线程共享的。
线程池主要用于:
1)需要大量的线程来完成任务,且完成任务的时间比较短。 比如WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大。但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
2)对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
3)接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。
6.C语言实现简单线程池
①头文件
#ifndef THREAD_POOL_H__
#define THREAD_POOL_H__
#include <pthread.h>
/* 要执行的任务链表 */
typedef struct tpool_work {
void* (*routine)(void*); /* 任务函数 */
void *arg; /* 传入任务函数的参数 */
struct tpool_work *next;
}tpool_work_t;
typedef struct tpool {
int shutdown; /* 线程池是否销毁 */
int max_thr_num; /* 最大线程数 */
pthread_t *thr_id; /* 线程ID数组 */
tpool_work_t *queue_head; /* 线程链表 */
pthread_mutex_t queue_lock;
pthread_cond_t queue_ready;
}tpool_t;
/*
* @brief 创建线程池
* @param max_thr_num 最大线程数
* @return 0: 成功 其他: 失败
*/
int
tpool_create(int max_thr_num);
/*
* @brief 销毁线程池
*/
void
tpool_destroy();
/*
* @brief 向线程池中添加任务
* @param routine 任务函数指针
* @param arg 任务函数参数
* @return 0: 成功 其他:失败
*/
int
tpool_add_work(void*(*routine)(void*), void *arg);
#endif
②源文件
1: #include <unistd.h>
2: #include <stdlib.h>
3: #include <errno.h>
4: #include <string.h>
5: #include <stdio.h>
6:
7: #include "tpool.h"
8:
9: static tpool_t *tpool = NULL;
10:
11: /* 工作者线程函数, 从任务链表中取出任务并执行 */
12: static void*
13: thread_routine(void *arg)
14: {
15: tpool_work_t *work;
16:
17: while(1) {
18: /* 如果线程池没有被销毁且没有任务要执行,则等待 */
19: pthread_mutex_lock(&tpool->queue_lock);
20: while(!tpool->queue_head && !tpool->shutdown) {
21: pthread_cond_wait(&tpool->queue_ready, &tpool->queue_lock);
22: }
23: if (tpool->shutdown) {
24: pthread_mutex_unlock(&tpool->queue_lock);
25: pthread_exit(NULL);
26: }
27: work = tpool->queue_head;
28: tpool->queue_head = tpool->queue_head->next;
29: pthread_mutex_unlock(&tpool->queue_lock);
30:
31: work->routine(work->arg);
32: free(work);
33: }
34:
35: return NULL;
36: }
37:
38: /*
39: * 创建线程池
40: */
41: int
42: tpool_create(int max_thr_num)
43: {
44: int i;
45:
46: tpool = calloc(1, sizeof(tpool_t));
47: if (!tpool) {
48: printf("%s: calloc failed\n", __FUNCTION__);
49: exit(1);
50: }
51:
52: /* 初始化 */
53: tpool->max_thr_num = max_thr_num;
54: tpool->shutdown = 0;
55: tpool->queue_head = NULL;
56: if (pthread_mutex_init(&tpool->queue_lock, NULL) !=0) {
57: printf("%s: pthread_mutex_init failed, errno:%d, error:%s\n",
58: __FUNCTION__, errno, strerror(errno));
59: exit(1);
60: }
61: if (pthread_cond_init(&tpool->queue_ready, NULL) !=0 ) {
62: printf("%s: pthread_cond_init failed, errno:%d, error:%s\n",
63: __FUNCTION__, errno, strerror(errno));
64: exit(1);
65: }
66:
67: /* 创建工作者线程 */
68: tpool->thr_id = calloc(max_thr_num, sizeof(pthread_t));
69: if (!tpool->thr_id) {
70: printf("%s: calloc failed\n", __FUNCTION__);
71: exit(1);
72: }
73: for (i = 0; i < max_thr_num; ++i) {
74: if (pthread_create(&tpool->thr_id[i], NULL, thread_routine, NULL) != 0){
75: printf("%s:pthread_create failed, errno:%d, error:%s\n", __FUNCTION__,
76: errno, strerror(errno));
77: exit(1);
78: }
79:
80: }
81:
82: return 0;
83: }
84:
85: /* 销毁线程池 */
86: void
87: tpool_destroy()
88: {
89: int i;
90: tpool_work_t *member;
91:
92: if (tpool->shutdown) {
93: return;
94: }
95: tpool->shutdown = 1;
96:
97: /* 通知所有正在等待的线程 */
98: pthread_mutex_lock(&tpool->queue_lock);
99: pthread_cond_broadcast(&tpool->queue_ready);
100: pthread_mutex_unlock(&tpool->queue_lock);
101: for (i = 0; i < tpool->max_thr_num; ++i) {
102: pthread_join(tpool->thr_id[i], NULL);
103: }
104: free(tpool->thr_id);
105:
106: while(tpool->queue_head) {
107: member = tpool->queue_head;
108: tpool->queue_head = tpool->queue_head->next;
109: free(member);
110: }
111:
112: pthread_mutex_destroy(&tpool->queue_lock);
113: pthread_cond_destroy(&tpool->queue_ready);
114:
115: free(tpool);
116: }
117:
118: /* 向线程池添加任务 */
119: int
120: tpool_add_work(void*(*routine)(void*), void *arg)
121: {
122: tpool_work_t *work, *member;
123:
124: if (!routine){
125: printf("%s:Invalid argument\n", __FUNCTION__);
126: return -1;
127: }
128:
129: work = malloc(sizeof(tpool_work_t));
130: if (!work) {
131: printf("%s:malloc failed\n", __FUNCTION__);
132: return -1;
133: }
134: work->routine = routine;
135: work->arg = arg;
136: work->next = NULL;
137:
138: pthread_mutex_lock(&tpool->queue_lock);
139: member = tpool->queue_head;
140: if (!member) {
141: tpool->queue_head = work;
142: } else {
143: while(member->next) {
144: member = member->next;
145: }
146: member->next = work;
147: }
148: /* 通知工作者线程,有新任务添加 */
149: pthread_cond_signal(&tpool->queue_ready);
150: pthread_mutex_unlock(&tpool->queue_lock);
151:
152: return 0;
153: }
③测试代码
1: #include <unistd.h>
2: #include <stdio.h>
3: #include <stdlib.h>
4: #include "tpool.h"
5:
6: void *func(void *arg)
7: {
8: printf("thread %d\n", (int)arg);
9: return NULL;
10: }
11:
12: int
13: main(int arg, char **argv)
14: {
15: if (tpool_create(5) != 0) {
16: printf("tpool_create failed\n");
17: exit(1);
18: }
19:
20: int i;
21: for (i = 0; i < 10; ++i) {
22: tpool_add_work(func, (void*)i);
23: }
24: sleep(2);
25: tpool_destroy();
26: return 0;
27: }
7.当服务端主动关闭后,使用过的socketbind失败的原因
虽然进程server已经终止,但是这条client到server的连接并没有终止,因为还没有完成TCP的最后“四次挥手”,连接进入了TIME_WAIT状态 造成这个错误的原因是之前的连接还并没有消失(处于TIME_WAIT),而server1又试图bind一个现有连接(处于TIME_WAIT的连接)上的端口(9999),所以bind失败。Server2当然也就不能连接成功了
const int on=1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
SO_REUSEADDR选项的用途有多中,我们只讨论这里使用到的功能。先来看看UNP V1对这种情况的描述。
SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将该端口用作它们的本地的连接仍存在。这个条件通常是这样碰到的: (1) 启动一个监听服务器; (2) 连接请求到达,派生一个子进程来处理这个客户; (3)
监听服务器终止,但子进程继续为现有连接上的客户提供服务; (4) 重启监听服务器。
默认情况下,当监听服务器在步骤(4)中通过调用socket、bind和listen重新启动时,由于它试图捆绑一个现有连接(即正由早先派生的那个子进程处理着的连接)上的端口,从而bind调用会失败。但如果该服务器在socket和bind中间调用设置了SO_REUSEADDR选项,那么bind将成功。下面对比我们这里遇到的情况,server主动关闭后进入TIME_WAIT状态,此时对server来说原有连接没有彻底终止,当重启server时,就试图bind一个现有的连接,所以造成bind失败。所以一般TCP服务端都要设置SO_REUSEADDR选项,以便可以快速重启。