學習筆記之SOCKET網絡編程之二

1.sendto()和recvfrom()函數
Sendto()和recvfrom()函數用於在無連接的數據報套接字方式下進行數據發送和接收.

sendto()函數
定義:int sendto(int s,void *msg,int len,unsigned int flags,struct sockaddr *to, int tolen);
在發送數據時.由於本地端並沒有與遠程機器建立一個連接.所以在用sendto()發送數據時我們要告訴它把數據發送到哪裏?也就是告訴sendto()發送數據的目的地.
所以它比send()函數多了兩個參數.
參數 "struct sockaddr* to"用來告訴它發送數據目的地.包括目標機器的IP和端口等信息.
參數 "int tolen"是struct sockaddr結構的大小.
除此之外其它的參數與send()函數完全相同.
函數成功返回實際發送數據的長度.失敗返回-1.

recvfrom()函數
定義:int recvfrom(int s,void *buf,int len,unsigned int flags,sockaddr*from,int *fromlen);
因爲是利用數據報套接字傳送的數據.所以並沒有連接.所以recvfrom()在收到數據後需要知道數據到底是誰發來的?在這裏稱爲"源".所以它比recv()函數多了兩個參數.
參數from用來記錄源機器的地址信息.包括IP地址和端口等信息.
參數fromlen同樣是struct sockaddr結構的大小.
其它參數與recv()函數完全相同.
函數成功時返回實際收到的數據長度.失敗返回-1.

2.套接字的關閉.
當我們完成通訊後我們需要把套接字關閉.從而釋放資源.
關閉套接字的操作通過調用close()函數來實現.
不管是有連接的流式套接字還是沒有連接的數據包式的套接字.只要任何一端調用了close()函數關閉了套接字.都將無法再進行通訊.
另外還有一個函數就是shutdown().它允許你將一定方向上的通訊關閉.當然也可雙向通訊都關閉.
也就是說shutdown()函數可以讓某一端只接收數據.或者只發送數據.
定義:int shutdown(int s,int how);
參數s是我們將要操作的套接字.
參數how是具體的操作方式.
A.爲0時表示不允許接收數據
B.爲1時表示不允許發送數據
C.爲2時表示不允許接收和發送數據(雙向通訊都將關閉,與調用close()函數效果一樣.)
函數成功時返回0失敗時返回-1.

3.其它.
getpeername()函數.
功能:它用來在有連接的流式套接字中獲取對方的地址信息.(包括IP地址和端口)
定義:int getpeername(int s, struct sockaddr *addr, int *addrlen);
參數addr用來存放獲取的地址信息.
函數失敗返回-1.

gethostname()函數.
功能:它用來獲取本機的名字.
定義:int gethostname(char *hostname, size_t len);
參數hostname是一個字符類型的緩衝區.它用來保存本機名字
參數len是緩衝區的長度.
函數成功時返回0.失敗時返回-1.

gethostbyname(char*name)函數.
功能:返回由參數name指定的機器的地址信息
定義:struct hostent *gethostbyname(const char *name);
參數name可以是用gethostname()函數獲取的機器名字
函數成功返回一個指向struct hostent結構體的指針.失敗返回NULL

另:此函數返回的是一個struct hostent結構指針.該結構就包含了一個機器的地址信息
該結構的定義:
struct hostent
{
   char *h_name;                   //地址的正式名稱
   char **h_aliases;               //空字節 地址的預備名稱的指針
   int h_addrtype;                 //地址的類型.WINDOWS中通常爲AF_INET
   int h_length;                   //地址的長度.以位來計算(不是以字節來計算)
   char **h_addr_list;             //零字節 主機網絡地址指針.(網絡字節順序)
#define h_addr  h_addr_list[0]       //h_addr_list中的第一地址
};


4.關於阻塞與非阻塞.

阻塞.
簡單的說阻塞就是當你調用accept(),send()或者是recv()等過程後.程序必須要等待一個返回結果程序纔會繼續執行下去.在這種情況下,如果你的程序是單線程的.那麼你的程序就會完全掛起.

非阻塞.
而非阻塞正好相反.非阻塞套接字是指執行此套接字的網絡調用時,不管是否執行成功,都立即返回.

習慣上也稱之爲同步阻塞和異步非阻塞.


在默認情況下套接字都是阻塞模式的.如果要改變這個設置就要調用fcntl()函數.

fcntl()定義如下:
#include <fcntl.h>
int   fcntl(int  s,int  cmd,  .../* int arg */);
參數s是我們將要設置的套接字.
參數cmd有多種形式.在這裏一般設置爲F_SETFL.
第三個參數總是一個整數.在此處只需要設置成O_NONBLOCK(表示非阻塞模式)

注意:在windows下的socket編程中.是用iooctlsocket()函數來實現fcntl()函數的功能的.
它的定義如下:
int ioctlsocket(int s,long cmd,u_long *argp);
功能:控制套接字的模式.
參數s是將要操作的套接字.
參數cmd是對套接字的操作方式.
參數argp指向cmd命令所帶參數的指針.

參數cmd支持下列幾種命令:
A.FIONBIO允許或禁止套接字s的非阻塞模式.參數argp指向一個無符號長整型.如果要允許非阻塞模式則將參數argp設成非零.如果要禁止非阻塞模式(也就是阻塞模式)則將參數argp設成零即可.
注:一個套接字進行了WSAAsyncSelect()操作.它是套接字的一種模式,它將套接字自動設置成非阻塞模式.則任何用試圖ioctlsocket()來重新設置套接成阻塞模式的操作都將失敗.必須先調用WSAAsyncSelect()來禁止WSAAsyncSelect後纔可以.關於WSAAsyncSelect()請查閱其它資料.
B.FIONREAD確定套接字s自動讀入數據量.參數argp此時用來存放ioctlsocket()的返回值.如果套接字是SOCKET_STREAM類型(即流式套接字)則FIONREAD返回一次在recv()中所接收到的所有數據量.(即數據長度).這通常與套接字中排隊的數據總量相同.如果套接字是SOCK_DGRAM類型.則FIONREAD返回套接字上排隊的第一個數據報的大小.
還有一些其它的命令.略.

函數調用成功返回0.失敗返回-1.

從系統性能上看,用非阻塞的socket效率和性能更高,但是編程更復雜,特別是當你使用事件或者消息的時候,但是,你可以通過多個工作線程管理多個socket連接,效率非常高,不需要每個工作線程只管理一個socket連接. 用阻塞的方式比較簡單,但當較多客戶端時消耗系統資源太多.
其大概思想是建立一個線程池,當有socket的事件需要處理時,從線程池中取一個線程來執行,執行完,將線程歸還到線程池中.
每個socket連接的時間較長,不斷的與服務器交互.每個連接的socket並不是每時每刻都在收發數據.收發數據是斷斷續續的.這種情況就很適合於上面的方法.

簡單的講同步阻塞常用於連接較少流量較大的地方.非阻塞則剛好相反.用於連接較多.流量較小的地方.
如果要採用阻塞模式.並且要同時處理多個連接.就只能運用多線程來處理.

多路同步.
假設有一臺服務器.它一邊要進行監聽是否有連接到來,還要在其它已經完成的連接上接收數據.也就是accept()和recv()兩個函數的調用.當你在調用accept()時遇到阻塞該怎麼辦.前提是你並沒有調用
fcntl()函數把套接字設置非阻塞模式.這時你就可以調用select().它讓你可以同時監視多個套接字.它可以告訴你.哪個套接字準備讀,哪個套接字準寫,哪個套接字發生了意外.

select()函數的定義如下:
int select(int nums,fd_set*reads, fd_set*writes,fd_set *excepts,timeval *timeout);

在講解select()函數之前我們先講一下fd_set.
它表示一個集合.具體的講是一個文件描述符的集合.在此例中都把它理解爲套接字描述符.
在Unix中一切都是文件.包括輸入設備鍵盤.硬盤上的文件.還有專門用於網絡傳輸的socket(套接字)等等.所以.在這裏都把它當作是socket套接字.
那麼文件描述符或者是套接字描述符就好比是WINDOWS下面的句柄.

fd_set的定義如下:
struct fd_set
{
        u_int   fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
};

對這些集合的操作都是通過一些特定的宏來完成的.
    FD_ZERO(fd_set *set)          //清除一個文件描述符集合
      FD_SET(int fd, fd_set *set)   //把fd添加到集合set中
      FD_CLR(int fd, fd_set *set)   //從集合set中移去fd
      FD_ISSET(int fd, fd_set *set) //測試fd是否在集合set中

現在再來說說select()函數,以及它的用法.
該函數的功能就是用來等待套接字(這裏只討論套接字)狀態的改變.
參數nums代表最大的套接字描述符加1.
參數reads,writes和excepts是套接字描述符集合,用來回傳該套接字描述符的讀,寫或例外的狀況.
參數timeout指向一個timeval結構.它用來設定select等待的時間.可以設置到微秒級.1秒等於1000毫秒.1毫秒等於1000微秒.雖然可以精確到1微秒.但這只是理想的.實際上跟系統還有硬件有關.
timeval結構的定義如下:
struct timeval
{
   int tv_sec;   /* 秒 */
   int tv_usec;  /* 微秒 */
};

當然你可以將參數timeout設置成NULL,那麼它將永遠不會超時.
成功返回套接字描述狀態改變的個數,如果返回0表示在套接字描述符狀態改變前已經超過參數timeout設定的時間.當有錯誤發生時則返回-1.此時參數reads,writes,execepts和timeout的值變得不可預測.
同時有錯誤發生時,它會設置全局錯誤變量.常見錯誤有下面幾種.

EBADF 套接字描述符無效或該套接字已關閉
EINTR 此調用被信號所中斷
EINVAL 參數n 爲負值
ENOMEM 核心內存不足

select()函數的常見用法.

//============
    ......
sockfd = socket(AF_INET, SOCK_STREAM, 0); //獲取一個套接字
         ......
fs_set readset; //定義套接字集合,用於查看讀狀態
FD_ZERO(&readset); //對該集合進行清除操作.
timeval  out; //創建一個timeval結構
out.tv_sec=5; //設定等待秒數爲5秒
out.tv_usec=1000*500; //設定等毫秒數爲500毫秒
//總的等待時間爲5秒+500毫秒.
select(sockfd+1,&readset,NULL,NULL,&out); //select()函數的調用.
if(FD_ISSET(sockfd,readset))//進行判斷
    ......
//=============



5.socket編程的最後一個問題
這也是socket編程中的第一個問題.如果你是在windows下面編程的話.

在windows下面進行socket編程我們首先要對socket進行初始化.這時就要用到WSAStartup()函數.
在windows中要調用所有的socket函數之前.我們必須先調用WSAStartup()函數
其定義如下:
int WSAStartup(WORD wVer,WSADATA* lpWSAData);
參數wVer指明可使用的windows socket的版本號.高位字節標明副版本號.低位字字指明主版本號.
參數lpWSAData是指向WSADATA結構的指針.用來接收windows socket實現的細節.
成功時返回0失敗返回錯誤代碼.

這裏將用MAKEWORD()宏.

MAKEWORD(a,b)它的作用是把a和b組成一個WORD值.
其中第一個參數a是高8位的值.第二個參數b是低8位的值.
我們就是用它來把windows socket的版本號放入WSAStartup()函數的.

例程:
//=====================
WORD sver=MAKEWORD(1,1);//設置socket版本號爲1.1
WSADATA wsda;//創建WSADATA結構
if(WSAStartup(sver,&wsda)==0)//調用WSAStartup函數初始化socket
{
    //調用成功.初始化成功
}
//=====================

另外一個函數.它是windows socket編程中的最後一個問題.
WSACleanup()函數.它是終止windows socket的調用.註銷windows socket.釋放資源.
任何打開的並已建立連接的SOCK_STREAM流式套接字.在調用WSACleanup()時會重置.而已經由close()關閉卻仍有要發送而未發送的數據的套接字則不會受影響.該數據仍要發送.
通常.WSACleanup()函數與WSAStartup()函數應成對出現.也就是說對於一個進程的每一次WSAStartup()調用.必須有一個WSACleanup()調用.
但是隻有最後一個WSACleanup()調用纔會做實際的清除工作.前面的WSACleanup()調用僅僅將windows socket DLL中的內置引用計數遞減1.
一般情況下,爲了確保WSACleanup()調用了足夠的次數.可以在最後用一個循環來不斷的調用WSACleanup()函數.直到返回WSANOTINITIALISED爲止.
該函數的定義:
int WSACleanup ( void );
函數成功返回0.失敗返回-1.同時會設置全局錯誤變量.
常見的幾種錯誤代碼.
A.WSANOTINITIALISED使用本函數之前沒有一次成功的WSAStartup()調用.簡單的講.它就是說.前面已經沒有WSAStartup()調用了.在這裏可以不需要再調用WSACleanup()函數來釋放資源了.
B.WSAEINPROGRESS一個阻塞的windows socket操作正在進行.簡單的講.它就是說你正準備註銷的這個套接字中某一個操作(比如recv())遇到了阻塞.


<全文完>


 來自:http://blog.sina.com.cn/s/blog_56ea069101000bxm.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章