C語言 socket函式的簡單運用 29
下面所講的是在Linux系統上用gcc編寫的,Windows好像不能用。
socket是套接字,是用來在網路之間進行資料傳遞的。
在網路中,我們通過一個IP來確定一臺主機,再通過一個port來確定一個進行,而socket就是一個進行。因此:socket = IP + port
網路傳輸的2種模式
C/S:server端與client端進行相互通訊,好處是可以自行的定義。而且在首次登入後都不會浪費過多的流量等等。而其的壞處也是顯而易見的,在安裝在電腦上的客戶端會對系統造成安全問題,開發量大等等。
而還有一種模型是:B/S,與C/S的缺點正好互補。在這裡就只是簡單的說下。
在講到正題前,我們先來補充下幾個知識點。我們先不用想是幹什麼用的,先記住就行了用處在下面會講到。
socket傳輸型別:
1 .流式socket(SOCK_STREAM)
流式套接字使用的是TCP協議,由於TCP協議建立在三次握手的基礎上,所以這種型別能夠提供可靠的、面向連線的通訊流,能夠保證資料傳輸的正確性和順序性。
2.資料報socket(SOCK_DGRAM)
資料報套接字使用的是UDP協議,由於UDP將資料扔出去之後就不管的桀驁特性,所以該型別定義了一種無連線的服務,資料通過相互獨立的報文進行傳輸,是無序的,並且不保證是可靠、無差錯的。
3.原始socket
原始套接字允許對底層協議如IP或ICMP(在網路層,而TCP和UDP都在傳輸層)進行直接訪問,功能比較強大但是使用不便,主要用於一些協議的開發。
socket指定的連線型別:
- AF_INET:IPv4協議
- AF_INET4:IPv6協議
- AF_LOCAL:UNIX域協議
- AF_LINK:鏈路地址協議
- AF_KEY:祕鑰套接字
大端位元組序與小端位元組序
大端位元組序在Linux誕生之初就已經沿用,因此在網路中傳輸的時候用的是大端位元組序。而小端位元組序是微軟後來發明的,我們計算機內部的就是小端位元組序,這裡指的是Windows和Linux。因此我們在進行網路傳輸的時候要把IP和port 從小端位元組序轉換成大端位元組序。什麼是大小端:大端:就是高位的地址儲存地位的值(或低位的地址儲存高位的值),小端:高位的地址儲存高位的值,低位的地址儲存低位的值。
因此我們需要一些函式來進行大小端的轉換。
#include <arpa/inet.h> //這是下面的埠轉換的函式的標頭檔案
數字轉換,建立在傳入的引數必須是數字的:
埠:
htons(要轉換的埠(也可以是存放埠的變數)); //這個是把主機的埠轉換成網路的,host to network short
ntohs(要轉換的埠(也可以是存放埠的變數)); //這個是把網路的埠轉換成主機的,network to host short
ip地址:
htonl(要轉換的ip(也可以是存放ip的變數)); //這個是把主機的ip轉換成是網路的ip
ntohl(要轉換的ip(也可以是存放ip的變數)); //這個是把網路的ip轉換成主機的ip
字串轉換:
ip地址:
inet_pton(socket連線型別,源字串,輸出結果); //這個是把主機的IP轉換成網路的IP,ip to network
1 int inet_pton(int family,const char * strptr,void * addrptr); 2 返回:1--成功, 0--輸入不是有效的表達格式 , -1--出錯inet_pton
inet_ntop(socket連線型別,結構體的IP地址的指標,接收的陣列(char型別),接收陣列的長度); //這個是把網路的ip轉換成主機ip,network to ip
1 const char * inet_ntop(int family,const void * addrptr,char * strptr,size_t len); 2 其中len =sizeof(* strptr) 3 返回: 指向結果的指標--成功 , NULL--出錯inet_ntop
結構體,我們這裡說的結構體是指socket內建的結構體,用於儲存IP的型別,IP和埠號的內建結構體。
socket的結構體有四種:sockaddr,sockaddr_in,sockaddr_un,socket_in6
sockaddr:最早的一種結構體,現在已經被淘汰掉了。但是在呼叫一些函式的時候還是需要這個型別的。
sockaddr_in:ipv4的一種結構體,也是我們要學習的重點。
sockaddr_un:unix的一種,不是用於網路傳輸的。
sockaddr_in6:和上面的sockaddr_in一樣,但是這個是支援ipv6的。暫時不怎麼需要用到。
在下面我只講下:sockaddr 和 sockaddr_in 這兩種型別的結構,剩下的兩種我們暫時不需要。
1 struct sockaddr { 2 unsigned short sa_family; /* address family, AF_xxx */ 3 char sa_data[14]; /* 14 bytes of protocol address */ 4 };sockaddr結構
1 struct sockaddr_in { 2 short int sin_family; /* Address family */ 3 unsigned short int sin_port; /* Port number */ 4 struct in_addr sin_addr; /* Internet address */ 5 unsigned char sin_zero[8]; /* Same size as struct sockaddr */ 6 }; 7 struct in_addr { 8 unsigned long s_addr; 9 };sockaddr_in結構
我們通常用到的是sockaddr_in,要記住的是ip的下面還包含了一個結構體,因此要指到這個結構體裡面。下面的前三項就是我們要做的。
sin_family指代協議族,在socket程式設計中只能是AF_INET
sin_port儲存埠號(使用網路位元組順序)
sin_addr儲存IP地址,使用in_addr這個資料結構
sin_zero是為了讓sockaddr與sockaddr_in兩個資料結構保持大小相同而保留的空位元組。
先看一下使用TCP和UDP的流程圖:
TCP:
UDP:
學藝不精,沒有弄過UDP的。因此就用TCP的模型說幾個要注意的點:
- 我們在連線的時候sever會執行到accept這個函式。那麼就會處於阻塞狀態等待client的連線,只有連線了才會往下面走。
- server端的程式碼到accept前面的屬於連線所需要的,connect前面也是連線所需要的。
- client端可以不指定自己的IP和埠,客戶端是主動連線,而伺服器是等待連線。
- 客戶端:socket函式的返回值進過連線的操作後,這個的返回值就是服務端的控制代碼
- 服務端:socket函式的返回值進過連線的操作後,這個的返回值就是本身的控制代碼。而accept的返回值就是客戶端的控制代碼
- 最後記得借宿的時候要close,客戶端的close掉socket的返回值。服務端close掉socket和accept的返回值
socket:
標頭檔案:#include < sys/socket.h>
函式原型:int socket(int family,int type,int protocol)
引數含義:
family:對應的就是AF_INET、AF_INET6等。
type:套接字型別:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW。
protocol:0
返回值:
成功:非負套接字描述符。
出錯:-1
bind:
標頭檔案:#include< sys/socket.h>
函式原型:int bind(int sockfd,struct sockaddr *my_addr,int addrlen)
引數含義:
sockfd:套接字描述符。
my_addr:本地地址。
addrlen:地址長度:
返回值:
成功:0
出錯:-1
listen:
標頭檔案:#include < sys/socket.h>
函式原型:int listen(int sockfd,int backlog)
引數含義:
sockfd:套接字描述符
backlog:請求佇列中允許的最大請求數,大多數系統預設為20
返回值:
成功:0
出錯:-1
accept:
標頭檔案:#include < sys/socket.h>
函式原型:int accept(int sockfd,struct sockaddr * addr,socklen_t* addrlen)
引數含義:
sockfd:套接字描述符
addr:客戶端地址
addrlen:地址長度
返回值:
成功:返回非負數,這個類似於socket的返回值。是客戶端的控制代碼。
出錯:-1
connect:
標頭檔案:#include < sys/socket.h>
函式原型:int connect(int sockfd,struct sockaddr* serv_addr,int addrlen)
引數含義:
sockfd:套接字描述符
serv_addr:伺服器端地址
addrlen:地址長度
返回值:
成功:0
出錯:-1
send
標頭檔案:#include < sys/socket.h>
函式原型:int send(int sockfd,const void* msg,int len,int flags)
引數含義:
sockfd:套接字描述符
msg:指向要傳送資料的指標
len:資料長度
flags:一般為0
返回值:
成功:傳送的位元組數
出錯:-1
recv
標頭檔案:#include < sys/socket.h>
函式原型:int recv(int sockfd,void* buf,int len,unsigned int flags)
引數含義:
sockfd:套接字描述符
buf:存放接受資料的緩衝區
len:資料長度
flags:一般為0
返回值:
成功:接受的位元組數
出錯:-1
read
標頭檔案:#include <unistd.h>
函式原型:ssize_t read(int fd, void *buf, size_t count);
引數含義:
fd:被讀的物件
buf:存放接受資料的緩衝區
size_t:這裡是要讀出的位元組數
返回值:
成功:讀出的位元組數
出錯:-1
write
標頭檔案:#include <unistd.h>
函式原型:ssize_t write(int fd, const void *buf, size_t count);
引數含義:
fd:被寫入物件
buf:存放接受資料的緩衝區
size_t:這裡是要寫入的位元組數
返回值:
成功:寫入的位元組數
出錯:-1
例子:
1 //建立server 2 #include<sys/socket.h> 3 #include<unistd.h> 4 #include<arpa/inet.h> 5 #include<stdio.h> 6 #include<string.h> 7 /*第一個的標頭檔案是:socket,bind,listen,accept,connect這幾個函式的標頭檔案 8 *第二個標頭檔案是:read,write函式的標頭檔案* 9 *第三個的標頭檔案是:inet_pton,inet_ntop,ntohs,htons這幾個函式的,還有其他的這裡就不一一列出來了 10 * */ 11 12 #define server_ip "127.0.0.1" //伺服器的ip,注意我們這裡用的是字串 13 #define server_sport 7776 //伺服器埠號 14 15 int main(){ 16 int sock,cli; 17 //建立套接字 18 sock = socket(AF_INET,SOCK_STREAM,0); //第一個引數表示我們用的是IPV4,第二個引數表示我們用的是TCP協議,第三個引數表示我們用的是一般形式 19 //建立連線所需要的結構體 20 struct sockaddr_in server_addr,client_addr; //這裡我們引用的是socket_in的結構體,這個結構體是ipv4的 21 22 server_addr.sin_family = AF_INET; //我們在這個結構體中要設定三個引數:1,我們輸入的ip的型別AF_INET2,我們的埠,這裡要把“大端” 23 server_addr.sin_port = htons(server_sport); //轉換成“小端”,我們用htops函式 3,這個引數是IP有兩個要注意的:第一這是一個字串所以要用 24 inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);//inet_pton這個函式。第二:要填的,在結構體裡的sin_addr,所指的另外一個結構體裡面的in_addr。 25 26 //連線 27 bind(sock,(struct sockaddr*)&server_addr,sizeof(server_addr)); //一個前面socket的返回的套接字,一個是上面定義的結構體,要記得轉換型別成: 28 //struct sockaddr*,還有結構體的長度 29 //設定允許連線數量 30 listen(sock,20); //左邊的是套接字,右邊的是連線數量 31 //設定使用者訪問的控制代碼 32 socklen_t cli_len = sizeof(client_addr); 33 cli = accept(sock,(struct sockaddr*)&client_addr,&cli_len); //三個引數:第一個引數是sock套接字。第二個引數是客戶端的結構體名字,要記得轉換。第三i 是使用者端的套 34 接字長度,要記得轉換成socklen_t*型別。我們可以通過這個的返回值來接收客戶端的資訊。 35 //上面的就是建立服務端的連結所需要的。 36 char buf[BUFSIZ]; //作業系統的巨集,大概是8k大小 37 int n,i; 38 while(1){ 39 n = read(cli,buf,sizeof(buf)); //返回值:讀到的位元組數 40 for(i = 0;i<=n;i++){ 41 buf[i] = toupper(buf[i]); 42 } 43 write(cli,buf,n); 44 //printf("%s",buf); 45 } 46 close(sock); 47 close(cli); 48 return 0; 49 }
1 //建立client 2 #include<sys/socket.h> 3 #include<arpa/inet.h> 4 #include<unistd.h> 5 #include<stdio.h> 6 #include<string.h> 7 8 #define server_ip "127.0.0.1" 9 #define server_port 7776 10 11 //客戶端可以不指定自己的ip,因為系統會自動分配埠和ip,因此我們不需要bind函式 12 int main(){ 13 int cli; 14 cli = socket(AF_INET,SOCK_STREAM,0); 15 16 struct sockaddr_in server_addr; 17 memset(&server_addr,0,sizeof(server_addr)); 18 19 server_addr.sin_family = AF_INET; 20 server_addr.sin_port = htons(server_port); 21 inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr); 22 23 connect(cli,(struct sockaddr*)&server_addr,sizeof(server_addr)); 24 25 char buf [BUFSIZ]; 26 int ret,i; 27 while(1){ 28 fgets(buf,sizeof(buf),stdin); 29 write(cli,buf,strlen(buf)); //讀取檔案操作 30 ret = read(cli,buf,sizeof(buf)); 31 write(STDOUT_FILENO,buf,strlen(buf)); 32 } 33 close(cli); 34 return 0; 35 }
因為下面的博主寫的太好了,借鑑了大部分。剩下的都是自己學的總結。
---------------------
作者:繁城落葉
來源:CSDN
原文:https://blog.csdn.net/Leafage_M/article/details/78459799
版權宣告:本文為博主原創文章,轉載請附上博文連結!