1. 程式人生 > >C語言 socket函式的簡單運用 29

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
版權宣告:本文為博主原創文章,轉載請附上博文連結!