天天看點

《Linux程式設計》套接字筆記

套接字:一種通信機制,通過套接字可以進行本地和網絡的鏈接。明確的講客戶和服務器區分開來(cs架構),是系統配置設定給服務器進程的類似與檔案描述符的資源,不能與其他進程共享。

本地的名字是Linux的檔案名,一般放在/tmp(/usr/tmp)

網絡套接字是與客戶鏈接的特定網絡有關的服務器標示符(端口號或者訪問點)

socket通信的流程如下:

《Linux程式設計》套接字筆記

實例:

問題:

本地通信出現找不到檔案: No such file or directory

在ubuntu下面沒有權限,需要改變路徑爲/tmp/ssss.domain 就OK了

一個簡單的本地客戶:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main()
{
int sockfd = ;
int len = ;
struct sockaddr_un address;
int result = ;
char ch = 'A';

extern int errno;

sockfd = socket(AF_UNIX,SOCK_STREAM,);
address.sun_family = AF_UNIX;
strcpy(address.sun_path,"/tmp/server_socket.domain");
len = sizeof(address);

result = connect(sockfd,(struct sockaddr *)&address,len);

if ( result == -)
    {
        perror("oops client1\n");
printf("errno = %d\n",errno);
exit();
}

    write(sockfd,&ch,);
read(sockfd,&ch,);
printf("char from server = %c\n",ch);
close(sockfd);
exit();
}
           

一個簡單的本地服務器:

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
int server_sockfd = ;
int client_sockfd = ;
int server_len = ;
int client_len = ;

extern int errno;

struct sockaddr_un server_address;
struct sockaddr_un client_address;
//  create the local file
unlink("/tmp/server_socket.domain");
server_sockfd = socket(AF_UNIX,SOCK_STREAM,);
//  the way of comunicating is local 
server_address.sun_family = AF_UNIX;
//the location
strcpy(server_address.sun_path,"/tmp/server_socket.domain");
server_len = sizeof(server_address);

bind(server_sockfd,(struct sockaddr*)&server_address,server_len);
listen(server_sockfd,);
while (  )
    {
char ch;
printf("server waiting\n");
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address,(socklen_t*)&client_len);
//hanle the error
if ( client_sockfd == - )
        {
            printf("errno = %d\n",errno);
close(server_sockfd);
exit();
}
        read(client_sockfd,&ch,);
ch++;
write(client_sockfd,&ch,);
close(client_sockfd);
}
}
           

套接字屬性:

1 域:指定套接字使用的網絡介質

比如:AF_INET,AF_INET6,AF_UNIX,AF_ISO,AF_XNS

2 類型

流(stream)

流套接字由類型sock_stream指定,在AF_INET是使用TCP/IP實現的,具有有序,可靠,雙向字節流,在傳輸的過程中遇到錯誤不會傳回,太大的數據將會從組,很像檔案流

數據報(datagram)

由類型SOCK_DGRAM指定,在AF_INET是由UDP/IP實現的

優點 服務器奔潰了,也不會影響上課

3 協議

底層的傳輸機制可以使用不止一個協議來提供要求的套接字類型,就可以選擇一個特定的協議。

todo:有什麼協議

unix網絡套接字和檔案系統套接字,隻需要使用默認的就可以

4 創建套接字API

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol)
           

domain制定協議族,包括:

AF_UNIX UNIX域協議(檔案系統套接字) 常用 本地套接字

AF_INET APRA因特網協議(UNIX網絡套接字) 常用 包括因特網在內的TCP/IP網絡進行通信的程式

AF_ISO ISO標準協議

AF_NS 施樂網絡系統協議

AF_IPX Novell IPX協議

AF_APPLETALK Appletalk DDS

type:

SOCK_STREAM :字節流,有序,可靠,面向鏈接,雙向,AF_INET默認通過TCP提供,TCP提供分片和重組長消息,重傳丟失的數據

SOCK_DGRAM:數據報,不可靠,亂序,AF_INET由UDP數據報提供

protocol

協議通常由套接字類型和套接字域決定,通常不需要選擇,默認爲0

傳回值:

整形的描述符,可以通過該描述符接受和發送數據

5 套接字位址

AF_UNIX:由sockaddr_un描述

#include <sys/un.h>
struct sockaddr_un{
    sa_family_t            sum_family;
   char                          sun_path[];
}
           

對套接字處理的不同系統調用可能使用不同類型但類似的結構的位址結構來描述

sum_family :指定位址類型

sun_path:套接字位址

AF_INET:由sockaddr_in來描述

#include <netinet/in.h>
struct sockaddr_in{
    short int             sin_family;
    unsigned short int  sin_port;
    struct in_addr     sin_addr;
}
struct in_addr{
    unsigned long int  s_addr;
}
           

AF_INET由域,IP位址和端口號來完全確定,從應用角度來看是以的套接字行爲就像檔案描述符一樣,並且通過一個唯一的整數值來區分。

6 命名套接字

通過socket創建的套接字必須命名才能被其他進程使用。

bind系統調用吧address中的位址配置設定給與檔案描述付socket關聯的命名套接字,

address_len位址結構的長度

#include <sys/socket.h>
int bind(int socket,const struct sockaddr* address,size_t address_len)
           

傳回:

0 成功

-1 失敗

errno值

EBADF 檔案描述付無效

ENOTSOCK 檔案描述付對應的不是一個套接字

EINVAL 檔案描述付對應的是一個已命名的套接字

EADDRNOTAVAIL 位址不可用

EADDRINUSE 位址已經綁定了一個套接字

EACCESS 權限不足

ENOTDI 檔案名不符合要求

7創建套接字隊列

通過listen系統調用來完成,服務器程式必須調用

#include <sys/socket.h>
int listen(int socket,int backlog);
           

backlog:等待處理的連接數最大個數,往後的鏈接講失敗,常用5

傳回值

0 成功

-1 失敗

8 接受鏈接

通過accept系統調用

#include <sys/socket.h>
int accept(int socket,struct sockaddr* address)
           

accept接受套接字對壘未處理的第一個鏈接,傳回新的套接字描述符,新套接字描述符與服務器監聽的類型是一樣的

套接字必須先命名(bind),並且由listen調研給他配置設定一個連接隊列。address_len預期的位址長度,大於則系統會

進行截斷,不關心位址的情況下addres可以爲空

阻塞:

1 套接字隊列沒有未處理的鏈接

可以通過O_NONBLOCK改變這一行爲

int flags = fcntl(socket,F_GETFL,0)

傳回-1 發生錯誤

EWOULDBLOCK :指定了O_NONBLOCK,隊列中沒有未處理的鏈接

EINTR:阻塞時。執行被中斷

9 請求鏈接

通過connect系統調用

#include <sys/socket.h>
int connect(int socket,const struct sockaddr* address,size_t address_len)
           

socket指定的套接字講鏈接到address指定的服務器套接字,位址長度由address_len指定。

傳回

0 成功

-1 失敗

EBADF 檔案描述付無效

EALREADY 該套接字上已經有一個在進行鏈接

ETIMEDOUT 鏈接超時

ECONNREFUSED 鏈接請求被服務器拒絕

鏈接不能立刻建立,講阻塞一段不確定的時間,時間到達,鏈接放棄,調用失敗

但如果是被信號中斷,該型號得到處理,調用失敗(errno設置爲EINTR),鏈接會以異步方式建立

必須檢查該鏈接是否成功建立

可以通過O_NONBLOCK改變這一行爲

int flags = fcntl(socket,F_GETFL,0)

不能立刻建立,調用失敗(errno設置爲EINPROGRESS),鏈接將以異步方式進行,可以使用select來檢查。

10 關閉套接字

通過close系統調用

11 套接字通信

檔案套接字缺點:除非定義爲絕對路徑名,否則套接字創建在服務器的當前目錄下,要更通用型,必須放在客戶端和服務器

都認可的全局訪問的目錄,網絡套接字使用端口號就可以了

每個與計算機通信的網絡都有一個對應的硬體接口,一個計算機在每個網絡中都可能有不同的名字,記錄中/etc/hosts檔案中,比如:

127. 0. 0. 1 localhost # Loopback

192. 168. 1. 1 tilde. localnet # Local, private Ethernet

158. 152. X. X tilde. demon. co. uk # Modem dial- up

12 主機位元組序與網絡子節序

主機位元組序列分為大端和小端,比如:0x12345678

在ibm powerpc這種大端的機器中用0x12345678 表示

在itel pc x86中用0x87654321表示

為了在不同系列的計算機中,通過網絡傳輸資料的時候達成一緻,使用了網絡子節序

從計算機傳到網絡的時候,先轉換為網絡子節序

從網絡讀取資料到計算機的時候,從網絡子節序轉換為計算機自己的子節序

需要包含頭檔案 netline/in.h

#include <netline/in.h>
unsigned long int htonl( unsigned long int hostlong); 
unsigned short int htons( unsigned short int hostshort); 
unsigned long int ntohl( unsigned long int netlong);
unsigned short int ntohs( unsigned short int netshort);
           

以上函數将16和32位整數中計算機子節序和網絡子節序中轉換

函數名是與之操作的簡寫形式

比如htonl(host to network long)

htons(host to network short)

13 網絡資訊

對于一個更通用的網絡伺服器和用戶端來講,可以通過網絡資訊函數來決定使用的位址和端口号,主機資料庫的函數在

struct hostent *gethostbyaddr( const void *addr, size_ t len, int type); 
struct hostent *gethostbyname( const char *name);
           

傳回值最少傳回幾個成員

struct hostent { char *h_ name; /* name of the host */
char **h_ aliases; /* list of aliases (nicknames) */ 
int h_ addrtype; /* address type */ 
int h_ length; /* length in bytes of the address */
char **h_ addr_ list /* list of address (network order)
           

如果沒有則傳回空指針

與服務及其端口号有關的資訊也可以通過一些服務資訊來擷取

#include < netdb. h>
struct servent *getservbyname( const char *name, const char *proto);
 struct servent *getservbyport( int port, const char *proto);
           

proto 參數表示連結該服務的協定,有兩個取值tcp(SOCK_STREAM)和udp(SOCK_DGRAM)

傳回值最少包括以下幾個成員

struct servent { 
char *s_ name; /* name of the service*/ 
char **s_ aliases; /* list of aliases (alternative names) */ 
int s_ port; /* The IP port number */ 
char *s_ proto; /* The service type, usually "tcp" or "udp" */ 
};
           

可以調用gethostbyname把結果列印出來,但要調用inet_ ntoa轉換為可以列印的字元串

#include < arpa/ inet. h> 
char *inet_ ntoa( struct in_ addr in)
           

這個函數的作用是把一個主機網際網路位址轉換為一個點分四元祖的字元串

失敗時傳回-1

還可以用以下的函數

#include < unistd. h> 
int gethostname( char *name, int namelength);
           

它的作用是把主機名字寫入name中,namelength表示長度,太長則會截斷,失敗傳回-1

14 守護程序xinetd/inetd

超級伺服器程式(網際網路守護程序)同時監聽許多端口位址上的連結,當有客戶連結上時,超級服務程式就會啟動相應的服務。這就讓針對各項網絡服務不需要一直運作着。

xinetd/inetd通常使用相應的界面來配置也可以通過配置檔案來設定

15 套接字選項

套接字選項很多,可以通過以下函數來設定

#include < sys/ socket. h> 
int setsockopt( int socket, int level, int option_ name, const void *option_ value, size_ t option_ len);
           

成功傳回0,失敗傳回-1

16 多客戶

一旦連接配接建立,套接字的行為就類似于底層的檔案描述符,在很多情況下類似于雙向通道。伺服器在接受一個新連接配接的時候會建立出一個新套接字,原先的套接字會繼續監聽以後的連接配接,放在隊列裡等待處理,原先的套接字的行為就想檔案描述符。給了我們一種方法,我們可以調用fork産生子程序,子程序繼承打開的套接字,子程序可以和客戶通信,主程序接受以後的新連接配接。主要要設定SIGCHID的信号處理函數避免出現僵屍程序。這樣就可以實作一個伺服器處理多個用戶端。

17 select

16提到的方法不是最佳的方法,因為我們需要的是一種不阻塞,不等待客戶請求到達的情況下實作處理多個客戶。

select系統調用可以同時在多個底層檔案描述符上等待輸入的到達。

#include < sys/ types. h>
 #include < sys/ time. h> 
void FD_ ZERO( fd_ set *fdset); //用于将fdset初始化情況
 void FD_ CLR( int fd, fd_ set *fdset); //清除集合中有fd指定的檔案描述符
void FD_ SET( int fd, fd_ set *fdset);  //設定集合中由fd指定的檔案描述符
int FD_ ISSET( int fd, fd_ set *fdset); //當fd傳遞的檔案描述符是集合fdset的元素時,傳回非0值
select可以使用timevale設定一個逾時值防止出現無線阻塞。
struct timeval { time_ t tv_ sec; /* seconds */ 
long tv_ usec; /* microseconds */
 }
           

select 函數 會在 發生 以下 情況 時 傳回: readfds 集合 中有 描述 符 可讀、 writefds 集合 中有 描述 符 可 寫 或 errorfds 集合 中有 描述 符 遇到 錯誤 條件。 如果 這 3 種 情況 都沒 有 發生, select 将 在 timeout 指定 的 逾時 時間 經過 後 傳回。 如果 timeout 參數 是 一個 空 指針并且 套 接 字 上 也沒 有 任何 活動, 這個 調用 将 一直 阻塞 下去。

18 資料報服務

當客戶發送一個短小的請求給伺服器,并且期望接受到一個短小的響應時,使用資料報(UDP)服務就可以了。相比于位元組流(TCP),資料報簡化了伺服器程式設計的難度。

因為 UDP 提供 的 是 不可靠 服務, 是以 你 可能 發現 數 據報 或 響應 會 丢失。 如果 資料 對于 你 來說 非常 重要, 就 需要 小心 編寫 UDP 客戶 程式, 以 檢查 錯誤 并在 必要 時 重傳。 實際上, UDP 數 據報 在 局域 網中 是非 常 可靠 的。

你 需要 像 以前 一樣 使用 套 接 字 和 close 系統 調用, 但 你 需要 用兩 個數 據報 專用 的 系統 調用 sendto 和 recvfrom 來 代替 原來 使 用在 套 接 字 上 的 read 和 write 調用。

繼續閱讀