當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > linux網(wǎng)絡(luò)編程中的并發(fā)控制
在Linux網(wǎng)絡(luò)編程中,一般建立在兩端之間,服務(wù)器端和客戶端?蛻舳耸敲嫦蛴脩舻膽(yīng)用,而服務(wù)器端要處理客戶端所提出的請(qǐng)求。通常一個(gè)服務(wù)器要面向多個(gè)客戶端,保證對(duì)每個(gè)客戶端都能高效的處理,這時(shí)候需要并發(fā)操作。實(shí)現(xiàn)并發(fā)控制的方法有兩個(gè),一個(gè)是并發(fā)服務(wù)器,另一個(gè)是多路復(fù)用I/O,現(xiàn)在就給大家介紹一下這兩種方法。
方法一:并發(fā)服務(wù)器
這個(gè)方法可以通過進(jìn)程(線程)來實(shí)現(xiàn),主要根據(jù)子進(jìn)程(子線程)之間并行運(yùn)行的特點(diǎn)。將對(duì)客戶端請(qǐng)求的處理工作,交于子進(jìn)程(子線程)來處理,達(dá)到一個(gè)服務(wù)器同時(shí)處理多個(gè)客戶端的效果。通過2個(gè)例子實(shí)現(xiàn)一個(gè)簡(jiǎn)單的服務(wù)器與客戶端的一對(duì)多。
例1:進(jìn)程實(shí)現(xiàn)并發(fā)服務(wù)器(TCP通信)
首先,服務(wù)器端代碼如下:
#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); // 創(chuàng)建TCP通信的套接字--流式套接字
myaddr.sin_family = AF_INET; // 地址信息填寫
myaddr.sin_port = htons(56666); // 要綁定的端口號(hào)
myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 要綁定的地址 這里以 127.0.0.1為例
r = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr)); // 綁定地址信息
if( listen(sockfd, 10) < 0){ // 設(shè)置監(jiān)聽 同一時(shí)刻能客戶端的連接請(qǐng)求的大數(shù)
perror("listen ");return -1;
}
while(1) { // 循環(huán)
newfd = accept(sockfd, &fromaddr, &len); // 阻塞接收 客戶端的連接請(qǐng)求
pid = fork(); // 創(chuàng)建新進(jìn)程
if(pid == 0){ // 子進(jìn)程 處理以連接成功的客戶端
while(1){
r = recv(newfd, buf, 100, 0); //處理客戶端 接收信息
if(r <= 0){ printf("客戶端已退出:%d \n",newfd);break; }
printf("%d : %s\n", newfd, buf);
bzero(buf, strlen(buf));
}
close(newfd); // 關(guān)閉 連接
exit(0); // 處理完 子進(jìn)程退出
}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); // 創(chuàng)建TCP通信的套接字--流式套接字
printf("sockfd = %d\n", sockfd);
toaddr.sin_family = AF_INET; // 地址信息填寫
toaddr.sin_port = htons(56666); // 對(duì)方的端口號(hào)
toaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 對(duì)方的IP地址
// 發(fā)送連接請(qǐng)求 與對(duì)方建立連接
r = connect(sockfd, (struct sockaddr *)&toaddr, sizeof(toaddr));
if(r == -1){ perror("connect "); return -1; }
printf("connect OK\n"); // 連接成功
while(1){ // 循環(huán) 向服務(wù)器端發(fā)送信息
scanf("%s", buf);
send(sockfd, buf, strlen(buf), 0);
}
close(sockfd);
}
然后,編譯服務(wù)器端 和 客戶端,終端執(zhí)行如圖1命令:
圖1 編譯文件
用一個(gè)終端執(zhí)行服務(wù)器,多個(gè)終端執(zhí)行客戶端,結(jié)果如圖2:
左邊第一個(gè)是服務(wù)器端,先開啟;右邊2個(gè)是客戶端,同時(shí)訪問服務(wù)器;服務(wù)器能同時(shí)處理這些客戶端。
圖2 執(zhí)行結(jié)果
例2:線程實(shí)現(xiàn)并發(fā)服務(wù)器(TCP通信)
首先,服務(wù)器端代碼如下:
……
#include
void * fun(void *p) // 線程處理函數(shù)
{
int fd = *((int *)p); // 獲取傳參 得到 套接字描述符
char buf[100] = {0};
int r;
printf("pthread fd = %d start\n", fd);
while(1){ //循環(huán) 接受信息
r = recv(fd, buf, 100, 0);
if(r <= 0){ printf("客戶端已退出 : %d\n",fd); break; }
printf("%d : %s\n", fd, buf);
bzero(buf,strlen(buf));
}
close(fd); // 關(guān)閉套接字 線程結(jié)束
}
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); // 創(chuàng)建TCP通信的套接字--流式套接字
myaddr.sin_family = AF_INET; // 地址信息填寫
myaddr.sin_port = htons(56666); // 端口號(hào)
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; } //監(jiān)聽
while(1){ // 進(jìn)程 循環(huán)接受客戶端的請(qǐng)求
newfd = accept(sockfd, &fromaddr, &len); // 阻塞 接受 并建立連接
printf("newfd = %d\n", newfd);
pthread_create(&tid, NULL, fun, &newfd); //創(chuàng)建線程 將連接好的套接字傳給線程
}
close(sockfd);
}
客戶端代碼,同例1中客戶端代碼。
編譯服務(wù)器和客戶端,終端執(zhí)行命令,如圖3:注意線程編譯時(shí)加載庫。
圖3 gcc編譯
用一個(gè)終端執(zhí)行服務(wù)器,多個(gè)終端執(zhí)行客戶端,結(jié)果如圖4。左邊第一個(gè)是服務(wù)器端,先開啟;右邊2個(gè)是客戶端,同時(shí)訪問服務(wù)器;服務(wù)器為客戶端創(chuàng)建線程,同時(shí)處理這些客戶端。
圖4 執(zhí)行結(jié)果
方法二:多路復(fù)用I/O
基本思想:有一個(gè)存儲(chǔ)文件描述符的表,有固定的函數(shù)(select)可以檢測(cè)表中的文件描述符狀態(tài),當(dāng)這些文件描述符中的一個(gè)或多個(gè)已準(zhǔn)備好進(jìn)行I/O時(shí)函數(shù)才返回。
函數(shù)返回時(shí)告訴進(jìn)程那個(gè)描述符已就緒,可以進(jìn)行I/O操作。
解決問題:多進(jìn)程(多線程)情況下程序的復(fù)雜性較高,阻塞模式/非阻塞模式下效率低。IO多路復(fù)用是更好的方法,邏輯簡(jiǎn)單、效率高。
IO多路復(fù)用涉及函數(shù) :第一:select函數(shù) 功能:檢測(cè)表中文件描述符的狀態(tài)
函數(shù)原型 : #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 : 所有讀文件描述符的集合
write_fds : 寫 文件描述符集合
except_fds : 其他的 文件描述符集合
timeout : 阻塞等待的時(shí)間 毫秒
struct timeval t = {5, 600}; &t 5.6秒
NULL/0 無限等待
struct timeval t = {0, 0}; &t 0秒 不等待
返回值 : 就緒描述符的數(shù)目
超時(shí)返回 0
失敗返回 -1
第二:文件描述符操作函數(shù)(宏定義)
void FD_SET(int fd, fd_set *fds); 將文件描述符 添加到 表中
void FD_CLR(int fd, fd_set *fds); 刪除 一個(gè)文件描述符
void FD_ZERO(fd_set *fds); 清零
int FD_ISSET(int fd, fd_set *fds); 判斷 fd 是否已經(jīng)準(zhǔn)備I/O
服務(wù)器端可以采用多路IO復(fù)用實(shí)現(xiàn)一對(duì)多處理,代碼如下:
……
#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);// 創(chuàng)建TCP通信的套接字--流式套接字
myaddr.sin_family = AF_INET; // 地址信息填寫
myaddr.sin_port = htons(56667); // 端口號(hào)
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){ //監(jiān)聽
perror("listen "); return -1; }
FD_ZERO(&fds); // 清空表
FD_SET(sockfd, &fds); // 添加 套接字描述符 到表中
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)) { //找出 I/O操作的套接字描述符
if(i == sockfd){ // 客戶端 發(fā)送 連接請(qǐng)求
newfd = accept(i, &fromaddr, &len); // 接受 并建立連接
printf("newfd = %d start\n", newfd);
FD_SET(newfd, &fds); // 將新套接字描述符 添加到表中
maxfd = maxfd > newfd ? maxfd : newfd; // 更新 大值
}
else{ // 客戶端 接收/發(fā)送 信息
r = recv(i, buf, 100, 0);
if(r <= 0){
close(i);
FD_CLR(i, &fds); // 從表中刪除該套接字
}else {
send(i, buf, strlen(buf), 0);
printf("%d : %s\n", i, buf);
bzero(buf, strlen(buf));
}
}}}}}
客戶端代碼如下:
int main()
{
int sockfd,r;
char buf[100] = {0};
struct sockaddr_in toaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建TCP通信的套接字--流式套接字
printf("sockfd = %d\n", sockfd);
toaddr.sin_family = AF_INET; // 地址信息填寫
toaddr.sin_port = htons(56667); // 對(duì)方的端口號(hào)
toaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 對(duì)方的IP地址
// 發(fā)送連接請(qǐng)求 與對(duì)方建立連接
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); //向服務(wù)器端發(fā)送信息
bzero(buf, strlen(buf));
recv(sockfd, buf, 100, 0); //收取對(duì)方的回發(fā)信息
printf("recv : %s\n", buf);
close(sockfd);
}
然后,gcc編譯服務(wù)器端和客戶端,分別生成可執(zhí)行文件,在不同終端執(zhí)行(左邊第一個(gè)為服務(wù)器端,之后的是客戶端),執(zhí)行后結(jié)果如圖5所示:
圖5 執(zhí)行結(jié)果圖
在多路復(fù)用I/O中例子中,服務(wù)器端用的是for循環(huán)依次遍歷描述符表,所以造成后面客戶端的等待問題。
以上就是在網(wǎng)絡(luò)編程中常用的并發(fā)操作,希望可以為你提供一定的幫助。