linux網(wang)絡(luo)編程中的(de)并(bing)發控制(zhi)
時間:2018-09-25 來源(yuan):未知(zhi)
在Linux網絡編程(cheng)中,一(yi)般建(jian)立在兩端(duan)(duan)(duan)之間,服(fu)(fu)務器(qi)端(duan)(duan)(duan)和客(ke)(ke)戶(hu)(hu)端(duan)(duan)(duan)。客(ke)(ke)戶(hu)(hu)端(duan)(duan)(duan)是面向(xiang)用戶(hu)(hu)的應用,而服(fu)(fu)務器(qi)端(duan)(duan)(duan)要(yao)處理(li)客(ke)(ke)戶(hu)(hu)端(duan)(duan)(duan)所(suo)提出的請(qing)求。通常一(yi)個服(fu)(fu)務器(qi)要(yao)面向(xiang)多個客(ke)(ke)戶(hu)(hu)端(duan)(duan)(duan),保證對每個客(ke)(ke)戶(hu)(hu)端(duan)(duan)(duan)都(dou)能(neng)高效的處理(li),這(zhe)(zhe)時候需要(yao)并(bing)發操作。實(shi)現并(bing)發控制的方(fang)(fang)法有兩個,一(yi)個是并(bing)發服(fu)(fu)務器(qi),另一(yi)個是多路復用I/O,現在就(jiu)給大家(jia)介紹一(yi)下這(zhe)(zhe)兩種方(fang)(fang)法。
方法一:并發服務器
這個方(fang)法(fa)可以通過(guo)進(jin)程(cheng)(cheng)(線程(cheng)(cheng))來(lai)實現,主要根據子(zi)進(jin)程(cheng)(cheng)(子(zi)線程(cheng)(cheng))之間并行運行的(de)(de)特點。將對客(ke)戶(hu)(hu)端(duan)請求的(de)(de)處(chu)理(li)(li)工(gong)作,交(jiao)于子(zi)進(jin)程(cheng)(cheng)(子(zi)線程(cheng)(cheng))來(lai)處(chu)理(li)(li),達到一個服務器(qi)(qi)同時處(chu)理(li)(li)多個客(ke)戶(hu)(hu)端(duan)的(de)(de)效果。通過(guo)2個例子(zi)實現一個簡單的(de)(de)服務器(qi)(qi)與客(ke)戶(hu)(hu)端(duan)的(de)(de)一對多。
例1:進程實現(xian)并(bing)發服務(wu)器(TCP通(tong)信)
首(shou)先,服務器(qi)端代碼如下:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd, newfd, r;
struct sockaddr_in myaddr;
struct sockaddr fromaddr;
socklen_t len = 16;
char buf[100] = {0};
pid_t pid;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創(chuang)建TCP通信的套(tao)接(jie)字(zi)--流式(shi)套(tao)接(jie)字(zi)
myaddr.sin_family = AF_INET; // 地址(zhi)信息(xi)填寫
myaddr.sin_port = htons(56666); // 要綁定(ding)的端口號
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 要綁(bang)定的地址 這里以 127.0.0.1為(wei)例
r = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // 綁定(ding)地(di)址信(xin)息
if( listen(sockfd, 10) < 0){ // 設置監聽 同一時刻(ke)能客戶端的連接請求(qiu)的大(da)數
perror("listen ");return -1;
}
while(1) { // 循(xun)環
newfd = accept(sockfd, &fromaddr, &len); // 阻(zu)塞接(jie)收 客(ke)戶端的連(lian)接(jie)請(qing)求(qiu)
pid = fork(); // 創建新(xin)進程
if(pid == 0){ // 子進程 處理(li)以連接(jie)成(cheng)功的客戶端
while(1){
r = recv(newfd, buf, 100, 0); //處理客(ke)戶端 接(jie)收信息
if(r <= 0){ printf("客戶端已退出:%d \n",newfd);break; }
printf("%d : %s\n", newfd, buf);
bzero(buf, strlen(buf));
}
close(newfd); // 關閉 連(lian)接
exit(0); // 處理完(wan) 子(zi)進程退出
}else if(pid < 0){ exit(0); }
}
close(sockfd);
}
客戶端代碼如下:
int main()
{
int sockfd,r;
char buf[100] = {0};
struct sockaddr_in toaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創(chuang)建TCP通信(xin)的套(tao)接字(zi)(zi)--流式(shi)套(tao)接字(zi)(zi)
printf("sockfd = %d\n", sockfd);
toaddr.sin_family = AF_INET; // 地址(zhi)信息填寫
toaddr.sin_port = htons(56666); // 對方的端口號(hao)
toaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 對方的IP地址(zhi)
// 發送連接請求 與對(dui)方建立連接
r = connect(sockfd, (struct sockaddr *)&toaddr, sizeof(toaddr));
if(r == -1){ perror("connect "); return -1; }
printf("connect OK\n"); // 連(lian)接成功(gong)
while(1){ // 循環(huan) 向服(fu)務器端發(fa)送(song)信息
scanf("%s", buf);
send(sockfd, buf, strlen(buf), 0);
}
close(sockfd);
}
然后,編譯服務(wu)器(qi)端(duan) 和(he) 客戶端(duan),終(zhong)端(duan)執行(xing)如圖(tu)1命(ming)令:

圖1 編譯(yi)文(wen)件
用一個終端(duan)(duan)(duan)執行服務器,多個終端(duan)(duan)(duan)執行客戶端(duan)(duan)(duan),結果如圖2:
左邊第一個(ge)是服務器端(duan)(duan),先開啟(qi);右(you)邊2個(ge)是客(ke)(ke)戶端(duan)(duan),同時訪問服務器;服務器能同時處(chu)理這(zhe)些客(ke)(ke)戶端(duan)(duan)。

圖2 執(zhi)行(xing)結果(guo)
例2:線程實現(xian)并發服務器(TCP通信)
首先,服務器(qi)端(duan)代碼(ma)如下(xia):
……
#include
void * fun(void *p) // 線程處理函數
{
int fd = *((int *)p); // 獲取傳參 得到 套接字描述符
char buf[100] = {0};
int r;
printf("pthread fd = %d start\n", fd);
while(1){ //循(xun)環 接(jie)受信息
r = recv(fd, buf, 100, 0);
if(r <= 0){ printf("客戶端已(yi)退出 : %d\n",fd); break; }
printf("%d : %s\n", fd, buf);
bzero(buf,strlen(buf));
}
close(fd); // 關閉套接字(zi) 線程結束
}
int main()
{
int sockfd, newfd, r;
struct sockaddr_in myaddr;
struct sockaddr fromaddr;
socklen_t len = 16;
char buf[100] = {0};
pthread_t tid;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創(chuang)建TCP通信(xin)的套(tao)接字--流式(shi)套(tao)接字
myaddr.sin_family = AF_INET; // 地址信息(xi)填寫(xie)
myaddr.sin_port = htons(56666); // 端口(kou)號(hao)
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // IP地址(zhi)
r = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // 綁定
if( listen(sockfd, 10) < 0) { perror("listen "); return -1; } //監聽(ting)
while(1){ // 進(jin)程 循(xun)環接受客戶端(duan)的請求
newfd = accept(sockfd, &fromaddr, &len); // 阻塞 接受 并(bing)建立連接
printf("newfd = %d\n", newfd);
pthread_create(&tid, NULL, fun, &newfd); //創建線程(cheng)(cheng) 將連接(jie)好的(de)套(tao)接(jie)字(zi)傳給線程(cheng)(cheng)
}
close(sockfd);
}
客(ke)戶端代碼,同例1中(zhong)客(ke)戶端代碼。
編(bian)譯服務器和(he)客戶端,終(zhong)端執行(xing)命(ming)令,如圖3:注意線程編(bian)譯時加載(zai)庫。

圖3 gcc編譯
用一個終端(duan)(duan)執行服(fu)(fu)務器(qi),多個終端(duan)(duan)執行客(ke)(ke)戶(hu)端(duan)(duan),結果如圖4。左(zuo)邊第一個是(shi)服(fu)(fu)務器(qi)端(duan)(duan),先開啟;右邊2個是(shi)客(ke)(ke)戶(hu)端(duan)(duan),同(tong)時訪問服(fu)(fu)務器(qi);服(fu)(fu)務器(qi)為客(ke)(ke)戶(hu)端(duan)(duan)創建(jian)線程,同(tong)時處理這(zhe)些客(ke)(ke)戶(hu)端(duan)(duan)。

圖(tu)4 執行結(jie)果(guo)
方法二:多路復用I/O
基本(ben)思想:有(you)一個存儲文(wen)(wen)件(jian)描述(shu)符(fu)的表,有(you)固定的函數(select)可以檢測表中(zhong)的文(wen)(wen)件(jian)描述(shu)符(fu)狀態,當(dang)這(zhe)些文(wen)(wen)件(jian)描述(shu)符(fu)中(zhong)的一個或多個已準(zhun)備好進行I/O時函數才返(fan)回(hui)。
函數(shu)返回時告(gao)訴(su)進程那個描述符已就(jiu)緒,可以進行I/O操作。
解決問題(ti):多(duo)進程(cheng)(多(duo)線程(cheng))情況下程(cheng)序的復雜(za)性較高,阻塞模(mo)式/非(fei)阻塞模(mo)式下效率低。IO多(duo)路復用(yong)是更好的方法,邏(luo)輯簡(jian)單、效率高。
IO多(duo)路(lu)復用涉及函(han)(han)數(shu) :第一:select函(han)(han)數(shu) 功能:檢(jian)測表中(zhong)文(wen)件描述(shu)符(fu)的(de)狀態
函數原型 : #include #include #include
int select(int n, fd_set * read_fds, fd_set *write_fds, fd_set *except_dst, struct timeval *timeout);
n : 文件描述符大值+1
read_fds : 所有讀(du)文(wen)件描述符的集合
write_fds : 寫(xie) 文件描述符集合
except_fds : 其他的 文(wen)件描(miao)述符集合
timeout : 阻塞等(deng)待的時間(jian) 毫(hao)秒
struct timeval t = {5, 600}; &t 5.6秒
NULL/0 無限等待
struct timeval t = {0, 0}; &t 0秒 不等待(dai)
返回值 : 就緒描述符的數目
超時返回 0
失敗(bai)返(fan)回 -1
第二:文件描述符操作函數(shu)(宏定義)
void FD_SET(int fd, fd_set *fds); 將文件描述符(fu) 添加(jia)到(dao) 表中
void FD_CLR(int fd, fd_set *fds); 刪(shan)除(chu) 一個文(wen)件(jian)描述符
void FD_ZERO(fd_set *fds); 清(qing)零
int FD_ISSET(int fd, fd_set *fds); 判(pan)斷 fd 是否已經準(zhun)備I/O
服務器端可以采用(yong)多(duo)路(lu)IO復用(yong)實現一對多(duo)處理(li),代碼如(ru)下:
……
#include
int main()
{
int sockfd, newfd, r, i, maxfd;
struct sockaddr_in myaddr;
struct sockaddr fromaddr;
socklen_t len = 16;
char buf[100] = {0};
fd_set fds;
sockfd = socket(AF_INET, SOCK_STREAM, 0);// 創建TCP通(tong)信(xin)的套(tao)接字(zi)--流式套(tao)接字(zi)
myaddr.sin_family = AF_INET; // 地(di)址信息填寫
myaddr.sin_port = htons(56667); // 端口號
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// IP地址
r = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // 綁定
if( listen(sockfd, 10) < 0){ //監聽
perror("listen "); return -1; }
FD_ZERO(&fds); // 清(qing)空(kong)表
FD_SET(sockfd, &fds); // 添加(jia) 套接字(zi)描述符 到表中(zhong)
maxfd = sockfd; // 記錄 描述符 的 大值
while(1){
// 阻塞 等待是否 有訪問到來
r = select(maxfd+1, &fds, NULL, NULL, NULL);
if(r <=0){ return -1; }
for(i = 0; i <= maxfd;i++){
if(FD_ISSET(i, &fds)) { //找出(chu) I/O操作的(de)套接(jie)字描述(shu)符(fu)
if(i == sockfd){ // 客戶端 發送 連接請求
newfd = accept(i, &fromaddr, &len); // 接(jie)受 并建立連接(jie)
printf("newfd = %d start\n", newfd);
FD_SET(newfd, &fds); // 將(jiang)新套(tao)接字(zi)描述符 添加到表中
maxfd = maxfd > newfd ? maxfd : newfd; // 更新 大值
}
else{ // 客戶端(duan) 接收/發送 信息
r = recv(i, buf, 100, 0);
if(r <= 0){
close(i);
FD_CLR(i, &fds); // 從表中刪除該套接(jie)字
}else {
send(i, buf, strlen(buf), 0);
printf("%d : %s\n", i, buf);
bzero(buf, strlen(buf));
}
}}}}}
客戶(hu)端代碼如下:
int main()
{
int sockfd,r;
char buf[100] = {0};
struct sockaddr_in toaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創建TCP通(tong)信(xin)的(de)套接字(zi)--流式(shi)套接字(zi)
printf("sockfd = %d\n", sockfd);
toaddr.sin_family = AF_INET; // 地址信息(xi)填寫
toaddr.sin_port = htons(56667); // 對方的(de)端口(kou)號
toaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 對方的IP地址
// 發(fa)送連接請求 與對方建立連接
r = connect(sockfd, (struct sockaddr *)&toaddr, sizeof(toaddr));
if(r == -1){ perror("connect "); return -1; }
printf("connect OK\n"); // 連接成功
scanf("%s", buf);
send(sockfd, buf, strlen(buf), 0); //向服務器端(duan)發送信息
bzero(buf, strlen(buf));
recv(sockfd, buf, 100, 0); //收取對(dui)方的回發信息
printf("recv : %s\n", buf);
close(sockfd);
}
然后,gcc編譯(yi)服務器端和客戶端,分別生(sheng)成可執(zhi)行(xing)(xing)文件,在不同終端執(zhi)行(xing)(xing)(左邊第(di)一個為服務器端,之后的(de)是客戶端),執(zhi)行(xing)(xing)后結(jie)果如圖5所(suo)示(shi):

圖5 執行結果圖
在多路(lu)復用(yong)I/O中例子中,服務器端用(yong)的是for循(xun)環依次遍歷描述符表,所(suo)以造(zao)成后(hou)面客(ke)戶端的等待(dai)問題。
以上就(jiu)是在網絡編程中常(chang)用的并發操作(zuo),希望可以為你提供(gong)一定的幫助。

