http://blog.csdn.net/li_wen01/article/details/52665505
工作中最近有使用到socket 向客戶端同時發送和接收數據,因為是嵌入式linux設備,且要求只能同時一個客戶端連接該端口。考慮到節省系統資源,只創建了兩個線程分別實現服務端的收發數據。下面直接上代碼,該代碼為在PC機上程序,已作詳細注釋。
server.c
[objc]?view plaincopy print?
- #include<stdio.h>??
- #include<stdlib.h>??
- #include<string.h>??
- #include<errno.h>??
- #include<sys/types.h>??
- #include<sys/socket.h>??
- #include<netinet/in.h>??
- #include<termios.h>??
- #include<sys/types.h>?????
- #include<sys/stat.h>??????
- #include<fcntl.h>??
- #include<unistd.h>??
- #include<sys/ioctl.h>??
- #include<signal.h>??
- ??
- #define?MAXLINE?256??
- #define?PORT????6666??
- int?listenfd;??
- int?connfd;??
- pthread_t?read_id,?write_id;??
- ??
- /*?
- linux?ctrl?+?C?會產生?SIGINT信號?
- 接收到SIGINT?信號進入該函數?
- */??
- void?stop(int?signo)??
- {??
- ????printf("stop\n");??
- ????close(connfd);????
- ????close(listenfd);??
- ?????_exit(0);??
- }??
- ??
- /*?
- 當客戶端斷開連接的時候,?
- 在服務端socket?send進程可以收到收到信號SIGPIPE,?
- 收到SIGPIPE信號進入該函數結束創建的線程。?
- */??
- void?signal_pipe(int?signo)??
- {??
- ????pthread_kill(read_id,SIGQUIT);//向read線程發送SIGQUIT??
- ????pthread_join(read_id,NULL);???//阻塞線程運行,直到read?線程退出。??
- ??????
- ????close(connfd);????????????????//關閉連接??
- ????printf("read?pthread?out?\n");??
- ??????
- ????pthread_exit(0);??????????????//結束write?線程??
- }??
- ??
- /*?
- read?線程接收到SIGQUIT信號,?
- 執行線程退出操作?
- */??
- void?pthread_out(int?signo)??
- {??
- ????pthread_exit(0);??
- }??
- ??
- /*?
- read?線程執行函數?
- */??
- void*?read_func(void*?arg)??
- {??
- ????char?readbuff[MAXLINE];??
- ????int?n?=?0;??
- ????int?fd;??
- ??
- ????fd?=?*(int*)arg;????/*main?主進程傳遞過來的連接文件描述符*/??
- ????memset(&readbuff,0,sizeof(readbuff));??
- ??
- ????signal(SIGQUIT,pthread_out);?/*?注冊SIGQUIT?信號*/???
- ????while(1)??
- ????{??
- ????????n?=?recv(fd,?readbuff,?MAXLINE,?0);??/*recv?在這里是阻塞運行*/??
- ????????if(n?>?0)??
- ????????{??
- ????????????printf("server?recv?data:?%s?\n",readbuff);??
- ????????}??
- ????};??
- }??
- /*?
- write?線程執行函數?
- */??
- void*?write_func(void*?arg)??
- {??
- ????char?writebuff[MAXLINE];??
- ????char*?write?=?"I?am?server";??
- ????unsigned?char?i?=?0;??
- ????int?num?=?0;??
- ????int?fd;??
- ??
- ????fd?=?*(int*)arg;??
- ????memset(&writebuff,0,sizeof(writebuff));??
- ??????
- ????signal(SIGPIPE,signal_pipe);?/*?注冊?SIGPIPE信號?*/??
- ????while(1)??
- ????{??
- ????????sleep(1);??
- ????????send(fd,write,strlen(write)+1,0);/*向客戶端發送數據*/??
- ????}??
- }??
- ??
- int?main(int?argc,?char**?argv)??
- {??
- ????char?buff[MAXLINE];??
- ????int?num;??
- ????int?addrlen;??
- ????struct?sockaddr_in?server_addr;??/*服務器地址結構*/??
- ????struct?sockaddr_in?client_addr;??/*客戶端地址結構*/??
- ??
- ????if((listenfd?=?socket(AF_INET,SOCK_STREAM,0))?==?-1)/*建立一個流式套接字*/??
- ????{??
- ????????printf("create?socket?error:?%s(errno:?%d)\n",strerror(errno),errno);??
- ????????exit(0);??
- ????}??
- ??????
- ????/*設置服務端地址*/??
- ????addrlen?=?sizeof(struct?sockaddr_in);??
- ????memset(&server_addr,?0,?addrlen);??
- ????server_addr.sin_family?=?AF_INET;????/*AF_INET表示?IPv4?Intern?協議*/??
- ????server_addr.sin_addr.s_addr?=?htonl(INADDR_ANY);?/*INADDR_ANY?可以監聽任意IP?*/??
- ????server_addr.sin_port?=?htons(PORT);?/*設置端口*/??
- ??????
- ????/*綁定地址結構到套接字描述符*/??
- ????if(?bind(listenfd,?(struct?sockaddr*)&server_addr,?sizeof(server_addr))?==?-1)??
- ????{??
- ????????printf("bind?socket?error:?%s(errno:?%d)\n",strerror(errno),errno);??
- ????????exit(0);??
- ????}??
- ??
- ????/*設置監聽隊列,這里設置為1,表示只能同時處理一個客戶端的連接*/??
- ????if(?listen(listenfd,?1)?==?-1)??
- ????{??
- ????????printf("listen?socket?error:?%s(errno:?%d)\n",strerror(errno),errno);??
- ????????exit(0);??
- ????}??
- ??????
- ????signal(SIGINT,stop);?/*注冊SIGINT?信號*/??
- ????while(1)??
- ????{?????
- ????????printf("wait?client?accpt?\n");??
- ????????if(?(connfd?=?accept(listenfd,?(struct?sockaddr*)&client_addr,?&addrlen))?==?-1)/*接收客戶端的連接,這里會阻塞,直到有客戶端連接*/??
- ????????{??
- ????????????printf("accept?socket?error:?%s(errno:?%d)",strerror(errno),errno);??
- ????????????continue;??
- ????????}??
- ??????
- ????????if(pthread_create(&read_id,NULL,read_func,&connfd))/*創建?read?線程*/??
- ????????{??
- ????????????printf("pthread_create?read_func?err\n");??
- ????????}??
- ??
- ????????if(pthread_create(&write_id,NULL,write_func,&connfd))/*創建?write?線程*/??
- ????????{??
- ????????????printf("pthread_create?write_func?err\n");??
- ????????}??
- ??????????
- ????????pthread_join(write_id,NULL);?/*阻塞,直到write進程退出后才進行新的客戶端連接*/??
- ????????printf("write?pthread?out?\n");??
- ????}??
- }??
? ? 這里還有一個問題,signal(SIGPIPE,signal_pipe)函數的注冊,我本來是把它放置在主線程main函數中,然后在signal_pipe 同時回收read 和 write線程,結果read進程可以正常回收,write線程卻回收失敗,具體原因也不清楚,求高人指點。于是我就是用SIGQUIT信號,在write線程收到SIGPIPE信號時,通知read線程退出,直到其退出再退出write線程。這樣就實現了系統資源的回收。
client客戶端測試程序:
[objc]?view plaincopy print?
- #include<stdio.h>?????????
- #include<stdlib.h>??
- #include<string.h>??
- #include<errno.h>??
- #include<sys/types.h>??
- #include<sys/socket.h>??
- #include<netinet/in.h>??
- #include<termios.h>??
- #include<sys/types.h>?????
- #include<sys/stat.h>??????
- #include<fcntl.h>??
- #include<unistd.h>??
- #include<sys/ioctl.h>??
- #include<signal.h>??
- ??
- #define?MAXLINE?256??
- #define?PORT????6666??
- int?fd;??
- /*?
- linux?ctrl?+?C?會產生?SIGINT信號?
- 接收到SIGINT?信號進入該函數?
- */??
- void?stop(int?signo)??
- {??
- ????printf("client?stop\n");??
- ????close(fd);????
- ????_exit(0);??
- }??
- ??
- /*客戶端處理函數*/??
- void?client_process(void)??
- {??
- ????char?readbuff[MAXLINE];??
- ????char?writebuff[MAXLINE];??
- ????charchar?*?write?=?"I?am?client";??
- ????int?num?=?0;??
- ??????
- ????while(1)??
- ????{??
- ????????num?=?recv(fd,readbuff,MAXLINE,0);/*接收服務端的數據,recv在這里如果沒有數據會阻塞*/??
- ????????if(num?>?0)??
- ????????{??
- ????????????printf("client?read?data?:?%s?\n",readbuff);??
- ????????????send(fd,?write,?strlen(write)+1,?0);?/*接收到數據后再向服務端發送一個字符串*/??
- ????????}??
- ????????else?if(num?==?0)/*recv返回值為0?的時候表示服務端已經斷開了連接*/??
- ????????{??
- ????????????stop(1);?/*執行退出操作*/??
- ????????}??
- ????}??
- }??
- ??
- int?main(int?argc,?char**?argv)??
- {??
- ????struct?sockaddr_in?server_addr;??
- ????struct?sockaddr_in?client_addr;??
- ????int?ret;??
- ??
- ????fd?=?socket(AF_INET,SOCK_STREAM,0);/*建立流式套接字*/??
- ????if(fd?<?0)??
- ????{??
- ????????printf("clinet?socket?err?\n");??
- ????}??
- ??
- ????/*設置服務端地址*/??
- ????memset(&server_addr,0,sizeof(server_addr));??
- ????server_addr.sin_family?=?AF_INET;???????????????/*AF_INET表示?IPv4?Intern?協議*/??
- ????server_addr.sin_addr.s_addr?=?htonl(INADDR_ANY);/*INADDR_ANY?可以監聽任意IP?*/??
- ????server_addr.sin_port?=?htons(PORT);?????????????/*設置端口*/??
- ??
- ????inet_pton(AF_INET,argv[1],&server_addr.sin_addr);/*將用戶輸入的字符串類型的IP地址轉為整型*/??
- ????connect(fd,(struct?sockaddr*)&server_addr,sizeof(server_addr));/*連接服務器*/??
- ??????
- ????signal(SIGINT,stop);????/*注冊SIGINT信號*/??
- ????client_process();???????/*進入處理函數*/??
- ??????
- ????close(fd);/*關閉文件*/??
- ????return?0;??
- }??
下面是測試執行情況:
[objc]?view plaincopy print?
- root@ubuntu:/home/share/Socket#?./server???
- wait?client?accpt???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- read?pthread?out???
- write?pthread?out???
- wait?client?accpt???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- read?pthread?out???
- write?pthread?out???
- wait?client?accpt??
- ??
- ??
- ??
- root@ubuntu:/home/share/Socket#?./client?127.0.0.1??
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- ^Cclient?stop??
- root@ubuntu:/home/share/Socket#?./client?127.0.0.1??
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- ^Cclient?stop??
- root@ubuntu:/home/share/Socket#???
[objc]?view plaincopy print?
- root@ubuntu:/home/share/Socket#?./server???
- wait?client?accpt???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- server?recv?data:?I?am?client???
- ^Cstop??
- root@ubuntu:/home/share/Socket#???
- ??
- root@ubuntu:/home/share/Socket#?./client?127.0.0.1??
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?read?data?:?I?am?server???
- client?stop??
- root@ubuntu:/home/share/Socket#??
這里實現的是只有一個客戶端同時連接時的處理方式,如果要處理多客戶端同時連接,使用這種方式好像并不合適,可以使用多進程的方式處理。