Linux C 下的socket网络编程
1.socket簡介
????????在我們常用的網絡通信中,socket是最常用的一種,socket編程也稱套接字編程,下面我們將對socket編程進行相關講解,其中包括服務器與客戶端通信講解,以及代碼實現。
????????Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
2.socket網絡通信步驟
在我們利用socket創建網絡通信連接的時候,遵循下圖所示的步驟:
圖? 1 圖? 2如圖1所示,當我們兩臺PC進行通信時,一方的服務器端server向另一方的客戶端client發送數據時,由socket創建網絡通道,但必須指定雙方所使用的協議種類及端口號,同時還需告知IP地址。
其中IP地址相當于住址,用于查找PC所在的位置,端口號相當于“房間號”,用與告知通信雙方所使用的端口,常用的通信協議由TCP/UDP兩種,下面將補充介紹:
TCP/UDP協議對比:
1.TCP是面向連接(如打電話要先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接。
2.TCP提供可靠的服務。也就是說TCP連接傳送的數據無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付。?
3.TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流;UDP是面向報文的,UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低。
4.每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互式通信
5.TCP首部開銷20字節;UDP的首部開銷小,只有8字節。
6.TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠信道。
如圖2所示,在服務器端,首先新建一個socket套接字,同時綁定地址及端口號,這時進入監聽狀態,客戶端此時若想連接服務器端,則發出連接請求,待服務器端接收請求后,一條完整的通信方式就建立完成,客戶端和服務器便可進行數據交換了。
3.相關API介紹
創建套接字int socket(int domain,int type,int protocol),其中domain指明所使用的協議族,通常使用的因特網域為IPv4,設置為AF_INET。type為參數指定socket的類型,我們使用的為TCP協議,可以保證數據傳輸的正確性與順序性,因此第二個參數設置為SOCK_STREAM。potocol通常賦值為“0”,0選擇type類型對應的默認協議。返回值:若成功則返回socket套接字的文件描述符,若失敗則返回-1.
IP與端口號綁定int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen),參數說明:sockfd為socket返回的描述符,addr是一個指向包含有本機IP地址及端口號等信息的sockaddr類型的指針。其中第二個參數addr給出的結構體如下:
struct sockaddr {sa_family_t sa_family;char sa_data[14];}結構體中的成員,sa_data[ ]表示進程地址;而bind函數中的第三個參數addrlen表示參數addr的長度;addr參數可以接受多種類型的結構體,而這些結構體的長度各不相同,因此需要使用addrlen參數額外指定結構體長度;經常為了傳輸IPv4的地址及端口號,定義一個struct sockaddr_in類型的結構體,但最后使用bind函數時必須轉換成struct sockaddr *類型的。struct sockaddr_in如下:
struct sockaddr_in{sa_family_t sin_family;//協議族in_port_t sin_port;//端口號struct in_addr sin_addr;//IP地址結構體unsigned char sin_zero[8]://填充,沒有實際意義 }注意,在本結構體的第三個成員,相當于結構體的成員為結構體,注意結構體成員的引用。
地址轉換int inet_aton(const char *straddr,struct in_addr *addrp);該函數的作用是將字符串形式的IP地址如“192.168.0.0.1”轉換成網絡能識別的形式,第一個參數straddr為IP地址字符串,第二個參數為struct in_addr *型數據,因此保持類型一致,我們首先給struct sockaddr_in中的第三個成員結構體給出IP地址,此時的類型剛好為struct in_addr類型,但要求的是結構體指針,我們只需進行取地址運算即可。
cha *inet_ntoa(struct in_addr inaddr)該函數的作用是將網絡格式的IP地址轉換成字符串格式,通常用于接收方接收對方的IP地址并轉換為字符串形式。
監聽int listen(int sockfd,int backlog);該函數只用于服務器端不斷等待客戶端的連接請求,第一個參數為描述符,第二個參數backlog指定在請求隊列中允許的最大請求數。
接收連接int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);該函數用于接受客戶端請求的連接。第一個參數為描述符,第二個參數addr用來返回已連接的客戶端的協議地址,第三個參數addrlen為客戶端地址長度。
請求連接int connect(int sockfd,const struct sockaddr *addr,socklen_t *addrlen)該函數用于客戶端請求連接服務器端的函數,sockfd為描述符,addr為服務器端的IP地址和端口號的地址結構指針,addrlen為地址長度。
服務器端代碼如下:
#include<stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include<stdlib.h> #include<string.h>int main(int argc,char **argv) {int s_fd;int c_fd;int n_read;char readBuf[128];char msg[128]={0};struct sockaddr_in s_addr;struct sockaddr_in c_addr;memset(&s_addr,0,sizeof(struct sockaddr_in));memset(&c_addr,0,sizeof(struct sockaddr_in));//1.sockets_fd=socket(AF_INET,SOCK_STREAM,0);if(s_fd==-1){perror("socket");exit(-1);}//2.binds_addr.sin_family=AF_INET;s_addr.sin_port=htons(atoi(argv[2]));inet_aton(argv[1],&s_addr.sin_addr);bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));//3.listenlisten(s_fd,10);//4.acceptint c_len=sizeof(struct sockaddr_in);while(1){c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&c_len);if(c_fd==-1){perror("accept");}printf("get connect :%s\n",inet_ntoa(c_addr.sin_addr));//5.readif(fork() == 0){if(fork()==0){while(1){memset(msg,0,sizeof(msg));printf("input \n:");gets(msg);write(c_fd,msg,strlen(msg));sleep(1);}}while(1){memset(readBuf,0,sizeof(readBuf));n_read=read(c_fd,readBuf,128);if(n_read==-1){perror("read");}else{printf("get message %d:%s\n ",n_read,readBuf);}}}}return 0; }在服務器端,當我們與客戶端連接成功后,我們開辟一個子進程用來不斷地讀取客戶端發送過來的消息并打印,與此同時再開辟一個子進程用來檢測用戶的輸入,實現可以將服務器端的消息發送給客戶端。
客戶端代碼:
#include<stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include<stdlib.h> #include<string.h>int main(int argc,char **argv) {int c_fd;int n_read;char readBuf[128];char msg[128]={0};struct sockaddr_in c_addr;memset(&c_addr,0,sizeof(struct sockaddr_in));//1.socketc_fd=socket(AF_INET,SOCK_STREAM,0);if(c_fd==-1){perror("socket");exit(-1);}//2.connectc_addr.sin_family=AF_INET;c_addr.sin_port=htons(atoi(argv[2]));inet_aton(argv[1],&c_addr.sin_addr);if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr))==-1){perror("connect");exit(-1);}printf("get connect :%s\n",inet_ntoa(c_addr.sin_addr));while(1){//3.sendif(fork()==0){while(1){memset(msg,0,sizeof(msg));printf("input \n:");gets(msg);write(c_fd,msg,strlen(msg));sleep(2);}}//4.readwhile(1){memset(readBuf,0,sizeof(readBuf));n_read=read(c_fd,readBuf,128);if(n_read==-1){perror("read");}else{printf("get message %d:%s\n ",n_read,readBuf);}}}return 0; }與服務器端不同,當我們請求連接成功后,便開辟子進程與服務器間進行通信。
總結
以上是生活随笔為你收集整理的Linux C 下的socket网络编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用iWebOffice实现电子签章
- 下一篇: Node2vec原理剖析,代码实现