一、服務(wù)器模型
1.1 服務(wù)器概念
服務(wù)器模型主要分為兩種,循環(huán)服務(wù)器和并發(fā)服務(wù)器。
循環(huán)服務(wù)器:
在同一時(shí)間只能處理一個(gè)客戶端的請(qǐng)求。
并發(fā)服務(wù)器:
在同一時(shí)間內(nèi)能同時(shí)處理多個(gè)客戶端的請(qǐng)求。
TCP的服務(wù)器默認(rèn)的就是一個(gè)循環(huán)服務(wù)器,原因是有兩個(gè)阻塞 accept函數(shù) 和recv函數(shù) 之間會(huì)相互影響。
UDP的服務(wù)器默認(rèn)的就是一個(gè)并發(fā)服務(wù)器,因?yàn)橹挥幸粋€(gè)阻塞的 recvfrom函數(shù)。
1.2 TCP并發(fā)服務(wù)器的意義
在有些應(yīng)用場(chǎng)景下,我們既要保證數(shù)據(jù)可靠,又要支持并發(fā)
這就需要用到TCP并發(fā)服務(wù)器。
1.3 實(shí)現(xiàn)TCP并發(fā)服務(wù)器的方式
- 使用多路IO復(fù)用實(shí)現(xiàn)TCP并發(fā)服務(wù)器(常用)
- 使用多進(jìn)程實(shí)現(xiàn)TCP并發(fā)服務(wù)器
- 使用多線程實(shí)現(xiàn)TCP并發(fā)服務(wù)器
本次我們學(xué)習(xí)第一個(gè)方式,使用多路IO復(fù)用(select函數(shù))實(shí)現(xiàn)TCP并發(fā)服務(wù)器的實(shí)現(xiàn),在后續(xù)博客中我們會(huì)依次講解其他實(shí)現(xiàn)方式。
感興趣可以收藏加關(guān)注哦。
二、使用IO多路復(fù)用實(shí)現(xiàn)TCP并發(fā)服務(wù)器優(yōu)勢(shì)
對(duì)于實(shí)際開發(fā)過程中:
如果使用多進(jìn)程實(shí)現(xiàn)TCP并發(fā)服務(wù)器,并發(fā)量大的時(shí)候,對(duì)系統(tǒng)的資源占用量也會(huì)很大。
如果使用多線程,業(yè)務(wù)邏輯復(fù)雜的時(shí)候,又涉及到臨近資源訪問的問題
比較好的方式是使用多路IO復(fù)用實(shí)現(xiàn)TCP并發(fā)服務(wù)器。
三、select函數(shù)
功能:
實(shí)現(xiàn)IO多路復(fù)用
頭文件:
#include <sys/select.h>
函數(shù)原型:
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
參數(shù)
@nfds:監(jiān)視的最大文件描述符+1
@readfds:要監(jiān)視的讀文件描述符集合,如果不關(guān)心,可以傳NULL
@writefds:要監(jiān)視的寫文件描述符集合,如果不關(guān)心,可以傳NULL
@exceptfds:要監(jiān)視的異常的文件描述符集合,如果不關(guān)心,可以傳NULL
(一般我們只關(guān)心readfds)
@timeout:超時(shí)時(shí)間
為0時(shí)非阻塞
為NULL時(shí)永久阻塞
為結(jié)構(gòu)體時(shí)阻塞一定時(shí)間
返回值:
成功 返回就緒文件描述符的個(gè)數(shù)
失敗 返回-1,置位錯(cuò)誤碼
超時(shí) 返回0
void FD_CLR(int fd, fd_set *set);
功能:
刪除集合中的文件描述符
參數(shù):
@fd:文件描述符
@set:構(gòu)建要監(jiān)視的文件描述符集合
int FD_ISSET(int fd, fd_set *set);
功能:
判斷文件描述符是否在集合中
參數(shù):
@fd:文件描述符
@set:構(gòu)建要監(jiān)視的文件描述符集合
返回值:
為0時(shí)不在里面
非0時(shí)在里面
void FD_SET(int fd, fd_set *set);
功能:
將文件描述符添加到集合中
參數(shù):
@fd:文件描述符
@set:構(gòu)建要監(jiān)視的文件描述符集合
void FD_ZERO(fd_set *set);
功能:
清空集合
參數(shù):
@set:構(gòu)建要監(jiān)視的文件描述符集合
注意:
- select只能監(jiān)視小于 FD_SETSIZE(1024) 的文件描述符。
- select函數(shù)在返回時(shí)會(huì)將沒有就緒的文件描述符在表中擦除,
所以,在循環(huán)中調(diào)用select時(shí),每次需要重新填充集合。
四、TCP并發(fā)服務(wù)器的構(gòu)建
4.1 創(chuàng)建套接字
使用socket函數(shù)創(chuàng)建IPV4、TCP套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
4.2 填寫服務(wù)器網(wǎng)絡(luò)信息結(jié)構(gòu)體
struct sockaddr_in serviceaddr;
memset(&serviceaddr, 0, sizeof(serviceaddr));
serviceaddr.sin_family = AF_INET;
serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
serviceaddr.sin_port = htons(atoi(argv[2]));
socklen_t serviceaddr_len = sizeof(serviceaddr);
4.3 將服務(wù)器網(wǎng)絡(luò)信息結(jié)構(gòu)體與套接字綁定
if (bind(sockfd, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
ERRLOG("bind error");
}
4.4 將套接字設(shè)置為被動(dòng)監(jiān)聽狀態(tài)
if (listen(sockfd, 5) == -1)
{
ERRLOG("listen error");
}
4.5 創(chuàng)建文件描述符集合母本和子本并進(jìn)行清空操作
fd_set readfds;
FD_ZERO(&readfds);
fd_set readfds_msg;
FD_ZERO(&readfds_msg);
4.6 將sockfd添加進(jìn)入集合內(nèi),并更新最大文件描述符
FD_SET(sockfd, &readfds);
max_fd = max_fd > sockfd ? max_fd : sockfd;
4.7 循環(huán)實(shí)現(xiàn)內(nèi)部功能偽代碼
while(1){
select();
//遍歷文件描述符集合
for(){
if(sockfd就緒了){
//說明有新的客戶端建立連接了
acceptfd = accept();
將acceptfd加入到readfds中
更新最大文件描述符
}else{
//說明有客戶端發(fā)來數(shù)據(jù)了
recv();
//如果recv返回0了 需要將當(dāng)前的客戶端的acceptfd在
//readfds中刪除,后續(xù)就不再監(jiān)視它了
strcat();
send();
}
}
}
五、客戶端的構(gòu)建
5.1步驟一和二和4.1,4.2一樣
5.2 嘗試與服務(wù)器建立連接
if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
ERRLOG("connect error");
}
5.3 內(nèi)部功能實(shí)現(xiàn)偽代碼
while(1){
//從終端獲取數(shù)據(jù)寫入buff中
fgets();
buff[strlen(buff)-1] = '\0';//清理結(jié)尾的\n
//發(fā)送數(shù)據(jù)
if(-1 == send(sockfd, buff, sizeof(buff), 0)){
ERRLOG("send error");
}
//接收服務(wù)器的應(yīng)答信息
if(-1 == (nbytes = recv(sockfd, buff, sizeof(buff), 0))){
ERRLOG("recv error");
}
}
六、測(cè)試結(jié)果
使用三個(gè)客戶端連接一個(gè)TCP并發(fā)服務(wù)器。文章來源:http://www.zghlxwxcb.cn/news/detail-435543.html
測(cè)試TCP并發(fā)服務(wù)器功能實(shí)現(xiàn)
測(cè)試quit退出功能實(shí)現(xiàn)
測(cè)試ctrl+c終止程序斷開來連接實(shí)現(xiàn)
測(cè)試退出文件,清除客戶端的文件描述符,在新的客戶端連接時(shí),從最小的文件描述符開始實(shí)現(xiàn)。
成功實(shí)現(xiàn)IO多路復(fù)用TCP并發(fā)服務(wù)器和客戶端。文章來源地址http://www.zghlxwxcb.cn/news/detail-435543.html
七、TCP并發(fā)服務(wù)器源代碼
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>
#define ERRLOG(msg) \
do \
{ \
printf("%s %s %d:", __FILE__, __func__, __LINE__); \
perror(msg); \
exit(-1); \
} while (0)
#define N 128
int main(int argc, const char *argv[])
{
//檢查入?yún)⒑侠硇?/span>
if (argc != 3)
{
printf("Usage : %s <IP> <PORT>\n", argv[0]);
return -1;
}
//創(chuàng)建套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
ERRLOG("socket error");
}
//填寫服務(wù)器網(wǎng)絡(luò)信息結(jié)構(gòu)體
struct sockaddr_in serviceaddr;
memset(&serviceaddr, 0, sizeof(serviceaddr));
serviceaddr.sin_family = AF_INET;
serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
serviceaddr.sin_port = htons(atoi(argv[2]));
socklen_t serviceaddr_len = sizeof(serviceaddr);
//將服務(wù)器網(wǎng)絡(luò)信息結(jié)構(gòu)體與套接字綁定
if (bind(sockfd, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
ERRLOG("bind error");
}
//將套接字設(shè)置為被動(dòng)監(jiān)聽狀態(tài)
if (listen(sockfd, 5) == -1)
{
ERRLOG("listen error");
}
char buf[N] = {0};
int max_fd;
int i;
int ret;
int acceptfd;
int nbytes;
//創(chuàng)建文件描述符集合母本和子本并進(jìn)行清空操作
fd_set readfds;
FD_ZERO(&readfds);
fd_set readfds_msg;
FD_ZERO(&readfds_msg);
//將sockfd添加進(jìn)入集合內(nèi),并跟新最大文件描述符
FD_SET(sockfd, &readfds);
max_fd = max_fd > sockfd ? max_fd : sockfd;
while (1)
{
//在每次循環(huán)前將子本重新賦值,因?yàn)閟elect會(huì)將沒有就緒的文件描述符在集合內(nèi)擦除
readfds_msg = readfds;
if ((ret = select(max_fd + 1, &readfds_msg, NULL, NULL, NULL)) == -1)
{
ERRLOG("select error");
}
else //說明有文件描述符就緒了
{
//遍歷文件描述符
for (i = 3; i < max_fd + 1 && ret != 0; i++)
{
//判斷是哪個(gè)文件描述符就緒了
if (FD_ISSET(i, &readfds_msg))
{
ret--;
if (i == sockfd) //如果套接字就緒了則等待客戶端連接
{
if ((acceptfd = accept(sockfd, NULL, NULL)) == -1)
{
ERRLOG("accept error");
}
printf("客戶端[%d]連接到服務(wù)器..\n", acceptfd);
//如果有客戶端連接將產(chǎn)生的新的文件描述符添加到集合中,并更新最大文件描述符
FD_SET(acceptfd, &readfds);
max_fd = max_fd > acceptfd ? max_fd : acceptfd;
}
else //否則就是客戶端發(fā)來消息了
{
memset(buf, 0, N);
if ((nbytes = recv(i, buf, N, 0)) == -1)
{
ERRLOG("recv error");
}
else if (nbytes == 0)
{
printf("客戶端[%d]已斷開連接..\n", i);
close(i); //關(guān)閉當(dāng)前客戶端的文件描述符
FD_CLR(i, &readfds); //將該客戶端的文件描述符在集合中刪除
continue;
}
if (strcmp(buf, "quit") == 0)
{
printf("客戶端[%d]已退出服務(wù)器..\n", i);
close(i);
FD_CLR(i, &readfds);
continue;
}
printf("客戶端[%d]發(fā)來消息[%s]..\n", i, buf);
strcat(buf, "--夜貓徐"); //組裝應(yīng)答
if (send(i, buf, N, 0) == -1) //發(fā)送給客戶端
{
ERRLOG("send error");
}
}
}
}
}
}
close(sockfd);
return 0;
}
八、客戶端源代碼
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERRLOG(msg) do{\
printf("%s %s %d:", __FILE__, __func__, __LINE__);\
perror(msg);\
exit(-1);\
}while(0)
#define N 128
int main(int argc, const char *argv[]){
//入?yún)⒑侠硇詸z查
if(3 != argc){
printf("Usage : %s <IP> <PORT>\n", argv[0]);
return -1;
}
//1.創(chuàng)建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd){
ERRLOG("socket error");
}
//2.填充服務(wù)器網(wǎng)絡(luò)信息結(jié)構(gòu)體
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
char buff[128] = {0};
int nbytes = 0;
//3.嘗試與服務(wù)器建立連接
if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
ERRLOG("connect error");
}
printf("與服務(wù)器建立連接成功..\n");
while(1){
memset(buff, 0, sizeof(buff));
fgets(buff, N, stdin);
buff[strlen(buff)-1] = '\0';//清理結(jié)尾的\n
//發(fā)送數(shù)據(jù)
if(-1 == send(sockfd, buff, sizeof(buff), 0)){
ERRLOG("send error");
}
//接收服務(wù)器的應(yīng)答信息
if(-1 == (nbytes = recv(sockfd, buff, sizeof(buff), 0))){
ERRLOG("recv error");
}
if(0 == nbytes){
break;
}
//輸出應(yīng)答信息
printf("應(yīng)答為:[%s]\n", buff);
}
//關(guān)閉套接字
close(sockfd);
return 0;
}
到了這里,關(guān)于【Linux網(wǎng)絡(luò)編程】TCP并發(fā)服務(wù)器的實(shí)現(xiàn)(IO多路復(fù)用select)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!