Linux网络编程(Socket)
目錄
- 網絡編程(Socket)概述
- 引入
- 網絡編程通識掃盲
- socket套接字
- 套接字描述符
- 字節序
- socket編程步驟
- Linux提供的API簡析
- 創建套接字即連接協議[socket](服、客)
- 綁定IP和端口[bind](服)
- 地址轉換api
- 字節序轉換api
- 監聽[listen](服)
- 接受連接[accept](服)
- 數據收發[read、write](服、客)
- 客戶端的[connect]函數(客)
- socket服務端代碼實現
- socket客戶端代碼實現
- 實現雙方(多方)一直聊天
- 多方消息收發
網絡編程(Socket)概述
引入
前面幾個章節講的進程間通訊均基于同一臺Linux內核實現的,因此無法實現多機(和手機、單片機、X86架構等)通訊,因此引入網絡通訊,入門先學習Socket(又叫做套接字)網絡編程。
問題:兩臺計算機實現TCP(通過socket編程)通信時,要用到線么??總感覺僅僅通過代碼就能建立連接不太靠譜。
回答:TCP連接的基礎就是網絡連接已經建立好之后,所以物理連接肯定是基礎。至于物理連接有很多種,可以是有線的、也可以是無線的,只要協議支持TCP/IP協議就可以。
網絡編程通識掃盲
socket套接字
socket起源于Unix,而Unix/Linux基本哲學之一就是“一切皆文件”,都可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現, socket即是一種特殊的文件,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)。
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
套接字描述符
其實就是一個整數,我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、stderr。當應用程序要創建一個套接字時,操作系統就返回一個小整數作為描述符,應用程序則使用這個描述符來引用該套接字需要I/O請求的應用程序請求操作系統打開一個文件。操作系統就創建一個文件描述符提供給應用程序訪問文件。從應用程序的角度看,文件描述符是一個整數,應用程序可以用它來讀寫文件。
字節序
字節序就是字節存儲的順序(從高地址開始存儲還是從低地址開始存儲),在網絡編程中要注意相關協議使用的字節序,防止數據傳輸出錯。具體使用的是字節序轉換api,配合端口號使用。
socket編程步驟
模擬場景:
步驟介紹:
Linux提供的API簡析
創建套接字即連接協議[socket](服、客)
int socket(int domain,int type,int protocol)綁定IP和端口[bind](服)
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);struct sockaddr 這個結構體一般同等替代成struct sockaddr_in,使用的時候注意新結構體的類型強制轉換。
地址轉換api
int inet_aton(const char *straddr, struct in_addr *addrp);char *inet_ntoa(struct in_addr inaddr);字節序轉換api
監聽[listen](服)
int listen(int sockfd, int backlog);backlog:支持最大的連接數
接受連接[accept](服)
三次握手成功就建立accept連接。函數里面的結構體存放客戶端的IP和端口號等信息!
int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);數據收發[read、write](服、客)
和文件read、write用的同一個api。底下最后兩對一般用于UDP
數據收發第二套API,多了flags控制參數
客戶端的[connect]函數(客)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);socket服務端代碼實現
利用telnet的方式進行通信,目前還沒有寫客戶端的代碼
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int main(int argc, char const *argv[]) {int s_fd;int c_fd;int n_read;int n_write;char readBuf[128];char *returnMsg="我收到了你的信息";//發送給客戶端的消息 盡量不使用數組struct sockaddr_in c_addr;struct sockaddr_in s_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));memset(&s_addr,0,sizeof(struct sockaddr_in));//數據清空 再配置//1.socket int socket(int domain, int type, int protocol);s_fd=socket(AF_INET,SOCK_STREAM,0);//ipv4 tcp協議if(s_fd == -1){printf("創建socket失敗");perror("socket:");exit(-1);}//2.bind int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);s_addr.sin_family=AF_INET;//ipv4s_addr.sin_port=htons(8687);//端口號,選擇5000以上(有些端口被系統調用)。honts返回網絡字節序//int inet_aton(const char *cp, struct in_addr *inp)inet_aton("192.168.103.49",&s_addr.sin_addr)//inet_aton("127.0.0.1",&s_addr.sin_addr);//sin_addr是結構體sockaddr_in里面的結構體 存放IP(下面有查找到原型) 然后轉換為網絡能識別的格式//或者使用ifconfig命令查到實際本機的IP也可以bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//結構體類型轉換,因為用的同等替換的結構體sockaddr_in//3.listen int listen(int sockfd, int backlog);listen(s_fd,10);//監聽10個連接//4.accept int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);int client=sizeof(struct sockaddr_in); //要求用指針(存放長度)c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&client);//sockaddr_in進行類型強轉 存放客戶端信息if(c_fd == -1){printf("連接失敗\n");perror("accept:");exit(-1);}//客戶端printf("客戶端的ip:%s\n",inet_ntoa(c_addr.sin_addr)); //把網絡格式的ip地址打印成字符串格式//5.read ssize_t read(int fd, void *buf, size_t count);n_read=read(c_fd,readBuf,128); //c_fd客戶端if(n_read == -1){perror("read:");}else{printf("得到的消息:%d,%s\n",n_read,readBuf);}//6.write ssize_t write(int fd, const void *buf, size_t count);n_write=write(c_fd,returnMsg,strlen(returnMsg));return 0; }運行結果(客戶端發送 huai dan):
Tips:在user/include目錄下查找頭文件。查找結構體sockaddr_in的定義原型、使用了哪個頭文件時,我們可以用grep xx* -nir 來實現(n:顯示行號;i:不區分大小寫;r:遞歸)。
進去就能找到這個結構體原型啦:
socket客戶端代碼實現
客戶端向服務端發送消息,實現通信(只能進行一次通訊)。
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int main(int argc, char const *argv[]) {int c_fd;int n_read;int n_write;int c_connect;char readBuf[128];char *returnMsg="這是來自客戶端的信息";struct sockaddr_in c_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));//數據清空//1.socket int socket(int domain, int type, int protocol);c_fd=socket(AF_INET,SOCK_STREAM,0);//ipv4 tcp協議if(c_fd == -1){printf("創建socket失敗");perror("socket:");exit(-1);}c_addr.sin_family=AF_INET;//ipv4c_addr.sin_port=htons(8687);//端口號,選擇5000以上。honts返回網絡字節序//int inet_aton(const char *cp, struct in_addr *inp)inet_aton("192.168.103.49",&c_addr.sin_addr);//轉換為網絡能識別的格式//2.connect int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);c_connect=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));if(c_connect == -1){printf("連接失敗\n");perror("connect:");}//3.write/send ssize_t write(int fd, const void *buf, size_t count);n_write=write(c_fd,returnMsg,strlen(returnMsg));//注意和sizeof的使用區別//4.read ssize_t read(int fd, void *buf, size_t count);n_read=read(c_fd,readBuf,128);if(n_read == -1){perror("read:");}else{printf("來自服務端的消息:%d,%s\n",n_read,readBuf);}//5.close int close(int fd);close(c_fd);return 0; }運行結果:
實現雙方(多方)一直聊天
本質就是上面的代碼加入while循環,實現不斷的消息收發。
其中服務端使用了兩次fork():
- 第一次是在accept后(即三次握手成功后)創建進程,實現和多個客戶端的通信;
- 第二次fork()創建進程應用在和客戶端通信“寫”的過程,而“讀”放在while循環中,這樣就實現了讀和寫并行運行。
客戶端僅用fork創建了一個進程,同樣應用在“寫”,“讀”放在while循環中,這樣就實現了讀和寫并行運行。
服務端代碼:
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int main(int argc, char const *argv[]) {int s_fd;int c_fd;int n_read;int n_write;char readBuf[128];char returnMsg[128]={0};struct sockaddr_in c_addr;struct sockaddr_in s_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));memset(&s_addr,0,sizeof(struct sockaddr_in));//數據清空if(argc != 3){printf("參數出錯\n");exit(-1);}//1.socket int socket(int domain, int type, int protocol);s_fd=socket(AF_INET,SOCK_STREAM,0);//ipv4 tcp協議if(s_fd == -1){printf("創建socket失敗");perror("socket:");exit(-1);}//2.bind int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);s_addr.sin_family=AF_INET;//ipv4s_addr.sin_port=htons(atoi(argv[2]));//端口號,選擇5000以上。honts返回網絡字節序,atoi(argv[2])防止端口被占用//int inet_aton(const char *cp, struct in_addr *inp)inet_aton(argv[1],&s_addr.sin_addr);//轉換為網絡能識別的格式bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//3.listen int listen(int sockfd, int backlog);listen(s_fd,10);//監聽10個連接//4.accept int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);int client=sizeof(struct sockaddr_in);while(1){//不斷接收客戶端c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&client);if(c_fd == -1){printf("連接失敗\n");perror("accept:");exit(-1);}printf("客戶端的ip:%s\n",inet_ntoa(c_addr.sin_addr)); //把網絡格式的ip地址打印成字符串格式if(fork() == 0){if(fork() == 0){while(1){//不斷寫入memset(returnMsg,0,sizeof(returnMsg));printf("請輸入:\n");gets(returnMsg);//6.write ssize_t write(int fd, const void *buf, size_t count);n_write=write(c_fd,returnMsg,strlen(returnMsg));}}while(1){//不斷讀取//5.read ssize_t read(int fd, void *buf, size_t count);memset(readBuf,0,sizeof(readBuf));//不斷清空數據防止數據重復出現n_read=read(c_fd,readBuf,128);if(n_read == -1){perror("read:");}else{printf("得到的消息:%d,%s\n",n_read,readBuf);}}}}return 0; }客戶端代碼:
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int main(int argc, char const *argv[]) {int ret;int c_fd;int n_read;int n_write;int c_connect;char readBuf[128];char returnMsg[128]={0};char *quit="quit";struct sockaddr_in c_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));//數據清空if(argc != 3){printf("參數出錯\n");exit(-1);}//1.socket int socket(int domain, int type, int protocol);c_fd=socket(AF_INET,SOCK_STREAM,0);//ipv4 tcp協議if(c_fd == -1){printf("創建socket失敗");perror("socket:");exit(-1);}c_addr.sin_family=AF_INET;//ipv4c_addr.sin_port=htons(atoi(argv[2]));//端口號,選擇5000以上。honts返回網絡字節序//int inet_aton(const char *cp, struct in_addr *inp)inet_aton(argv[1],&c_addr.sin_addr);//轉換為網絡能識別的格式//2.connect int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);c_connect=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));if(c_connect == -1){printf("連接失敗\n");perror("connect:");}while(1){if(fork() == 0){while(1){//不斷寫入memset(returnMsg,0,sizeof(returnMsg));printf("請輸入:\n");gets(returnMsg);//3.write/send ssize_t write(int fd, const void *buf, size_t count);n_write=write(c_fd,returnMsg,strlen(returnMsg));if(strcmp(quit,returnMsg) == 0){//如果輸入quit則客戶端就退出exit(0);}}}while(1){//不斷讀取memset(readBuf,0,sizeof(readBuf));//不斷清空數據防止數據重復出現//4.read ssize_t read(int fd, void *buf, size_t count);n_read=read(c_fd,readBuf,128);if(n_read == -1){perror("read:");}else{printf("來自服務端的消息:%d,%s\n",n_read,readBuf);}}//5.close int close(int fd);close(c_fd);}return 0; }結果:
多方消息收發
上一節代碼其實已經可以實現多方通信了,不過存在兩個問題:
- 1、客戶端發消息回車的那一瞬間,光標不知道被哪個進程搶到了,也就是說服務器同一時刻發送的消息,不能確定哪個客戶端子進程收到消息。
- 2、客戶端之間無法進行互相通訊。
下面的demo加入了類似心跳包的功能,用來說明服務端其實是知道哪個客戶端發來的消息,并且每隔兩秒給每個客戶端回復。
如果想要完全實現類似QQ聊天機制,思路就是將服務端作為中轉站,客戶端和客戶端之間通過服務器完成聊天功能。當然客戶端之間要提前建立“好友”關系,所謂的好友關系可以通過sqlite數據庫存儲每個客戶端的IP、端口、賬號等信息,然后服務端后臺對這些進行邏輯處理,實現客戶端之間的類似QQ聊天機制。
服務端代碼
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int main(int argc, char const *argv[]) {int mark=0;int s_fd;int c_fd;int n_read;int n_write;char readBuf[128];char returnMsg[128]={0};struct sockaddr_in c_addr;struct sockaddr_in s_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));memset(&s_addr,0,sizeof(struct sockaddr_in));//數據清空if(argc != 3){printf("參數出錯\n");exit(-1);}//1.socket int socket(int domain, int type, int protocol);s_fd=socket(AF_INET,SOCK_STREAM,0);//ipv4 tcp協議if(s_fd == -1){printf("創建socket失敗");perror("socket:");exit(-1);}//2.bind int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);s_addr.sin_family=AF_INET;//ipv4s_addr.sin_port=htons(atoi(argv[2]));//端口號,選擇5000以上。honts返回網絡字節序,atoi(argv[2])防止端口被占用//int inet_aton(const char *cp, struct in_addr *inp)inet_aton(argv[1],&s_addr.sin_addr);//轉換為網絡能識別的格式bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//3.listen int listen(int sockfd, int backlog);listen(s_fd,10);//監聽10個連接//4.accept int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);int client=sizeof(struct sockaddr_in);while(1){//不斷接收客戶端c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&client);if(c_fd == -1){printf("連接失敗\n");perror("accept:");exit(-1);}printf("客戶端的ip:%s\n",inet_ntoa(c_addr.sin_addr)); //把網絡格式的ip地址打印成字符串格式mark++;if(fork() == 0){if(fork() == 0){while(1){sprintf(returnMsg,"歡迎第%d號客戶端",mark);//6.write ssize_t write(int fd, const void *buf, size_t count);n_write=write(c_fd,returnMsg,strlen(returnMsg));sleep(20);}}while(1){//5.read ssize_t read(int fd, void *buf, size_t count);memset(readBuf,0,sizeof(readBuf));n_read=read(c_fd,readBuf,128);if(n_read == -1){perror("read:");}else{printf("得到%d號的消息:%s\n",mark,readBuf);}}}}return 0; }客戶端代碼
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int main(int argc, char const *argv[]) {int c_fd;int n_read;int n_write;int c_connect;char readBuf[128];char returnMsg[128]={0};char *quit="quit";struct sockaddr_in c_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));//數據清空if(argc != 3){printf("參數出錯\n");exit(-1);}//1.socket int socket(int domain, int type, int protocol);c_fd=socket(AF_INET,SOCK_STREAM,0);//ipv4 tcp協議if(c_fd == -1){printf("創建socket失敗");perror("socket:");exit(-1);}c_addr.sin_family=AF_INET;//ipv4c_addr.sin_port=htons(atoi(argv[2]));//端口號,選擇5000以上。honts返回網絡字節序//int inet_aton(const char *cp, struct in_addr *inp)inet_aton(argv[1],&c_addr.sin_addr);//轉換為網絡能識別的格式//2.connect int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);c_connect=connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));if(c_connect == -1){printf("連接失敗\n");perror("connect:");}while(1){if(fork() == 0){while(1){//不斷寫入memset(returnMsg,0,sizeof(returnMsg));printf("請輸入:\n");gets(returnMsg);//3.write/send ssize_t write(int fd, const void *buf, size_t count);n_write=write(c_fd,returnMsg,strlen(returnMsg));if(strcmp(quit,returnMsg) == 0){//如果輸入quit則客戶端就退出exit(0);}}}while(1){//不斷讀取memset(readBuf,0,sizeof(readBuf));//不斷清空數據防止數據重復出現//4.read ssize_t read(int fd, void *buf, size_t count);n_read=read(c_fd,readBuf,128);if(n_read == -1){perror("read:");}else{printf("來自服務端的消息:%s\n",readBuf);}}//5.close int close(int fd);close(c_fd);}return 0; }運行結果:
總結
以上是生活随笔為你收集整理的Linux网络编程(Socket)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何运行ruby代码
- 下一篇: EntityFramework的安装