Linux網絡編程之套接字基礎


1.套接字的基本結構

struct sockaddr

這個結構用來存儲套接字地址。

數據定義:

struct sockaddr {

unsigned short sa_family; /* address族, AF_xxx */

char sa_data[14]; /* 14 bytes的協議地址 */

};

sa_family 一般來說,都是“AFINET”。

sa_data 包含了一些遠程電腦的地址、端口和套接字的數目,它里面的數據是雜溶在一

切的。

為了處理struct sockaddr, 程序員建立了另外一個相似的結構 struct sockaddr_in:

struct sockaddr_in (“in” 代表 “Internet”)

struct sockaddr_in {

short int sin_family; /* Internet地址族 */

unsigned short int sin_port; /* 端口號 */

struct in_addr sin_addr; /* Internet地址 */

unsigned char sin_zero[8]; /* 添0(和struct sockaddr一樣大小)*/

};

這個結構提供了方便的手段來訪問socket address(struct sockaddr)結構中的每一個元

2.套接字字節轉換程序的列表:

l htons()——“Host to Network Short”主機字節順序轉換為網絡字節順序(對無符號

短型進行操作4 bytes)

l htonl()——“Host to Network Long” 主機字節順序轉換為網絡字節順序(對無符

號長型進行操作8 bytes)

l ntohs()——“Network to Host Short “ 網絡字節順序轉換為主機字節順序(對無符

號短型進行操作4 bytes)

l ntohl()——“Network to Host Long “ 網絡字節順序轉換為主機字節順序(對無符

號長型進行操作8 bytes)

3. IP 地址轉換

Linux 系統提供和很多用於轉換IP 地址的函數.首先,假設你有一個struct sockaddr_in ina,並且你的IP 是166.111.69.52 ,你想把你的IP 存儲到ina 中。你可以使用的函數: inet_addr() ,它能夠把一個用數字和點表

示IP 地址的字符串轉換成一個無符號長整型。你可以像下面這樣使用它:

ina.sin_addr.s_addr = inet_addr(“166.111.69.52”);

注意:

l inet_addr() 返回的地址已經是網絡字節順序了,你沒有必要再去調用htonl() 函數

反過來,如果你有一個struct in_addr 並且你想把它代表的IP 地址打印出來(按照數字.數字.數字.數字的格式),那么你可以使用函數inet_ntoa()(“ntoa”代表“Network to ASCII”),它會把struct in_addr 里面存儲的網絡地址以數字.數字.數字.數字的格式。

l inet_ntoa() 使用struct in_addr 作為一個參數,不是一個長整型值。

4.基本套接字調用

socket() 函數

取得套接字描述符

 socket 函數的定義是下面這樣子的:

#include <sys/types.h>

#include <sys/socket.h>

int socket(int domain , int type , int protocol);

bind() 函數

bind()函數可以幫助你指定一個套接字使用的端口。

當你使用socket() 函數得到一個套接字描述符,你也許需要將socket 綁定上一個你的

機器上的端口。

l 當你需要進行端口監聽 listen()操作,等待接受一個連入請求的時候,一般都需要

經過這一步。比如網絡泥巴(MUD),Telnet a.b.c.d 4000。

l 如果你只是想進行連接一台服務器,也就是進行 connect() 操作的時候,這一步

並不是必須的。

bind()的系統調用聲明如下:

#include <sys/types.h>

#include <sys/socket.h>

int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;

參數說明:

l sockfd 是由socket()函數返回的套接字描述符。

l my_addr 是一個指向struct sockaddr 的指針,包含有關你的地址的信息:名稱、

端口和IP 地址。

l addrlen 可以設置為sizeof(struct sockaddr)。

connect()函數

讓我們花一點時間來假設你是一個Telnet 應用程序。你的使用者命令你建立一個套接

字描述符。你遵從命令,調用了socket()。然后,使用者告訴你連接到“166.111.69.52”

的23 端口(標准的Telnet 端口)⋯⋯你應該怎么做呢?

你很幸運:Telnet 應用程序,你現在正在閱讀的就是套接字的進行網絡連接部分:

connect()。

connect() 函數的定義是這樣的:

#include <sys/types.h>

#include <sys/socket.h>

int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);

connect()的三個參數意義如下:

l sockfd :套接字文件描述符,由socket()函數返回的。

l serv_addr 是一個存儲遠程計算機的IP 地址和端口信息的結構。

l addrlen 應該是sizeof(struct sockaddr)。

listen() 函數

listen()函數是等待別人連接,進行系統偵聽請求的函數。當有人連接你的時候,你有

兩步需要做:通過listen()函數等待連接請求,然后使用accept()函數來處理。(accept()函數

在下面介紹)。

listen()函數調用是非常簡單的。函數聲明如下:

#include <sys/socket.h>

int listen(int sockfd, int backlog);

listen()函數的參數意義如下:

l sockfd 是一個套接字描述符,由socket()系統調用獲得。

l backlog 是未經過處理的連接請求隊列可以容納的最大數目。

backlog 具體一些是什么意思呢?每一個連入請求都要進入一個連入請求隊列,等待

listen 的程序調用accept()(accept()函數下面有介紹)函數來接受這個連接。當系統還沒有

調用accept()函數的時候,如果有很多連接,那么本地能夠等待的最大數目就是backlog 的

數值。你可以將其設成5 到10 之間的數值

accept()函數

函數accept()有一些難懂。當調用它的時候,大致過程是下面這樣的:

l 有人從很遠很遠的地方嘗試調用 connect()來連接你的機器上的某個端口(當然是

你已經在listen()的)。

l 他的連接將被 listen 加入等待隊列等待accept()函數的調用(加入等待隊列的最多

數目由調用listen()函數的第二個參數backlog 來決定)。

l 你調用 accept()函數,告訴他你准備連接。

l accept()函數將回返回一個新的套接字描述符,這個描述符就代表了這個連接!

好,這時候你有了兩個套接字描述符,返回給你的那個就是和遠程計算機的連接,而

第一個套接字描述符仍然在你的機器上原來的那個端口上listen()。

這時候你所得到的那個新的套接字描述符就可以進行send()操作和recv()操作了。

下面是accept()函數的聲明:

#include <sys/socket.h>

int accept(int sockfd, void *addr, int *addrlen);

accept()函數的參數意義如下:

l sockfd 是正在listen() 的一個套接字描述符。

l addr 一般是一個指向struct sockaddr_in 結構的指針;里面存儲着遠程連接過來的

計算機的信息(比如遠程計算機的IP 地址和端口)

send()、recv()函數

這兩個函數是最基本的,通過有連接的套接字流進行通訊的函數。

send() 函數的聲明:

#include <sys/types.h>

#include <sys/socket.h>

int send(int sockfd, const void *msg, int len, int flags);

send 的參數含義如下:

l sockfd 是代表你與遠程程序連接的套接字描述符。

l msg 是一個指針,指向你想發送的信息的地址。

l len 是你想發送信息的長度。

l flags 發送標記。一般都設為0

函數recv()調用在許多方面都和send()很相似,下面是recv()函數的聲明:

#include <sys/types.h>

#include <sys/socket.h>

int recv(int sockfd, void *buf, int len, unsigned int flags);

recv()的參數含義如下:

l sockfd 是你要讀取數據的套接字描述符。

l buf 是一個指針,指向你能存儲數據的內存緩存區域。

l len 是緩存區的最大尺寸。

l flags 是recv() 函數的一個標志,一般都為0 (具體的其他數值和含義請參考recv()

的man pages)。

recv() 返回它所真正收到的數據的長度

sendto() 和recvfrom() 函數

這兩個函數是進行無連接的UDP 通訊時使用的。使用這兩個函數,則數據會在沒有

建立過任何連接的網絡上傳輸。因為數據報套接字無法對遠程主機進行連接,想想我們在

發送數據前需要知道些什么呢?

對了!是遠程主機的IP 地址和端口!

下面是sendto()函數和recvfrom()函數的聲明:

#include <sys/types.h>

#include <sys/socket.h>

int sendto(int sockfd, const void *msg, int len, unsigned int flags,

const struct sockaddr *to, int tolen);

和你所看到的一樣,這個函數和send()函數基本一致。

l sockfd 是代表你與遠程程序連接的套接字描述符。

l msg 是一個指針,指向你想發送的信息的地址。

l len 是你想發送信息的長度。

l flags 發送標記。一般都設為0。(你可以查看send 的man pages 來獲得其他的參

數值並且明白各個參數所代表的含義)

l to 是一個指向struct sockaddr 結構的指針,里面包含了遠程主機的IP 地址和端口

數據。

l tolen 只是指出了struct sockaddr 在內存中的大小sizeof(struct sockaddr)。

和send()一樣,sendto()返回它所真正發送的字節數(當然也和send()一樣,它所真正

發送的字節數可能小於你所給它的數據的字節數)。當它發生錯誤的時候,也是返回 –1 ,

同時全局變量errno 存儲了錯誤代碼。

同樣的,recv()函數和recvfrom()函數也基本一致。

recvfrom()的聲明為:

#include <sys/types.h>

- 156 - Linux網絡編程

#include <sys/socket.h>

int recvfrom(int sockfd, void *buf, int len, unsigned int flags

struct sockaddr *from, int *fromlen);

其參數含義如下:

l sockfd 是你要讀取數據的套接字描述符。

l buf 是一個指針,指向你能存儲數據的內存緩存區域。

l len 是緩存區的最大尺寸。

l flags 是recv() 函數的一個標志,一般都為0 (具體的其他數值和含義請參考recv()

的man pages)。

l from 是一個本地指針,指向一個struct sockaddr 的結構(里面存有源IP 地址和端

口數).

l fromlen 是一個指向一個int 型數據的指針,它的大小應該是sizeof ( struct

sockaddr).當函數返回的時候,formlen 指向的數據是form 指向的struct sockaddr 的實際

大小.

recvfrom() 返回它接收到的字節數,如果發生了錯誤,它就返回-1

close()和shutdown()函數

程序進行網絡傳輸完畢后,你需要關閉這個套接字描述符所表示的連接。實現這個非

常簡單,只需要使用標准的關閉文件的函數:close()。

使用方法:

close(sockfd);

執行close()之后,套接字將不會在允許進行讀操作和寫操作。任何有關對套接字描述

符進行讀和寫的操作都會接收到一個錯誤。

如果你想對網絡套接字的關閉進行進一步的操作的話,你可以使用函數shutdown()。

它允許你進行單向的關閉操作,或是全部禁止掉。

shutdown()的聲明為:

#include <sys/socket.h>

int shutdown(int sockfd, int how);

它的參數含義如下:

l sockfd 是一個你所想關閉的套接字描述符.

l how 可以取下面的值。0 表示不允許以后數據的接收操;1 表示不允許以后數據

的發送操作;2 表示和close()一樣,不允許以后的任何操作(包括接收,發送數據)

shutdown() 如果執行成功將返回0,如果在調用過程中發生了錯誤,它將返回–1,全

局變量errno 中存儲了錯誤代碼.

如果你在一個未連接的數據報套接字上使用shutdown() 函數(還記得可以對數據報套

接字UDP 進行connect()操作嗎?),它將什么也不做.

setsockopt() 和getsockopt() 函數

Linux 所提供的socket 庫含有一個錯誤(bug)。此錯誤表現為你不能為一個套接字重

新啟用同一個端口號,即使在你正常關閉該套接字以后。例如,比方說,你編寫一個服務

器在一個套接字上等待的程序.服務器打開套接字並在其上偵聽是沒有問題的。無論如何,

總有一些原因(不管是正常還是非正常的結束程序)使你的程序需要重新啟動。然而重啟

動后你就不能把它綁定在原來那個端口上了。從bind()系統調用返回的錯誤代碼總是報告

說你試圖連接的端口已經被別的進程所綁定。

問題就是Linux 內核在一個綁定套接字的進程結束后從不把端口標記為未用。在大多

數Linux/UNIX 系統中,端口可以被一個進程重復使用,甚至可以被其它進程使用。

在Linux 中繞開這個問題的辦法是,當套接字已經打開但尚未有連接的時候用

setsockopt()系統調用在其上設定選項(options)。setsockopt() 調用設置選項而getsockopt()

從給定的套接字取得選項。

這里是這些調用的語法:

#include<sys/types.h>

#include<sys/socket.h>

int getsockopt(int sockfd, int level, int name, char *value, int *optlen);

int setsockopt(int sockfd, int level, int name, char *value, int *optlen);

下面是兩個調用的參數說明:

l sockfd 必須是一個已打開的套接字。

l level 是函數所使用的協議標准(protocol level)(TCP/IP 協議使用IPPROTO_TCP,

套接字標准的選項實用SOL_SOCKET)。

l name 選項在套接字說明書中(man page)有詳細說明。

l value 指向為getsockopt()函數所獲取的值,setsockopt()函數所設置的值的地址。

l optlen 指針指向一個整數,該整數包含參數以字節計算的長度。

現在我們再回到Linux 的錯誤上來.當你打開一個套接字時必須同時用下面的代碼段

來調用setsockopt()函數:

/* 設定參數數值 */

opt = 1; len = sizeof(opt);

/* 設置套接字屬性 */

setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);

getpeername()函數

這個函數可以取得一個已經連接上的套接字的遠程信息(比如IP 地址和端口),告訴

你在遠程和你連接的究竟是誰.

它的聲明為:

#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

下面是參數說明:

l sockfd 是你想取得遠程信息的那個套接字描述符。

l addr 是一個指向struct sockaddr (或是struct sockaddr_in)的指針。

l addrlen 是一個指向int 的指針,應該賦於sizeof(struct sockaddr)的大小。

如果在函數執行過程中出現了錯誤,函數將返回 –1 ,並且錯誤代碼儲存在全局變量

errno 中。

當你擁有了遠程連接用戶的IP 地址,你就可以使用inet_ntoa() 或gethostbyaddr()來輸

出信息或是做進一步的處理。

gethostname()函數

gethostname()函數可以取得本地主機的信息.它比getpeername()要容易使用一些。

它返回正在執行它的計算機的名字。返回的這個名字可以被gethostbyname()函數使用,

由此可以得到本地主機的IP 地址。

下面是它的聲明:

#include <unistd.h>

int gethostname(char *hostname, size_t size);

參數說明如下:

l hostname 是一個指向字符數組的指針,當函數返回的時候,它里面的數據就是本

地的主機的名字.

l size 是hostname 指向的數組的長度.

函數如果成功執行,它返回0,如果出現錯誤,則返回–1,全局變量errno 中存儲着錯

誤代碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM