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