前面介紹了關於連接linux服務端方式,但是服務端的資源是有限的,所以我們通常需要重新思考,設計一套服務器模型來處理對應的客戶端的請求。
第一種:並發服務器,通過主進程統一處理客戶端的連接,當客戶端連接過後,臨時fork()進程,由子進程處理客戶端請求,將連接請求和業務進行了分離。
server.c
#include
#include
#include
#include
#include
#include
#include
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
static void handle_request(int s_c)
{
time_t now; /*時間*/
char buff[BUFFLEN]; /*收發數據緩沖區*/
int n = 0;
memset(buff, 0, BUFFLEN); /*清零*/
n = recv(s_c, buff, BUFFLEN,0); /*接收發送方數據*/
if(n > 0 && !strncmp(buff, "TIME", 4)) /*判斷是否合法接收數據*/
{
memset(buff, 0, BUFFLEN); /*清零*/
now = time(NULL); /*當前時間*/
sprintf(buff, "%24s\r\n",ctime(&now)); /*將時間復制入緩沖區*/
send(s_c, buff, strlen(buff),0); /*發送數據*/
}
/*關閉客戶端*/
close(s_c);
}
static int handle_connect(int s_s)
{
int s_c; /*客戶端套接字文件描述符*/
struct sockaddr_in from; /*客戶端地址*/
socklen_t len = sizeof(from);
/*主處理過程*/
while(1)
{
/*接收客戶端連接*/
s_c = accept(s_s, (struct sockaddr*)&from, &len);
if(s_c > 0) /*客戶端成功連接*/
{
/*創建進程進行數據處理*/
if(fork() > 0){ /*父進程*/
close(s_c); /*關閉父進程的客戶端連接套接字*/
}else{
handle_request(s_c); /*處理連接請求*/
return(0);
}
}
}
}
int main(int argc, char *argv[])
{
int s_s; /*服務器套接字文件描述符*/
struct sockaddr_in local; /*本地地址*/
/*建立TCP套接字*/
s_s = socket(AF_INET, SOCK_STREAM, 0);
/*初始化地址*/
memset(&local, 0, sizeof(local)); /*清零*/
local.sin_family = AF_INET; /*AF_INET協議族*/
local.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/
local.sin_port = htons(SERVER_PORT); /*服務器端口*/
/*將套接字文件描述符綁定到本地地址和端口*/
bind(s_s, (struct sockaddr*)&local, sizeof(local));
listen(s_s, BACKLOG); /*偵聽*/
/*處理客戶端連接*/
handle_connect(s_s);
close(s_s);
return 0;
}
代碼比較詳細,容易理解。
下面介紹客戶端代碼,後面的客戶端代碼都是一樣的。
client.c
#include
#include
#include
#include
#include
#include
#define BUFFLEN 1024
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
int s; /*服務器套接字文件描述符*/
struct sockaddr_in server; /*本地地址*/
char buff[BUFFLEN]; /*收發數據緩沖區*/
int n = 0; /*接收字符串長度*/
/*建立TCP套接字*/
s = socket(AF_INET, SOCK_STREAM, 0);
/*初始化地址*/
memset(&server, 0, sizeof(server)); /*清零*/
server.sin_family = AF_INET; /*AF_INET協議族*/
server.sin_addr.s_addr = htonl(INADDR_ANY);/*任意本地地址*/
server.sin_port = htons(SERVER_PORT); /*服務器端口*/
/*連接服務器*/
connect(s, (struct sockaddr*)&server,sizeof(server));
memset(buff, 0, BUFFLEN); /*清零*/
strcpy(buff, "TIME"); /*復制發送字符串*/
/*發送數據*/
send(s, buff, strlen(buff), 0);
memset(buff, 0, BUFFLEN); /*清零*/
/*接收數據*/
n = recv(s, buff, BUFFLEN, 0);
/*打印消息*/
if(n >0){
printf("TIME:%s",buff);
}
close(s);
return 0;
}
第二種模型:通過線程來處理,線程比進程占用資源少,效率高,數據共享。通過pthread_create()建立一個連接請求處理,線程處理函數為handle_request().
server.c
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
static void handle_request(void *argv)
{
int s_c = *((int*)argv);
time_t now; /*時間*/
char buff[BUFFLEN]; /*收發數據緩沖區*/
int n = 0;
memset(buff, 0, BUFFLEN); /*清零*/
n = recv(s_c, buff, BUFFLEN,0); /*接收發送方數據*/
if(n > 0 && !strncmp(buff, "TIME", 4)) /*判斷是否合法接收數據*/
{
memset(buff, 0, BUFFLEN); /*清零*/
now = time(NULL); /*當前時間*/
sprintf(buff, "%24s\r\n",ctime(&now)); /*將時間復制入緩沖區*/
send(s_c, buff, strlen(buff),0); /*發送數據*/
}
/*關閉客戶端*/
close(s_c);
}
static void handle_connect(int s_s)
{
int s_c; /*客戶端套接字文件描述符*/
struct sockaddr_in from; /*客戶端地址*/
socklen_t len = sizeof(from);
pthread_t thread_do;
/*主處理過程*/
while(1)
{
/*接收客戶端連接*/
s_c = accept(s_s, (struct sockaddr*)&from, &len);
if(s_c > 0) /*客戶端成功連接*/
{
/*創建線程處理連接*/
pthread_create(&thread_do,
NULL,
(void*)handle_request,
&s_c);
}
}
}
int main(int argc, char *argv[])
{
int s_s; /*服務器套接字文件描述符*/
struct sockaddr_in local; /*本地地址*/
/*建立TCP套接字*/
s_s = socket(AF_INET, SOCK_STREAM, 0);
/*初始化地址和端口*/
memset(&local, 0, sizeof(local)); /*清零*/
local.sin_family = AF_INET; /*AF_INET協議族*/
local.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/
local.sin_port = htons(SERVER_PORT); /*服務器端口*/
/*將套接字文件描述符綁定到本地地址和端口*/
bind(s_s, (struct sockaddr*)&local, sizeof(local));
listen(s_s, BACKLOG); /*偵聽*/
/*處理客戶端連接*/
handle_connect(s_s);
close(s_s);
return 0;
}
第三種:服務端各線程獨自accept(),使用互斥鎖,使用pthread_create()建立多個線程組成的線程池,主線程等待程序結束,各個線程獨自接收客戶端accept,以及後面數據處理。
server.c
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 2
/*互斥量*/
pthread_mutex_t ALOCK = PTHREAD_MUTEX_INITIALIZER;
static void *handle_request(void *argv)
{
int s_s = *((int*)argv);
int s_c; /*客戶端套接字文件描述符*/
struct sockaddr_in from; /*客戶端地址*/
socklen_t len = sizeof(from);
for(;;)
{
time_t now; /*時間*/
char buff[BUFFLEN]; /*收發數據緩沖區*/
int n = 0;
pthread_mutex_lock(&ALOCK); /*進入互斥區*/
s_c = accept(s_s, (struct sockaddr*)&from, &len);
/*接收客戶端的請求*/
pthread_mutex_unlock(&ALOCK); /*離開互斥區*/
memset(buff, 0, BUFFLEN); /*清零*/
n = recv(s_c, buff, BUFFLEN,0); /*接收發送方數據*/
if(n > 0 && !strncmp(buff, "TIME", 4)) /*判斷是否合法接收數據*/
{
memset(buff, 0, BUFFLEN); /*清零*/
now = time(NULL); /*當前時間*/
sprintf(buff, "%24s\r\n",ctime(&now)); /*將時間復制入緩沖區*/
send(s_c, buff, strlen(buff),0); /*發送數據*/
}
/*關閉客戶端*/
close(s_c);
}
return NULL;
}
static void handle_connect(int s)
{
int s_s = s;
pthread_t thread_do[CLIENTNUM]; /*線程ID*/
int i = 0;
for(i=0;i
第四種:IO復用服務器,並發服務器客戶端越多,對服務器造成的壓力越大,所以還有第四種模型,IO復用函數用select來做。
server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 1024 /*最大支持客戶端數量*/
/*可連接客戶端的文件描述符數組*/
int connect_host[CLIENTNUM];
int connect_number = 0;
static void *handle_request(void *argv)
{
time_t now; /*時間*/
char buff[BUFFLEN]; /*收發數據緩沖區*/
int n = 0;
int maxfd = -1; /*最大偵聽文件描述符*/
fd_set scanfd; /*偵聽描述符集合*/
struct timeval timeout; /*超時*/
timeout.tv_sec = 1; /* 阻塞1s後超時返回 */
timeout.tv_usec = 0;
int i = 0;
int err = -1;
for(;;)
{
/*最大文件描述符值初始化為-1*/
maxfd = -1;
FD_ZERO(&scanfd); /*清零文件描述符集合*/
for(i=0;i 0 && !strncmp(buff, "TIME", 4))
/*判斷是否合法接收數據*/
{
memset(buff, 0, BUFFLEN); /*清零*/
now = time(NULL); /*當前時間*/
sprintf(buff, "%24s\r\n",ctime(&now));
/*將時間復制入緩沖區*/
send(connect_host[i], buff, strlen(buff),0);
/*發送數據*/
}
/*更新文件描述符在數組中的值*/
connect_host[i] = -1;
connect_number --; /*客戶端計數器減1*/
/*關閉客戶端*/
close(connect_host[i]);
}
}
break;
}
}
return NULL;
}
static void *handle_connect(void *argv)
{
int s_s = *((int*)argv) ; /*獲得服務器偵聽套接字文件描述符*/
struct sockaddr_in from;
socklen_t len = sizeof(from);
/*接收客戶端連接*/
for(;;)
{
int i = 0;
int s_c = accept(s_s, (struct sockaddr*)&from, &len);
/*接收客戶端的請求*/
printf("a client connect, from:%s\n",inet_ntoa(from.sin_addr));
/*查找合適位置,將客戶端的文件描述符放入*/
for(i=0;i
選擇合適的服務器模型十分重要,對於編程有很大的影響。