linux网络编程-socket(2)
當客戶端調用close函數的時候,服務器的read函數讀到的數據是0讀到文件結束通知,表示對端關閉了tcp連接
我們現實實現下面的功能:
?
?1、tcp客戶端從標準的輸入流中得到輸入數據發送到服務器,服務器收到數據之后,不做任何改變,將書法返回給客戶端,客戶端收到服務器的數據之后,在標準輸出流中輸出
?
上面代碼中PF_INET和AF_INET都是一樣的都是代碼tcp的協議族
tcp協議對應的流式套接字,所以寫成sock_STREAM
第三個參數可以寫成IPPPOTO_TCP或者0都是可以的
第四個結構體中服務器綁定的地址可以使用inet_addr函數將本機的點分十進制的ip地址轉換成32位的網絡地址
也可以使用htonl函數將本機的任何地址轉換成32位的函數,其中INADDR_ANY,表示本機的任何地址都可以
對于端口也必須是網絡字節序,所以需要使用htons將本機端口轉換成16位的無符號網絡字節端口
Read函數
????Ssize_t read(int fd,void *buf,size_t nbyte)
????Read函數是負責從fd中讀取內容,當讀取成功時,read返回實際讀取到的字節數,如果返回值是0,表示已經讀取到文件的結束了,小于0表示是讀取錯誤。
????如果錯誤是EINTR表示在寫的時候出現了中斷錯誤,如果是EPIPE表示網絡連接出現了問題。
?
Write函數
????Ssize_t write(int fd,const void *buf,size_t nbytes);
????Write函數將buf中的nbytes字節內容寫入到文件描述符中,成功返回寫的字節數,失敗返回-1.并設置errno變量。在網絡程序中,當我們向套接字文件描述舒服寫數據時有兩種可能:
我們來看下服務器的代碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>/* *定義一個宏,輸出錯誤信息并且退出 */ #define ERR_EXIT(m) \do \{\perror(m);\exit(EXIT_FAILURE);\}while(0)int main(int argc, char *argv[]) {int serv_sock;struct sockaddr_in serv_addr;serv_sock = socket(AF_INET, SOCK_STREAM, 0);if (serv_sock == -1){ERR_EXIT("socket創建失敗");}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");serv_addr.sin_port = htons(9999);if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){ERR_EXIT("客戶端connect失敗");}char revbuf[1024];char sendbuf[1024];memset(revbuf,0,sizeof(revbuf));memset(sendbuf,0,sizeof(revbuf));while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){write(serv_sock,sendbuf,strlen(sendbuf));read(serv_sock,revbuf,sizeof(revbuf));fputs(revbuf,stdout);//讀到多少數據就給客戶端返回多少字節的數據 memset(sendbuf,0,sizeof(revbuf));memset(revbuf,0,sizeof(revbuf));}close(serv_sock);return 0; }
?服務器代碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>/* *定義一個宏,輸出錯誤信息并且退出 */ #define ERR_EXIT(m) \ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)int main(int argc, char *argv[]) {int serv_sock;int clnt_sock;struct sockaddr_in serv_addr;struct sockaddr_in clnt_addr;socklen_t clnt_addr_size;serv_sock = socket(AF_INET, SOCK_STREAM, 0);if (serv_sock == -1){ERR_EXIT("socket創建失敗");}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(9999);if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){ERR_EXIT("bind失敗");}//SOMAXCON系統默認的最大的客戶端的連接數據 , (listen(serv_sock, 5)表示最大允許5個客戶端的連接 if (listen(serv_sock, SOMAXCONN) == -1){ERR_EXIT("listen失敗"); }clnt_addr_size = sizeof(clnt_addr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);if (clnt_sock == -1){ERR_EXIT("accept失敗"); }char revbuf[1024];while(1){memset(revbuf,0,sizeof(revbuf));int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len讀到數據的字節長度 if(len == 0){ //說明客戶端終止了數據的發送 break;}fputs(revbuf,stdout);//讀到多少數據就給客戶端返回多少字節的數據 write(clnt_sock,revbuf,len);}close(clnt_sock);close(serv_sock);return 0; }void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }?
?
在ubuntu系統上執行編譯:
gcc client.c -o client
執行客戶端程序就是./client
編譯服務器程序
gcc server.c -o server
執行服務器程序
./server
?上面的代碼只能對應一個客戶端連接一個服務器,如果一個服務器要支持多個客戶端的請求,請看socke(37)章節的代碼
?
我們來看服務器的函數:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> /* *定義一個宏,輸出錯誤信息并且退出 */ #define ERR_EXIT(m) \ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)void*thread_exc(void* arg){pthread_detach(pthread_self()); //將線程設置成分離狀態,避免僵尸線程 int clnt_sock = (int)arg;char revbuf[1024];while(1){memset(revbuf,0,sizeof(revbuf));int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len讀到數據的字節長度 if(len == 0){ //說明客戶端終止了數據的發送 break;}fputs(revbuf,stdout);//讀到多少數據就給客戶端返回多少字節的數據 write(clnt_sock,revbuf,len);}close(clnt_sock); //記得關閉線程 }int main(int argc, char *argv[]) {int serv_sock;int clnt_sock;struct sockaddr_in serv_addr;struct sockaddr_in clnt_addr;socklen_t clnt_addr_size;serv_sock = socket(AF_INET, SOCK_STREAM, 0);if (serv_sock == -1){ERR_EXIT("socket創建失敗");}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(9999);if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){ERR_EXIT("bind失敗");}//SOMAXCON系統默認的最大的客戶端的連接數據 , (listen(serv_sock, 5)表示最大允許5個客戶端的連接 if (listen(serv_sock, SOMAXCONN) == -1){ERR_EXIT("listen失敗"); }while(1){ //在while循環中一直等待客戶端的監聽 clnt_addr_size = sizeof(clnt_addr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);if (clnt_sock == -1){ERR_EXIT("accept失敗"); }//每一個客戶端的請求都開啟一個線程進行處理 pthread_t thread_id ;int ret;//將clnt_sock通過第三個參數傳遞到線程函數中 if((ret = pthread_create(&thread_id,NULL,thread_exc,(void*)clnt_sock)) != 0){ERR_EXIT("線程創建失敗");}}close(serv_sock);return 0; }void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }?
我們來看客戶端的函數:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>/* *定義一個宏,輸出錯誤信息并且退出 */ #define ERR_EXIT(m) \do \{\perror(m);\exit(EXIT_FAILURE);\}while(0)int main(int argc, char *argv[]) {int serv_sock;struct sockaddr_in serv_addr;serv_sock = socket(AF_INET, SOCK_STREAM, 0);if (serv_sock == -1){ERR_EXIT("socket創建失敗");}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");serv_addr.sin_port = htons(9999);if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){ERR_EXIT("客戶端connect失敗");}char revbuf[1024];char sendbuf[1024];memset(revbuf,0,sizeof(revbuf));memset(sendbuf,0,sizeof(revbuf));while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){write(serv_sock,sendbuf,strlen(sendbuf));read(serv_sock,revbuf,sizeof(revbuf));fputs(revbuf,stdout);//讀到多少數據就給客戶端返回多少字節的數據 memset(sendbuf,0,sizeof(revbuf));memset(revbuf,0,sizeof(revbuf));}close(serv_sock);return 0; }?
?
對應服務器的函數:有一點很關鍵在創建線程的的執行函數的入口處調用 pthread_detach(pthread_self()); //將線程設置成分離狀態,避免僵尸線程?
第二在客戶端關閉連接,服務器read字節數為0的時候,記得關閉客戶端的連接
close(clnt_sock); //記得關閉線程
}
我們來看程序運行的效果
服務器端收到了客戶端1和客戶端2的數據
?
?客戶端1:
客戶端2:
?
這里千萬不能將線程的地址傳遞進行會存在多線程隱患的問題,千萬不能寫成(void*)&clnt_sock
void*thread_exc(void* arg){
pthread_detach(pthread_self()); //將線程設置成分離狀態,避免僵尸線程
int clnt_sock = *(*int)arg;
這樣會存在多線程隱患的問題,當第一個線程正在執行thread_exc執行int clnt_sock = *(*int)arg或者自己線程的sockid的時候,此時第二個線程創建了成功改變了sockid的值,第一個線程通過*(*int)arg獲得的sockid就是剛剛創建的第二個線程的。所以這里存在線程安全問題,所以不能使用指針傳遞,所以必須使用值傳遞
但是上面的代碼還存在一個小bug
就是將int類型強制轉換成了void*類型void*)clnt_sock)存在問題,例如在64位的系統上指針void*是8個字節,int是4個字節,存在轉換的問題,可以使用下面的方式進行解決
?
程序的代碼修改如下所示:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> /* *定義一個宏,輸出錯誤信息并且退出 */ #define ERR_EXIT(m) \ do \ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0)void*thread_exc(void* arg){pthread_detach(pthread_self()); //將線程設置成分離狀態,避免僵尸線程 int clnt_sock = *((int*)arg);//記得關閉指針free(arg); char revbuf[1024];while(1){memset(revbuf,0,sizeof(revbuf));int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len讀到數據的字節長度 if(len == 0){ //說明客戶端終止了數據的發送 break;}fputs(revbuf,stdout);//讀到多少數據就給客戶端返回多少字節的數據 write(clnt_sock,revbuf,len);}close(clnt_sock); //記得關閉線程 }int main(int argc, char *argv[]) {int serv_sock;int clnt_sock;struct sockaddr_in serv_addr;struct sockaddr_in clnt_addr;socklen_t clnt_addr_size;serv_sock = socket(AF_INET, SOCK_STREAM, 0);if (serv_sock == -1){ERR_EXIT("socket創建失敗");}memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);serv_addr.sin_port = htons(9999);if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){ERR_EXIT("bind失敗");}//SOMAXCON系統默認的最大的客戶端的連接數據 , (listen(serv_sock, 5)表示最大允許5個客戶端的連接 if (listen(serv_sock, SOMAXCONN) == -1){ERR_EXIT("listen失敗"); }while(1){ //在while循環中一直等待客戶端的監聽 clnt_addr_size = sizeof(clnt_addr);clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);if (clnt_sock == -1){ERR_EXIT("accept失敗"); }//每一個客戶端的請求都開啟一個線程進行處理 pthread_t thread_id ;int ret;//將clnt_sock通過第三個參數傳遞到線程函數中 int * p = (int*)malloc(sizeof(int));*p = clnt_sock;if((ret = pthread_create(&thread_id,NULL,thread_exc, p ))!= 0){ERR_EXIT("線程創建失敗");}}close(serv_sock);return 0; }void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }?
?
?
?
轉載于:https://www.cnblogs.com/kebibuluan/p/7086423.html
總結
以上是生活随笔為你收集整理的linux网络编程-socket(2)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Node.js 博客实例(一)简单博客
- 下一篇: 在linux下玩转usb摄像头