基于Linux的Socket编程之TCP全双工Server-Client聊天程序
轉載:http://blog.csdn.net/apollon_krj/article/details/53437764#0-tsina-1-58570-397232819ff9a47a7b7e80a40613cfe1
一、引言:
由于accept函數、read、write、recv、send等函數都是是阻塞式的,在同一個進程之中,只要有任何一個函數沒有執行完畢,處于阻塞狀態,之后的函數與功能就不能處理,很難實現點對點的Server-Client全雙工通信。因為全雙工通信是非阻塞式的通信方式,即使對方沒有回復消息,都可以隨時發送。如果只是電報機式的半雙工通信,之前已經基本實現:基于Linux的SOCKET編程之TCP半雙工Client-Server聊天程序?
而對于QQ點對點聊天式的全雙工通信,又該怎樣實現呢?對于當前所學只能想到使用fork函數創建一個子進程,其中父進程用來處理發(或者收),而子進程用來處理收(或者發)的過程。fork函數的一些基本的使用可參照:進程創建與fork()的恩怨情仇
二、測試代碼:
測試環境(Redhat 6.4)
1、客戶端(Client):
# include<stdio.h> # include<stdlib.h> # include<string.h> # include<unistd.h> # include<sys/socket.h> # include<arpa/inet.h> # include<netinet/in.h> # include<signal.h># define MAX_BUF_LEN 128/*處理系統調用中產生的錯誤*/ void error_print(char * ptr) {perror(ptr);exit(EXIT_FAILURE); } /*處理通信結束時回調函數接收到的信號*/ void quit_tranmission(int sig) {printf("recv a quit signal = %d\n",sig);exit(EXIT_SUCCESS); } int main(void) {int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0)error_print("socket");struct sockaddr_in servaddr;bzero(&servaddr,sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = 1234;servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/int conn;if((conn = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0)error_print("connect");pid_t pid;pid = fork();if(pid == -1){error_print("fork");}if(pid == 0){char recv_buf[MAX_BUF_LEN] = {0};while(1){bzero(recv_buf,sizeof(recv_buf));int ret = read(sockfd, recv_buf, sizeof(recv_buf));if(ret == -1)error_print("read");else if(ret == 0){printf("server is close!\n");break;//子進程收到服務器端退出的信息(服務器Ctrl+C結束通信進程,read函數返回值為0,退出循環)}fputs(recv_buf,stdout);/*將收到的信息輸出到標準輸出stdout上*/}close(sockfd);/*子進程退出,通信結束關閉套接字*/kill(getppid(),SIGUSR1);/*子進程結束,也要向父進程發出一個信號告訴父進程終止接收,否則父進程一直會等待輸入*/exit(EXIT_SUCCESS);/*子進程正常退出結束,向父進程返回EXIT_SUCCESS*/}else{signal(SIGUSR1,quit_tranmission);/*回調函數處理通信中斷*/char send_buf[MAX_BUF_LEN] = {0};/*如果服務器Ctrl+C結束通信進程,fgets獲取的就是NULL,否則就進入循環正常發送數據*/while(fgets(send_buf,sizeof(send_buf), stdin) != NULL){int set = write(sockfd, send_buf, strlen(send_buf));/*將send_buf緩沖區的數據發送給對端服務器*/if(set < 0)error_print("write");bzero(send_buf,strlen(send_buf));}close(sockfd);/*通信結束,關閉套接字*/}return 0; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
2、服務器(Server):
# include<stdio.h> # include<stdlib.h> # include<string.h> # include<unistd.h> # include<sys/socket.h> # include<arpa/inet.h> # include<netinet/in.h> # include<signal.h># define MAX_BUF_LEN 128void error_print(char * ptr) {perror(ptr);exit(EXIT_FAILURE); }void quit_tranmission(int sig) {printf("recv a quit signal = %d\n",sig);exit(EXIT_SUCCESS); } int main(void) {int sockfd = socket(AF_INET, SOCK_STREAM, 0);/*IPV4流式協議即TCP協議*/if(sockfd < 0)error_print("socket");struct sockaddr_in servaddr;bzero(&servaddr,sizeof(servaddr));servaddr.sin_family = AF_INET;/*IPV4*/servaddr.sin_port = 1234;servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*使用本地環回地址做測試*//*inet_aton("127.0.0.1",&servaddr.sin_addr);//與inet_addr函數作用相同*//*setsockopt確保服務器不用等待TIME_WAIT狀態結束就可以重啟服務器,繼續使用原來的端口號*/int on = 1;if( setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)error_print("setsockopt");/*綁定本地Socket地址*/if(bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)error_print("bind");/*監聽連接*/if(listen(sockfd, SOMAXCONN) < 0)error_print("listen");struct sockaddr_in peeraddr;/*存儲連接成功的客戶端Socket信息*/socklen_t peerlen = sizeof(peeraddr);int conn;/*接收監聽隊列第一個完成連接的請求*/if((conn = accept(sockfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)error_print("accept");pid_t pid;pid = fork();/*創建一個新的子進程*/if(pid == -1){error_print("fork");}if(pid == 0){/*子進程中用來向客戶端發送數據*/signal(SIGUSR1,quit_tranmission);/*回調函數處理通信中斷*/char send_buf[MAX_BUF_LEN]={0};/*如果客戶端Ctrl+C結束通信進程,fgets獲取的就是NULL,否則就進入循環正常發送數據*/while(fgets(send_buf, sizeof(send_buf), stdin) != NULL){write(conn,send_buf,strlen(send_buf));bzero(send_buf,strlen(send_buf));/*發送完成清空發送緩沖區*/}exit(EXIT_SUCCESS);/*成功退出子進程*/}else{char recv_buf[MAX_BUF_LEN]={0};while(1){bzero(recv_buf,strlen(recv_buf));int ret = read(conn, recv_buf, sizeof(recv_buf));/*讀取conn連接發送過來的數據*/if(ret < 0)error_print("read");else if(ret == 0){printf("client is close!\n");break;//父進程收到服務器端退出的信息(服務器Ctrl+C結束通信進程,read函數返回值為0,退出循環)}fputs(recv_buf,stdout);}kill(pid,SIGUSR1);/*父進程結束,也要向子進程發出一個信號告訴子進程終止接收,否則子進程會一直等待輸入*/}close(conn);close(sockfd);return 0; }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
收到對端結束信息(NULL信息)的進程要向等待發送的進程發送一個結束通信的信號,回調函數處理使得等待輸入的進程結束,否則該進程會一直等待,直到有輸入(但此時的輸入已經沒有意義,所以應提早結束,而不是一直等待)。
三、測試結果:
這個不厚道的服務器結束了通信:
總結
以上是生活随笔為你收集整理的基于Linux的Socket编程之TCP全双工Server-Client聊天程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 地下城堡3民众之印怎么获得
- 下一篇: gethostbyname() 函数说明