主線程負責(zé)發(fā)送消息,另一線程負責(zé)接收消息。服務(wù)端和客戶端均是如此。
注意當(dāng)A方close掉用于通信的socket端口后,該端口是不會立即關(guān)閉的。因為此時可能B方的信息還沒send完。因此,此時A方的recv仍舊處于阻塞狀態(tài),會最后再等待收一次信息。此時,當(dāng)B方send一個信息給A后,A方recv到后,A的socket端口就正式關(guān)閉了,A的recv返回-1。
此時由于A的socket端口已關(guān)閉,因此B得recv返回0。
注意區(qū)分,如果是這樣的代碼:如果A方close掉socket端口后,A方程序中并沒有處于阻塞狀態(tài)的recv,那么這個socket端口一close就是真正關(guān)閉了。
代碼本代碼服務(wù)器和客戶端程序?qū)懺谝黄鹆?。具體請看注釋。
/************************************************************************* > File Name: my_chat.c > Author: KrisChou > Mail:zhoujx0219@163.com > Created Time: Thu 28 Aug 2014 11:06:04 PM CST ************************************************************************/#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <pthread.h>#include <netdb.h>#define ACTIVE 1 #define PASSIVE 0#define SERVER_PORT 1314#define CLIENT_PORT 1350/* 接收消息通過線程實現(xiàn) */void* chat_handler(void* arg){ int fd_client = (int)arg; char buf[1024]; int test; while(memset(buf, 0, 1024), (test=recv(fd_client, buf, 1024, 0)) > 0) { write(1, buf, strlen(buf)); } //用于測試recv返回值 PRintf("break child while!/n"); printf("test = %d /n",test); return (void*)0; //close(fd_client);}int main(int argc,char* argv[])// exe peer_ip{ int flag; int fd_peer; /* 對方socket描述符,由acccept返回 */ if(argc == 1) { flag = PASSIVE ; }else if(argc == 2) { flag = ACTIVE ; } /*---------------------------------------socket----------------------------------------*/ int sfd ; /* sfd為本地socket描述符 */ sfd = socket(AF_INET, SOCK_STREAM, 0) ; if(sfd == -1) { perror("socket"); exit(1); } /*------------ ---------------------------bind -----------------------------------------*/ char my_hostname[1024]=""; struct hostent * p ; gethostname(my_hostname, 1024); /* 獲取本地hostname,以便根據(jù)hostname查出ip */ p = gethostbyname(my_hostname) ;/* 根據(jù)hostname,獲取指向struct hostent結(jié)構(gòu)體的指針 */ struct sockaddr_in local_addr ; /* 綁定socket端口以及IP的結(jié)構(gòu)體 */ memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET ; if(flag == ACTIVE) { local_addr.sin_port = htons(CLIENT_PORT); /* 作為主動方,需要綁定的端口是client_port */ }else { local_addr.sin_port = htons(SERVER_PORT); /* 作為服務(wù)器,需要綁定的端口server_port */ /*兩者均為本機端口*/ } local_addr.sin_addr = *(struct in_addr *)(p ->h_addr_list)[0]; /* 綁定IP地址 */ /* 本地IP */ if(-1 == bind(sfd, (struct sockaddr *)&local_addr, sizeof(local_addr))) { perror("bind"); close(sfd); exit(1); } /*----------------------------------如果是服務(wù)器,listen+accept---------------------------------------------- */ if(flag == PASSIVE) { if(-1 == listen(sfd, 10)) { perror("listen"); close(sfd); exit(1); } struct sockaddr_in peer_addr ; /* 存放返回socket描述符方的聯(lián)系方式的結(jié)構(gòu)體,是傳出參數(shù) */ int len ; memset(&peer_addr, 0, sizeof(peer_addr)); len = sizeof(peer_addr); //accept返回對方socket描述符,并且peer_addr與len均為傳出參數(shù) fd_peer = accept(sfd,(struct sockaddr*)&peer_addr,&len); printf("%s:%d begin to talk!/n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port)); close(sfd);//kris }else if(flag == ACTIVE) /*-------------如果是主動方,connect -------------------------------------------------*/ { fd_peer = sfd ; //如果是主動方實際上本方就是從sfd通信的。 /* 主動方要去連對方,需要知道對方IP,通過命令行參數(shù)傳;也需要知道服務(wù)器端口號,程序已經(jīng)寫死 */ struct sockaddr_in server_addr ; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET ; server_addr.sin_port = htons(SERVER_PORT);/*加了htons,如果右邊是12345678,傳到左邊還是12345678;如果不加htons如果右邊是12345678,傳到左邊就變成78563412了 */ server_addr.sin_addr.s_addr = inet_addr(argv[1]); /* connect連不上對方就返回-1,睡一秒后繼續(xù)連 */ while( connect(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { sleep(1); printf("connecting..../n"); } printf("success ! /n"); } pthread_t thd ; pthread_create(&thd, NULL,chat_handler,(void*)fd_peer); char msg[1024]; while(memset(msg, 0, 1024), fgets(msg, 1024, stdin) != NULL) { send(fd_peer, msg, strlen(msg), 0); } close(fd_peer); //用于測試 printf("close close close /n"); pthread_join(thd, NULL); }/* 本代碼通信是在fd_peer這個socket端口上 */程序運行退出結(jié)果討論1. 如果A按ctrl+D(主線程退出while循環(huán),close端口,阻塞在pthread_join上),那么B此時再發(fā)送一個消息,在線程中A recv 到并將其打印,此時A的socket端口真正關(guān)閉,recv返回-1,退出循環(huán),退出線程,A結(jié)束。B由于A的socket端口關(guān)閉,線程中的recv返回0,退出循環(huán),退出線程。此時B如果按下ctrl+D,也會退出程序。
2. 一方按下ctrl+D后,另一方馬上也按下ctrl+D。則雙方陷入僵局。如果一方按下ctrl+c,強行退出程序后,另一方也會跟著退。原因:一方強退,導(dǎo)致socket端口真正關(guān)閉,另一方子線程中的recv返回0,退出循環(huán),退出線程。
3. 一方上來就ctrl+c強退。則對方的子線程會退掉,原因同上。此時另一方按ctrl+D就退出程序了。
小結(jié)recv返回值分以下3種情況
1. 大于0。接收到的數(shù)據(jù)大小。
2. 等于0。對方的socket端口真正關(guān)閉。
3. 小于0。出錯。其中一種情況是,本方的socket端口已經(jīng)真正關(guān)閉后,還試圖從該端口中獲取數(shù)據(jù)。
recv返回值實際上和read是類似的。
發(fā)現(xiàn)send一個空的消息,如下:
send(fd_peer,"",0,0);
對方的recv不會返回0,仍舊阻塞著。
這點和UDP有所不同,在UDP中,一方sendto一個空消息給另一方,對方的recvfrom是返回0的。
新聞熱點
疑難解答
圖片精選