linuxsocket
㈠ linux下Socket編程 怎樣實現客戶端之間互相通信
網路的Socket數據傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具有一個類似於打開文件的函數調用Socket(),該函數返回一個整型的Socket描述符,隨後的連接建立、數據傳輸等操作都是通過該Socket實現的。
下面用Socket實現一個windows下的c語言socket通信例子,這里我們客戶端傳遞一個字元串,伺服器端進行接收。
【伺服器端】
#include"stdafx.h"
#include<stdio.h>
#include<winsock2.h>
#include<winsock2.h>
#defineSERVER_PORT5208//偵聽埠
voidmain()
{
WORDwVersionRequested;
WSADATAwsaData;
intret,nLeft,length;
SOCKETsListen,sServer;//偵聽套接字,連接套接字
structsockaddr_insaServer,saClient;//地址信息
char*ptr;//用於遍歷信息的指針
//WinSock初始化
wVersionRequested=MAKEWORD(2,2);//希望使用的WinSockDLL的版本
ret=WSAStartup(wVersionRequested,&wsaData);
if(ret!=0)
{
printf("WSAStartup()failed! ");
return;
}
//創建Socket,使用TCP協議
sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sListen==INVALID_SOCKET)
{
WSACleanup();
printf("socket()faild! ");
return;
}
//構建本地地址信息
saServer.sin_family=AF_INET;//地址家族
saServer.sin_port=htons(SERVER_PORT);//注意轉化為網路位元組序
saServer.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//使用INADDR_ANY指示任意地址
//綁定
ret=bind(sListen,(structsockaddr*)&saServer,sizeof(saServer));
if(ret==SOCKET_ERROR)
{
printf("bind()faild!code:%d ",WSAGetLastError());
closesocket(sListen);//關閉套接字
WSACleanup();
return;
}
//偵聽連接請求
ret=listen(sListen,5);
if(ret==SOCKET_ERROR)
{
printf("listen()faild!code:%d ",WSAGetLastError());
closesocket(sListen);//關閉套接字
return;
}
printf("Waitingforclientconnecting! ");
printf("Tips:Ctrl+ctoquit! ");
//阻塞等待接受客戶端連接
while(1)//循環監聽客戶端,永遠不停止,所以,在本項目中,我們沒有心跳包。
{
length=sizeof(saClient);
sServer=accept(sListen,(structsockaddr*)&saClient,&length);
if(sServer==INVALID_SOCKET)
{
printf("accept()faild!code:%d ",WSAGetLastError());
closesocket(sListen);//關閉套接字
WSACleanup();
return;
}
charreceiveMessage[5000];
nLeft=sizeof(receiveMessage);
ptr=(char*)&receiveMessage;
while(nLeft>0)
{
//接收數據
ret=recv(sServer,ptr,5000,0);
if(ret==SOCKET_ERROR)
{
printf("recv()failed! ");
return;
}
if(ret==0)//客戶端已經關閉連接
{
printf("Clienthasclosedtheconnection ");
break;
}
nLeft-=ret;
ptr+=ret;
}
printf("receivemessage:%s ",receiveMessage);//列印我們接收到的消息。
}
//closesocket(sListen);
//closesocket(sServer);
//WSACleanup();
}
【客戶端】
#include"stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#include<winsock2.h>
#defineSERVER_PORT5208//偵聽埠
voidmain()
{
WORDwVersionRequested;
WSADATAwsaData;
intret;
SOCKETsClient;//連接套接字
structsockaddr_insaServer;//地址信息
char*ptr;
BOOLfSuccess=TRUE;
//WinSock初始化
wVersionRequested=MAKEWORD(2,2);//希望使用的WinSockDLL的版本
ret=WSAStartup(wVersionRequested,&wsaData);
if(ret!=0)
{
printf("WSAStartup()failed! ");
return;
}
//確認WinSockDLL支持版本2.2
if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2)
{
WSACleanup();
printf("InvalidWinSockversion! ");
return;
}
//創建Socket,使用TCP協議
sClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sClient==INVALID_SOCKET)
{
WSACleanup();
printf("socket()failed! ");
return;
}
//構建伺服器地址信息
saServer.sin_family=AF_INET;//地址家族
saServer.sin_port=htons(SERVER_PORT);//注意轉化為網路節序
saServer.sin_addr.S_un.S_addr=inet_addr("192.168.1.127");
//連接伺服器
ret=connect(sClient,(structsockaddr*)&saServer,sizeof(saServer));
if(ret==SOCKET_ERROR)
{
printf("connect()failed! ");
closesocket(sClient);//關閉套接字
WSACleanup();
return;
}
charsendMessage[]="hellothisisclientmessage!";
ret=send(sClient,(char*)&sendMessage,sizeof(sendMessage),0);
if(ret==SOCKET_ERROR)
{
printf("send()failed! ");
}
else
printf("clientinfohasbeensent!");
closesocket(sClient);//關閉套接字
WSACleanup();
}
㈡ linux下 socket函數的返回值代表什麼
int socket;domain指明所使用的協議族,通常為PF_INET,表示互聯網協議族;type參數指定socket的類型:SOCK_STREAM 或SOCK_DGRAM,Socket介面還定義了原始Socket,允許程序使用低層協議;protocol通常賦值"0"。
Socket()調用返回一個整型socket描述符,你可以在後面的調用使用它。 Socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。
調用Socket函數時,socket執行體將建立一個Socket,實際上"建立一個Socket"意味著為一個Socket數據結構分配存儲空間。 Socket執行體為你管理描述符表。
(2)linuxsocket擴展閱讀:
支持下述類型描述:
SOCK_STREAM 提供有序的、可靠的、雙向的和基於連接的位元組流,使用帶外數據傳送機制,為Internet地址族使用TCP。
SOCK_DGRAM 支持無連接的、不可靠的和使用固定大小(通常很小)緩沖區的數據報服務,為Internet地址族使用UDP。
SOCK_STREAM類型的套介面為全雙向的位元組流。對於流類套介面,在接收或發送數據前必需處於已連接狀態。用connect()調用建立與另一套介面的連接,連接成功後,即可用send()和recv()傳送數據。當會話結束後,調用close()。帶外數據根據規定用send()和recv()來接收。
㈢ 請問linux怎麼增大socket連接上限
1、修改用戶進程可打開文件數限制
在Linux平台上,無論編寫客戶端程序還是服務端程序,在進行高並發TCP連接處理時,
最高的並發數量都要受到系統對用戶單一進程同時可打開文件數量的限制(這是因為系統
為每個TCP連接都要創建一個socket句柄,每個socket句柄同時也是一個文件句柄)。
可使用ulimit命令查看系統允許當前用戶進程打開的文件數限制:
[speng@as4 ~]$ ulimit -n
1024
這表示當前用戶的每個進程最多允許同時打開1024個文件,這1024個文件中還得除去
每個進程必然打開的標准輸入,標准輸出,標准錯誤,伺服器監聽 socket,
進程間通訊的unix域socket等文件,那麼剩下的可用於客戶端socket連接的文件數就
只有大概1024-10=1014個左右。也就是說預設情況下,基於Linux的通訊程序最多允許
同時1014個TCP並發連接。
對於想支持更高數量的TCP並發連接的通訊處理程序,就必須修改Linux對當前用戶的
進程同時打開的文件數量的軟限制(soft limit)和硬限制(hardlimit)。其中軟限制
是指Linux在當前系統能夠承受的范圍內進一步限制用戶同時打開的文件數;硬限制
則是根據系統硬體資源狀況(主要是系統內存)計算出來的系統最多可同時打開的文件數量。
通常軟限制小於或等於硬限制。
修改上述限制的最簡單的辦法就是使用ulimit命令:
[speng@as4 ~]$ ulimit -n
上述命令中,在中指定要設置的單一進程允許打開的最大文件數。如果系統回顯
類似於「Operation notpermitted」之類的話,說明上述限制修改失敗,實際上是
因為在中指定的數值超過了Linux系統對該用戶打開文件數的軟限制或硬限制。
因此,就需要修改Linux系統對用戶的關於打開文件數的軟限制和硬限制。
第一步,修改/etc/security/limits.conf文件,在文件中添加如下行:
speng soft nofile 10240
speng hard nofile 10240
其中speng指定了要修改哪個用戶的打開文件數限制,可用』*'號表示修改所有用戶的限制;
soft或hard指定要修改軟限制還是硬限制;10240則指定了想要修改的新的限制值,
即最大打開文件數(請注意軟限制值要小於或等於硬限制)。修改完後保存文件。
第二步,修改/etc/pam.d/login文件,在文件中添加如下行:
session required /lib/security/pam_limits.so
這是告訴Linux在用戶完成系統登錄後,應該調用pam_limits.so模塊來設置系統對
該用戶可使用的各種資源數量的最大限制(包括用戶可打開的最大文件數限制),
而pam_limits.so模塊就會從/etc/security/limits.conf文件中讀取配置來設置這些限制值。
修改完後保存此文件。
第三步,查看Linux系統級的最大打開文件數限制,使用如下命令:
[speng@as4 ~]$ cat /proc/sys/fs/file-max
12158
這表明這台Linux系統最多允許同時打開(即包含所有用戶打開文件數總和)12158個文件,
是Linux系統級硬限制,所有用戶級的打開文件數限制都不應超過這個數值。通常這個系統級
硬限制是Linux系統在啟動時根據系統硬體資源狀況計算出來的最佳的最大同時打開文件數限制,
如果沒有特殊需要,不應該修改此限制,除非想為用戶級打開文件數限制設置超過此限制的值。
修改此硬限制的方法是修改/etc/rc.local腳本,在腳本中添加如下行:
echo 22158 > /proc/sys/fs/file-max
這是讓Linux在啟動完成後強行將系統級打開文件數硬限制設置為22158。修改完後保存此文件。
完成上述步驟後重啟系統,一般情況下就可以將Linux系統對指定用戶的單一進程允許同時
打開的最大文件數限制設為指定的數值。如果重啟後用 ulimit-n命令查看用戶可打開文件數限制
仍然低於上述步驟中設置的最大值,這可能是因為在用戶登錄腳本/etc/profile中使用ulimit -n命令
已經將用戶可同時打開的文件數做了限制。由於通過ulimit-n修改系統對用戶可同時打開文件的
最大數限制時,新修改的值只能小於或等於上次 ulimit-n設置的值,因此想用此命令增大這個
限制值是不可能的。
所以,如果有上述問題存在,就只能去打開/etc/profile腳本文件,
在文件中查找是否使用了ulimit-n限制了用戶可同時打開的最大文件數量,如果找到,
則刪除這行命令,或者將其設置的值改為合適的值,然後保存文件,用戶退出並重新登錄系統即可。
通過上述步驟,就為支持高並發TCP連接處理的通訊處理程序解除關於打開文件數量方面的系統限制。
2、修改網路內核對TCP連接的有關限制
在Linux上編寫支持高並發TCP連接的客戶端通訊處理程序時,有時會發現盡管已經解除了系統
對用戶同時打開文件數的限制,但仍會出現並發TCP連接數增加到一定數量時,再也無法成功
建立新的TCP連接的現象。出現這種現在的原因有多種。
第一種原因可能是因為Linux網路內核對本地埠號范圍有限制。此時,進一步分析為什麼無法
建立TCP連接,會發現問題出在connect()調用返回失敗,查看系統錯誤提示消息是「Can』t assign requestedaddress」。同時,如果在此時用tcpmp工具監視網路,會發現根本沒有TCP連接時客戶端
發SYN包的網路流量。這些情況說明問題在於本地Linux系統內核中有限制。
其實,問題的根本原因
在於Linux內核的TCP/IP協議實現模塊對系統中所有的客戶端TCP連接對應的本地埠號的范圍
進行了限制(例如,內核限制本地埠號的范圍為1024~32768之間)。當系統中某一時刻同時
存在太多的TCP客戶端連接時,由於每個TCP客戶端連接都要佔用一個唯一的本地埠號
(此埠號在系統的本地埠號范圍限制中),如果現有的TCP客戶端連接已將所有的本地埠號占滿,
則此時就無法為新的TCP客戶端連接分配一個本地埠號了,因此系統會在這種情況下在connect()
調用中返回失敗,並將錯誤提示消息設為「Can』t assignrequested address」。
有關這些控制
邏輯可以查看Linux內核源代碼,以linux2.6內核為例,可以查看tcp_ipv4.c文件中如下函數:
static int tcp_v4_hash_connect(struct sock *sk)
請注意上述函數中對變數sysctl_local_port_range的訪問控制。變數sysctl_local_port_range
的初始化則是在tcp.c文件中的如下函數中設置:
void __init tcp_init(void)
內核編譯時默認設置的本地埠號范圍可能太小,因此需要修改此本地埠范圍限制。
第一步,修改/etc/sysctl.conf文件,在文件中添加如下行:
net.ipv4.ip_local_port_range = 1024 65000
這表明將系統對本地埠范圍限制設置為1024~65000之間。請注意,本地埠范圍的最小值
必須大於或等於1024;而埠范圍的最大值則應小於或等於65535。修改完後保存此文件。
第二步,執行sysctl命令:
[speng@as4 ~]$ sysctl -p
如果系統沒有錯誤提示,就表明新的本地埠范圍設置成功。如果按上述埠范圍進行設置,
則理論上單獨一個進程最多可以同時建立60000多個TCP客戶端連接。
第二種無法建立TCP連接的原因可能是因為Linux網路內核的IP_TABLE防火牆對最大跟蹤的TCP
連接數有限制。此時程序會表現為在 connect()調用中阻塞,如同死機,如果用tcpmp工具監視網路,
也會發現根本沒有TCP連接時客戶端發SYN包的網路流量。由於 IP_TABLE防火牆在內核中會對
每個TCP連接的狀態進行跟蹤,跟蹤信息將會放在位於內核內存中的conntrackdatabase中,
這個資料庫的大小有限,當系統中存在過多的TCP連接時,資料庫容量不足,IP_TABLE無法為
新的TCP連接建立跟蹤信息,於是表現為在connect()調用中阻塞。此時就必須修改內核對最大跟蹤
的TCP連接數的限制,方法同修改內核對本地埠號范圍的限制是類似的:
第一步,修改/etc/sysctl.conf文件,在文件中添加如下行:
net.ipv4.ip_conntrack_max = 10240
這表明將系統對最大跟蹤的TCP連接數限制設置為10240。請注意,此限制值要盡量小,
以節省對內核內存的佔用。
第二步,執行sysctl命令:
[speng@as4 ~]$ sysctl -p
如果系統沒有錯誤提示,就表明系統對新的最大跟蹤的TCP連接數限制修改成功。
如果按上述參數進行設置,則理論上單獨一個進程最多可以同時建立10000多個TCP客戶端連接。
3、使用支持高並發網路I/O的編程技術
在Linux上編寫高並發TCP連接應用程序時,必須使用合適的網路I/O技術和I/O事件分派機制。
可用的I/O技術有同步I/O,非阻塞式同步I/O(也稱反應式I/O),以及非同步I/O。在高TCP並發的情形下,
如果使用同步I/O,這會嚴重阻塞程序的運轉,除非為每個TCP連接的I/O創建一個線程。
但是,過多的線程又會因系統對線程的調度造成巨大開銷。因此,在高TCP並發的情形下使用
同步 I/O是不可取的,這時可以考慮使用非阻塞式同步I/O或非同步I/O。非阻塞式同步I/O的技術包括使用select(),poll(),epoll等機制。非同步I/O的技術就是使用AIO。
從I/O事件分派機制來看,使用select()是不合適的,因為它所支持的並發連接數有限(通常在1024個以內)。
如果考慮性能,poll()也是不合適的,盡管它可以支持的較高的TCP並發數,但是由於其採用
「輪詢」機制,當並發數較高時,其運行效率相當低,並可能存在I/O事件分派不均,導致部分TCP
連接上的I/O出現「飢餓」現象。而如果使用epoll或AIO,則沒有上述問題(早期Linux內核的AIO技術
實現是通過在內核中為每個 I/O請求創建一個線程來實現的,這種實現機制在高並發TCP連接的情形下
使用其實也有嚴重的性能問題。但在最新的Linux內核中,AIO的實現已經得到改進)。
綜上所述,在開發支持高並發TCP連接的Linux應用程序時,應盡量使用epoll或AIO技術來實現並發的
TCP連接上的I/O控制,這將為提升程序對高並發TCP連接的支持提供有效的I/O保證。
內核參數sysctl.conf的優化
/etc/sysctl.conf 是用來控制linux網路的配置文件,對於依賴網路的程序(如web伺服器和cache伺服器)
非常重要,RHEL默認提供的最好調整。
推薦配置(把原/etc/sysctl.conf內容清掉,把下面內容復制進去):
net.ipv4.ip_local_port_range = 1024 65536
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_no_metrics_save=1
net.core.somaxconn = 262144
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
這個配置參考於cache伺服器varnish的推薦配置和SunOne 伺服器系統優化的推薦配置。
varnish調優推薦配置的地址為:http://varnish.projects.linpro.no/wiki/Performance
不過varnish推薦的配置是有問題的,實際運行表明「net.ipv4.tcp_fin_timeout = 3」的配置
會導致頁面經常打不開;並且當網友使用的是IE6瀏覽器時,訪問網站一段時間後,所有網頁都會
打不開,重啟瀏覽器後正常。可能是國外的網速快吧,我們國情決定需要
調整「net.ipv4.tcp_fin_timeout = 10」,在10s的情況下,一切正常(實際運行結論)。
修改完畢後,執行:
/sbin/sysctl -p /etc/sysctl.conf
/sbin/sysctl -w net.ipv4.route.flush=1
命令生效。為了保險起見,也可以reboot系統。
調整文件數:
linux系統優化完網路必須調高系統允許打開的文件數才能支持大的並發,默認1024是遠遠不夠的。
執行命令:
Shell代碼
echo ulimit -HSn 65536 >> /etc/rc.local
echo ulimit -HSn 65536 >>/root/.bash_profile
ulimit -HSn 65536
㈣ linux socket和sock結構體的區別
//**************************************************************************
/* 1、每一個打開的文件、socket等等都用一個file數據結構代表,這樣文件和socket就通過inode->u(union)中的各個成員來區別:
struct inode {
.....................
union {
struct ext2_inode_info ext2_i;
struct ext3_inode_info ext3_i;
struct socket socket_i;
.....................
} u; };
2、每個socket數據結構都有一個sock數據結構成員,sock是對socket的擴充,兩者一一對應,socket->sk指向對應的sock,sock->socket
指向對應的socket;
3、socket和sock是同一事物的兩個側面,為什麼不把兩個數據結構合並成一個呢?這是因為socket是inode結構中的一部分,即把inode結
構內部的一個union用作socket結構。由於插口操作的特殊性,這個數據結構中需要有大量的結構成分,如果把這些成分全部放到socket
結構中,則inode結構中的這個union就會變得很大,從而inode結構也會變得很大,而對於其他文件系統這個union是不需要這么大的,
所以會造成巨大浪費,系統中使用inode結構的數量要遠遠超過使用socket的數量,故解決的辦法就是把插口分成兩部分,把與文件系
統關系密切的放在socket結構中,把與通信關系密切的放在另一個單獨結構sock中;
*/
struct socket
{
socket_state state; // 該state用來表明該socket的當前狀態
typedef enum {
SS_FREE = 0, /* not allocated */
SS_UNCONNECTED, /* unconnected to any socket */
SS_CONNECTING, /* in process of connecting */
SS_CONNECTED, /* connected to socket */
SS_DISCONNECTING /* in process of disconnecting */
} socket_state;
unsigned long flags; //該成員可能的值如下,該標志用來設置socket是否正在忙碌
#define SOCK_ASYNC_NOSPACE 0
#define SOCK_ASYNC_WAITDATA 1
#define SOCK_NOSPACE 2
struct proto_ops *ops; //依據協議邦定到該socket上的特定的協議族的操作函數指針,例如IPv4 TCP就是inet_stream_ops
struct inode *inode; //表明該socket所屬的inode
struct fasync_struct *fasync_list; //非同步喚醒隊列
struct file *file; //file回指指針
struct sock *sk; //sock指針
wait_queue_head_t wait; //sock的等待隊列,在TCP需要等待時就sleep在這個隊列上
short type; //表示該socket在特定協議族下的類型例如SOCK_STREAM,
unsigned char passcred; //在TCP分析中無須考慮
};
㈤ Linux操作系統下Socket編程地址結構介紹
linux下的網路通信程序,一定要和一個結構打交道,這個結構就是socket
address。比如bind、connect等等函數都要使用socket
address結構。理解socket
address時我們要明白,其實在linux下針對於不同的socket
domain定義了一個通用的地址結構struct
sockaddr,它的具體定義為:
{unsigned
short
int
sa_family;char
sa_data[14];}
struct
sockaddr
其中,sa_family為調用socket()函數時的參數domain參數,sa_data為14個字元長度存儲。針對於不同domain下的socket,通用地址結構又對應了不同的定義,例如一般的AF_INET
domain下,socket
address的定義如下:
struct
sockaddr_in{unsigned
short
int
sin_family;uint16_t
sin_port;struct
in_addr
sin_addr;unsigned
char
sin_zero[8];//未使用}struct
in_addr{uint32_t
s_addr;}
當socket的domain不同於AF_INET時,具體的地址定義又是不同的,但是整個地址結構的大小、容量都是和通用地址結構一致的。
㈥ linux socket 連接超時 怎麼解決
今天發現自己的系統存在很嚴重缺陷,當前台關閉的時候後台就無法正常工作,原因很好定位,後台的socket連接超時時間過長,系統默認時間好像是75秒,於是找資料,根據下邊文章中的內容解決了,把超時時間設為5秒後,感覺好多了。看來還有好多東西需要慢慢挖掘阿!
如何設置socket的Connect超時(linux)
[From]http://dev.cbw.com/c/c/200510195601_4292587.shtml
1.首先將標志位設為Non-blocking模式,准備在非阻塞模式下調用connect函數
2.調用connect,正常情況下,因為TCP三次握手需要一些時間;而非阻塞調用只要不能立即完成就會返回錯誤,所以這里會返回EINPROGRESS,表示在建立連接但還沒有完成。
3.在讀套介面描述符集(fd_set rset)和寫套介面描述符集(fd_set wset)中將當前套介面置位(用FD_ZERO()、FD_SET()宏),並設置好超時時間(struct timeval *timeout)
4.調用select( socket, &rset, &wset, NULL, timeout )
返回0表示connect超時
如果你設置的超時時間大於75秒就沒有必要這樣做了,因為內核中對connect有超時限制就是75秒。
[From]http://www.ycgczj.com.cn/34733.html
網路編程中socket的分量我想大家都很清楚了,socket也就是套介面,在套介面編程中,提到超時的概念,我們一下子就能想到3個:發送超時,接收超時,以及select超時(註: select函數並不是只用於套介面的,但是套介面編程中用的比較多),在connect到目標主機的時候,這個超時是不由我們來設置的。不過正常情況下這個超時都很長,並且connect又是一個阻塞方法,一個主機不能連接,等著connect返回還能忍受,你的程序要是要試圖連接多個主機,恐怕遇到多個不能連接的主機的時候,會塞得你受不了的。我也廢話少說,先說說我的方法,如果你覺得你已掌握這種方法,你就不用再看下去了,如果你還不了解,我願意與你分享。本文是已在Linux下的程序為例子,不過拿到Windows中方法也是一樣,無非是換幾個函數名字罷了。
Linux中要給connect設置超時,應該是有兩種方法的。一種是該系統的一些參數,這個方法我不講,因為我講不清楚:P,它也不是編程實現的。另外一種方法就是變相的實現connect的超時,我要講的就是這個方法,原理上是這樣的:
1.建立socket
2.將該socket設置為非阻塞模式
3.調用connect()
4.使用select()檢查該socket描述符是否可寫(注意,是可寫)
5.根據select()返回的結果判斷connect()結果
6.將socket設置為阻塞模式(如果你的程序不需要用阻塞模式的,這步就省了,不過一般情況下都是用阻塞模式的,這樣也容易管理)
如果你對網路編程很熟悉的話,其實我一說出這個過程你就知道怎麼寫你的程序了,下面給出我寫的一段程序,僅供參考。
/******************************
* Time out for connect()
* Write by Kerl W
******************************/
#include <sys/socket.h>
#include <sys/types.h>
#define TIME_OUT_TIME 20 //connect超時時間20秒
int main(int argc , char **argv)
{
………………
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) exit(1);
struct sockaddr_in serv_addr;
………//以伺服器地址填充結構serv_addr
int error=-1, len;
len = sizeof(int);
timeval tm;
fd_set set;
unsigned long ul = 1;
ioctl(sockfd, FIONBIO, &ul); //設置為非阻塞模式
bool ret = false;
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
{
tm.tv_set = TIME_OUT_TIME;
tm.tv_uset = 0;
FD_ZERO(&set);
FD_SET(sockfd, &set);
if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
{
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
if(error == 0) ret = true;
else ret = false;
} else ret = false;
}
else ret = true;
ul = 0;
ioctl(sockfd, FIONBIO, &ul); //設置為阻塞模式
if(!ret)
{
close( sockfd );
fprintf(stderr , "Cannot Connect the server!n");
return;
}
fprintf( stderr , "Connected!n");
//下面還可以進行發包收包操作
……………
}
以上代碼片段,僅供參考,也是為初學者提供一些提示,主要用到的幾個函數,select, ioctl, getsockopt都可以找到相關資料,具體用法我這里就不贅述了,你只需要在linux中輕輕的敲一個man <函數名>就能夠看到它的用法。
此外我需要說明的幾點是,雖然我們用ioctl把套介面設置為非阻塞模式,不過select本身是阻塞的,阻塞的時間就是其超時的時間由調用select 的時候的最後一個參數timeval類型的變數指針指向的timeval結構變數來決定的,timeval結構由一個表示秒數的和一個表示微秒數(long類型)的成員組成,一般我們設置了秒數就行了,把微妙數設為0(註:1秒等於100萬微秒)。而select函數另一個值得一提的參數就是上面我們用到的fd_set類型的變數指針。調用之前,這個變數裡面存了要用select來檢查的描述符,調用之後,針對上面的程序這裡面是可寫的描述符,我們可以用宏FD_ISSET來檢查某個描述符是否在其中。由於我這里只有一個套介面描述符,我就沒有使用FD_ISSET宏來檢查調用select之後這個sockfd是否在set裡面,其實是需要加上這個判斷的。不過我用了getsockopt來檢查,這樣才可以判斷出這個套介面是否是真的連接上了,因為我們只是變相的用select來檢查它是否連接上了,實際上select檢查的是它是否可寫,而對於可寫,是針對以下三種條件任一條件滿足時都表示可寫的:
1)套介面發送緩沖區中的可用控制項位元組數大於等於套介面發送緩沖區低潮限度的當前值,且或者i)套介面已連接,或者ii)套介面不要求連接(UDP方式的)
2)連接的寫這一半關閉。
3)有一個套介面錯誤待處理。
這樣,我們就需要用getsockopt函數來獲取套介面目前的一些信息來判斷是否真的是連接上了,沒有連接上的時候還能給出發生了什麼錯誤,當然我程序中並沒有標出那麼多狀態,只是簡單的表示可連接/不可連接。
下面我來談談對這個程序測試的結果。我針對3種情形做了測試:
1. 目標機器網路正常的情況
可以連接到目標主機,並能成功以阻塞方式進行發包收包作業。
2. 目標機器網路斷開的情況
在等待設置的超時時間(上面的程序中為20秒)後,顯示目標主機不能連接。
3. 程序運行前斷開目標機器網路,超時時間內,恢復目標機器的網路
在恢復目標主機網路連接之前,程序一隻等待,恢復目標主機後,程序顯示連接目標主機成功,並能成功以阻塞方式進行發包收包作業。
以上各種情況的測試結果表明,這種設置connect超時的方法是完全可行的。我自己是把這種設置了超時的connect封裝到了自己的類庫,用在一套監控系統中,到目前為止,運行還算正常。這種編程實現的connect超時比起修改系統參數的那種方法的有點就在於它只用於你的程序之中而不影響系統。
㈦ linux socket是什麼意思
基於Linux的SOCKET編程。
㈧ linux中socket是如何調用驅動程序
分為發送和接受:
發送:
首先,socketAPI會創建並把數據至一個叫sk_buff的結構體,然後依次把sk_buff交給運輸層,網路層,數據鏈路層協議進行處理,然後在填寫完sk_buff後再把他交付給驅動程序由網路設備發送出去。
接受和發送是反過程,驅動層程序一般由中斷處理收到數據包後會創建sk_buff結構體,讓後把數據和一些控制信息填進去,再把sk_buff向數據鏈路層協議交付,然後就是網路層,運輸層最後交給socketAPI介面了
㈨ LINUX網路編程中socket函數詳細解釋下吧
函數原型:int socket(int family,int type,int protocol); 參數解釋:family指定協議簇,UNIX下(包括linux)有:AF_INET,AF_INET6,AF_LOCAL,AF_ROUTE,AF_KEY,分別是IPv4,IPv6協議,UNIX域協議,路由套介面,密鑰套介面; type指定套介面類型:unix下有四種,分別是:SOCK_STREAM,SOCK_DGRAM,SOCK_SEQPACKET,SOCK_RAW。(流套接字,數據報套接字,有序分組套接字,原始套接字),SOCK_SEQPACKET已很少使用; protocol:IPPROTO_TCP,IPPROTO_UDP,IPPROTO_SCTP,分別是TCP傳輸協議,UDP傳輸協議,SCTP傳輸協議; 函數返回一個非負整數值,類似於文件描述符(linux內核知識),稱之為套介面描述字,簡稱套接字。《Linux就該這么學》這本書,希望你能感受到linux系統和這本書帶給你的好處及幫助。
㈩ linux socket 怎麼處理大量的數據
1、 引言
Linux的興起可以說是Internet創造的一個奇跡。Linux作為一個完全開放其原代碼的免費的自由軟體,兼容了各種UNIX標准(如POSIX、UNIX System V 和 BSD UNIX 等)的多用戶、多任務的具有復雜內核的操作系統。在中國,隨著Internet的普及,一批主要以高等院校的學生和ISP的技術人員組成的Linux愛好者隊伍已經蓬勃成長起來。越來越多的編程愛好者也逐漸酷愛上這個優秀的自由軟體。本文介紹了Linux下Socket的基本概念和函數調用。
2、 什麼是Socket
Socket(套接字)是通過標準的UNIX文件描述符和其它程序通訊的一個方法。每一個套接字都用一個半相關描述:{協議,本地地址、本地埠}來表示;一個完整的套接字則用一個相關描述:{協議,本地地址、本地埠、遠程地址、遠程埠},每一個套接字都有一個本地的由操作系統分配的唯一的套接字型大小。
3、 Socket的三種類型
(1) 流式Socket(SOCK_STREAM)
流式套接字提供可靠的、面向連接的通信流;它使用TCP協議,從而保證了數據傳輸的正確性和順序的。
(2) 數據報Socket(SOCK_DGRAM)
數據報套接字定義了一種無連接的服務,數據通過相互獨立的報文進行傳輸,是無序的,並且不保證可靠、無差錯。它使用數據報協議UDP
(3) 原始Socket
原始套接字允許對底層協議如IP或ICMP直接訪問,它功能強大但使用較為不便,主要用於一些協議的開發。
4、 利用套接字發送數據
1、 對於流式套接字用系統調用send()來發送數據。
2、 對於數據報套接字,則需要自己先加一個信息頭,然後調用sendto()函數把數據發送出去。
5、 Linux中Socket的數據結構
(1) struct sockaddr { //用於存儲套接字地址
unsigned short sa_family;//地址類型
char sa_data[14]; //14位元組的協議地址
};
(2) struct sockaddr_in{ //in 代表internet
short int sin_family; //internet協議族
unsigned short int sin_port;//埠號,必須是網路位元組順序
struct in_addr sin_addr;//internet地址,必須是網路位元組順序
unsigned char sin_zero;//添0(和struct sockaddr一樣大小
};
(3) struct in_addr{
unsigned long s_addr;
};
6、 網路位元組順序及其轉換函數
(1) 網路位元組順序
每一台機器內部對變數的位元組存儲順序不同,而網路傳輸的數據是一定要統一順序的。所以對內部位元組表示順序與網路位元組順序不同的機器,一定要對數據進行轉換,從程序的可移植性要求來講,就算本機的內部位元組表示順序與網路位元組順序相同也應該在傳輸數據以前先調用數據轉換函數,以便程序移植到其它機器上後能正確執行。真正轉換還是不轉換是由系統函數自己來決定的。
(2) 有關的轉換函數
* unsigned short int htons(unsigned short int hostshort):
主機位元組順序轉換成網路位元組順序,對無符號短型進行操作4bytes
* unsigned long int htonl(unsigned long int hostlong):
主機位元組順序轉換成網路位元組順序,對無符號長型進行操作8bytes
* unsigned short int ntohs(unsigned short int netshort):
網路位元組順序轉換成主機位元組順序,對無符號短型進行操作4bytes
* unsigned long int ntohl(unsigned long int netlong):
網路位元組順序轉換成主機位元組順序,對無符號長型進行操作8bytes
註:以上函數原型定義在netinet/in.h里
7、 IP地址轉換
有三個函數將數字點形式表示的字元串IP地址與32位網路位元組順序的二進制形式的IP地址進行轉換
(1) unsigned long int inet_addr(const char * cp):該函數把一個用數字和點表示的IP地址的字元串轉換成一個無符號長整型,如:struct sockaddr_in ina
ina.sin_addr.s_addr=inet_addr("202.206.17.101")
該函數成功時:返回轉換結果;失敗時返回常量INADDR_NONE,該常量=-1,二進制的無符號整數-1相當於255.255.255.255,這是一個廣播地址,所以在程序中調用iner_addr()時,一定要人為地對調用失敗進行處理。由於該函數不能處理廣播地址,所以在程序中應該使用函數inet_aton()。
(2)int inet_aton(const char * cp,struct in_addr * inp):此函數將字元串形式的IP地址轉換成二進制形式的IP地址;成功時返回1,否則返回0,轉換後的IP地址存儲在參數inp中。
(3) char * inet_ntoa(struct in-addr in):將32位二進制形式的IP地址轉換為數字點形式的IP地址,結果在函數返回值中返回,返回的是一個指向字元串的指針。
8、 位元組處理函數
Socket地址是多位元組數據,不是以空字元結尾的,這和C語言中的字元串是不同的。Linux提供了兩組函數來處理多位元組數據,一組以b(byte)開頭,是和BSD系統兼容的函數,另一組以mem(內存)開頭,是ANSI C提供的函數。
以b開頭的函數有:
(1) void bzero(void * s,int n):將參數s指定的內存的前n個位元組設置為0,通常它用來將套接字地址清0。
(2) void b(const void * src,void * dest,int n):從參數src指定的內存區域拷貝指定數目的位元組內容到參數dest指定的內存區域。
(3) int bcmp(const void * s1,const void * s2,int n):比較參數s1指定的內存區域和參數s2指定的內存區域的前n個位元組內容,如果相同則返回0,否則返回非0。
註:以上函數的原型定義在strings.h中。
以mem開頭的函數有:
(1) void * memset(void * s,int c,size_t n):將參數s指定的內存區域的前n個位元組設置為參數c的內容。
(2) void * memcpy(void * dest,const void * src,size_t n):功能同b(),區別:函數b()能處理參數src和參數dest所指定的區域有重疊的情況,memcpy()則不能。
(4) int memcmp(const void * s1,const void * s2,size_t n):比較參數s1和參數s2指定區域的前n個位元組內容,如果相同則返回0,否則返回非0。
註:以上函數的原型定義在string.h中。
9、 基本套接字函數
(1) socket()
#include< sys/types.h>
#include< sys/socket.h>
int socket(int domain,int type,int protocol)
參數domain指定要創建的套接字的協議族,可以是如下值:
AF_UNIX //UNIX域協議族,本機的進程間通訊時使用
AF_INET //Internet協議族(TCP/IP)
AF_ISO //ISO協議族
參數type指定套接字類型,可以是如下值:
SOCK_STREAM //流套接字,面向連接的和可靠的通信類型
SOCK_DGRAM //數據報套接字,非面向連接的和不可靠的通信類型
SOCK_RAW //原始套接字,只對Internet協議有效,可以用來直接訪問IP協議
參數protocol通常設置成0,表示使用默認協議,如Internet協議族的流套接字使用TCP協議,而數據報套接字使用UDP協議。當套接字是原始套接字類型時,需要指定參數protocol,因為原始套接字對多種協議有效,如ICMP和IGMP等。
Linux系統中創建一個套接字的操作主要是:在內核中創建一個套接字數據結構,然後返回一個套接字描述符標識這個套接字數據結構。這個套接字數據結構包含連接的各種信息,如對方地址、TCP狀態以及發送和接收緩沖區等等,TCP協議根據這個套接字數據結構的內容來控制這條連接。
(2) 函數connect()
#include< sys/types.h>
#include< sys/socket.h>
int connect(int sockfd,struct sockaddr * servaddr,int addrlen)
參數sockfd是函數socket返回的套接字描述符;參數servaddr指定遠程伺服器的套接字地址,包括伺服器的IP地址和埠號;參數addrlen指定這個套接字地址的長度。成功時返回0,否則返回-1,並設置全局變數為以下任何一種錯誤類型:ETIMEOUT、ECONNREFUSED、EHOSTUNREACH或ENETUNREACH。
在調用函數connect之前,客戶機需要指定伺服器進程的套接字地址。客戶機一般不需要指定自己的套接字地址(IP地址和埠號),系統會自動從1024至5000的埠號范圍內為它選擇一個未用的埠號,然後以這個埠號和本機的IP地址填充這個套接字地址。
客戶機調用函數connect來主動建立連接。這個函數將啟動TCP協議的3次握手過程。在建立連接之後或發生錯誤時函數返回。連接過程可能出現的錯誤情況有:
(1) 如果客戶機TCP協議沒有接收到對它的SYN數據段的確認,函數以錯誤返回,錯誤類型為ETIMEOUT。通常TCP協議在發送SYN數據段失敗之後,會多次發送SYN數據段,在所有的發送都高中失敗之後,函數以錯誤返回。
註:SYN(synchronize)位:請求連接。TCP用這種數據段向對方TCP協議請求建立連接。在這個數據段中,TCP協議將它選擇的初始序列號通知對方,並且與對方協議協商最大數據段大小。SYN數據段的序列號為初始序列號,這個SYN數據段能夠被確認。當協議接收到對這個數據段的確認之後,建立TCP連接。
(2) 如果遠程TCP協議返回一個RST數據段,函數立即以錯誤返回,錯誤類型為ECONNREFUSED。當遠程機器在SYN數據段指定的目的埠號處沒有服務進程在等待連接時,遠程機器的TCP協議將發送一個RST數據段,向客戶機報告這個錯誤。客戶機的TCP協議在接收到RST數據段後不再繼續發送SYN數據段,函數立即以錯誤返回。
註:RST(reset)位:表示請求重置連接。當TCP協議接收到一個不能處理的數據段時,向對方TCP協議發送這種數據段,表示這個數據段所標識的連接出現了某種錯誤,請求TCP協議將這個連接清除。有3種情況可能導致TCP協議發送RST數據段:(1)SYN數據段指定的目的埠處沒有接收進程在等待;(2)TCP協議想放棄一個已經存在的連接;(3)TCP接收到一個數據段,但是這個數據段所標識的連接不存在。接收到RST數據段的TCP協議立即將這條連接非正常地斷開,並向應用程序報告錯誤。
(3) 如果客戶機的SYN數據段導致某個路由器產生「目的地不可到達」類型的ICMP消息,函數以錯誤返回,錯誤類型為EHOSTUNREACH或ENETUNREACH。通常TCP協議在接收到這個ICMP消息之後,記錄這個消息,然後繼續幾次發送SYN數據段,在所有的發送都告失敗之後,TCP協議檢查這個ICMP消息,函數以錯誤返回。
註:ICMP:Internet 消息控制協議。Internet的運行主要是由Internet的路由器來控制,路由器完成IP數據包的發送和接收,如果發送數據包時發生錯誤,路由器使用ICMP協議來報告這些錯誤。ICMP數據包是封裝在IP數據包的數據部分中進行傳輸的,其格式如下:
類型
碼
校驗和
數據
0 8 16 24 31
類型:指出ICMP數據包的類型。
代碼:提供ICMP數據包的進一步信息。
校驗和:提供了對整個ICMP數據包內容的校驗和。
ICMP數據包主要有以下類型:
(1) 目的地不可到達:A、目的主機未運行;B、目的地址不存在;C、路由表中沒有目的地址對應的條目,因而路由器無法找到去往目的主機的路由。
(2) 超時:路由器將接收到的IP數據包的生存時間(TTL)域減1,如果這個域的值變為0,路由器丟棄這個IP數據包,並且發送這種ICMP消息。
(3) 參數出錯:當IP數據包中有無效域時發送。
(4) 重定向:將一條新的路徑通知主機。
(5) ECHO請求、ECHO回答:這兩條消息用語測試目的主機是否可以到達。請求者向目的主機發送ECHO請求ICMP數據包,目的主機在接收到這個ICMP數據包之後,返回ECHO回答ICMP數據包。
(6) 時戳請求、時戳回答:ICMP協議使用這兩種消息從其他機器處獲得其時鍾的當前時間。
調用函數connect的過程中,當客戶機TCP協議發送了SYN數據段的確認之後,TCP狀態由CLOSED狀態轉為SYN_SENT狀態,在接收到對SYN數據段的確認之後,TCP狀態轉換成ESTABLISHED狀態,函數成功返回。如果調用函數connect失敗,應該用close關閉這個套接字描述符,不能再次使用這個套接字描述符來調用函數connect。
註:TCP協議狀態轉換圖:
被動OPEN CLOSE 主動OPEN
(建立TCB) (刪除TCB) (建立TCB,
發送SYN)
接收SYN SEND
(發送SYN,ACK) (發送SYN)
接收SYN的ACK(無動作)
接收SYN的ACK 接收SYN,ACK
(無動作) (發送ACK)
CLOSE
(發送FIN) CLOSE 接收FIN
(發送FIN) (發送FIN)
接收FIN
接收FIN的ACK(無動作) (發送ACK) CLOSE(發送FIN)
接收FIN 接收FIN的ACK 接收FIN的ACK
(發送ACK) (無動作) (無動作)
2MSL超時(刪除TCB)
(3) 函數bind()
函數bind將本地地址與套接字綁定在一起,其定義如下:
#include< sys/types.h>
#include< sys/socket.h>
int bind(int sockfd,struct sockaddr * myaddr,int addrlen);
參數sockfd是函數sockt返回的套接字描述符;參數myaddr是本地地址;參數addrlen是套接字地址結構的長度。執行成功時返回0,否則,返回-1,並設置全局變數errno為錯誤類型EADDRINUSER。
伺服器和客戶機都可以調用函數bind來綁定套接字地址,但一般是伺服器調用函數bind來綁定自己的公認埠號。綁定操作一般有如下幾種組合方式:
表1
程序類型
IP地址
埠號
說明
伺服器
INADDR_ANY
非零值
指定伺服器的公認埠號
伺服器
本地IP地址
非零值
指定伺服器的IP地址和公認埠號
客戶機
INADDR_ANY
非零值
指定客戶機的連接埠號
客戶機
本地IP地址
非零值
指定客戶機的IP地址連接埠號
客戶機
本地IP地址
零
指定客戶機的IP地址
分別說明如下:
(1) 伺服器指定套接字地址的公認埠號,不指定IP地址:即伺服器調用bind時,設置套接字的IP地址為特殊的INADDE-ANY,表示它願意接收來自任何網路設備介面的客戶機連接。這是伺服器最常用的綁定方式。
(2) 伺服器指定套接字地址的公認埠號和IP地址:伺服器調用bind時,如果設置套接字的IP地址為某個本地IP地址,這表示這台機器只接收來自對應於這個IP地址的特定網路設備介面的客戶機連接。當伺服器有多塊網卡時,可以用這種方式來限制伺服器的接收范圍。
(3) 客戶機指定套接字地址的連接埠號:一般情況下,客戶機調用connect函數時不用指定自己的套接字地址的埠號。系統會自動為它選擇一個未用的埠號,並且用本地的IP地址來填充套接字地址中的相應項。但有時客戶機需要使用一個特定的埠號(比如保留埠號),而系統不會未客戶機自動分配一個保留埠號,所以需要調用函數bind來和一個未用的保留埠號綁定。
(4) 指定客戶機的IP地址和連接埠號:表示客戶機使用指定的網路設備介面和埠號進行通信。
(5) 指定客戶機的IP地址:表示客戶機使用指定的網路設備介面和埠號進行通信,系統自動為客戶機選一個未用的埠號。一般只有在主機有多個網路設備介面時使用。
我們一般不在客戶機上使用固定的客戶機埠號,除非是必須使用的情況。在客戶機上使用固定的埠號有以下不利:
(1) 伺服器執行主動關閉操作:伺服器最後進入TIME_WAIT狀態。當客戶機再次與這個伺服器進行連接時,仍使用相同的客戶機埠號,於是這個連接與前次連接的套接字對完全一樣,但是一呢、為前次連接處於TIME_WAIT狀態,並未消失,所以這次連接請求被拒絕,函connect以錯誤返回,錯誤類型為ECONNREFUSED
(2) 客戶機執行主動關閉操作:客戶機最後進入TIME_WAIT狀態。當馬上再次執行這個客戶機程序時,客戶機將繼續與這個固定客戶機埠號綁定,但因為前次連接處於TIME_WAIT狀態,並未消失,系統會發現這個埠號仍被佔用,所以這次綁定操作失敗,函數bind以錯誤返回,錯誤類型為EADDRINUSE。
(4) 函數listen()
函數listen將一個套接字轉換為征聽套接字,定義如下;
#include< sys/socket,h>
int listen(int sockfd,int backlog)
參數sockfd指定要轉換的套接字描述符;參數backlog設置請求隊列的最大長度;執行成功時返回0, 否則返回-1。函數listen功能有兩個:
(1) 將一個尚未連接的主動套接字(函數socket創建的可以用來進行主動連接但不能接受連接請求的套接字)轉換成一個被動連接套接字。執行listen之後,伺服器的TCP狀態由CLOSED轉為LISTEN狀態。
(2) TCP協議將到達的連接請求隊列,函數listen的第二個參數指定這個隊列的最大長度。
註:參數backlog的作用:
TCP協議為每一個征聽套接字維護兩個隊列:
(1) 未完成連接隊列:每個尚未完成3次握手操作的TCP連接在這個隊列中佔有一項。TCP希望儀在接收到一個客戶機SYN數據段之後,在這個隊列中創建一個新條目,然後發送對客戶機SYN數據段的確認和自己的SYN數據段(ACK+SYN數據段),等待客戶機對自己的SYN數據段的確認。此時,套接字處於SYN_RCVD狀態。這個條目將保存在這個隊列中,直到客戶機返回對SYN數據段的確認或者連接超時。
(2) 完成連接隊列:每個已經完成3次握手操作,但尚未被應用程序接收(調用函數accept)的TCP連接在這個隊列中佔有一項。當一個在未完成連接隊列中的連接接收到對SYN數據段的確認之後,完成3次握手操作,TCP協議將它從未完成連接隊列移到完成連接隊列中。此時,套接字處於ESTABLISHED狀態。這個條目將保存在這個隊列中,直到應用程序調用函數accept來接收它。
參數backlog指定某個征聽套接字的完成連接隊列的最大長度,表示這個套接字能夠接收的最大數目的未接收連接。如果當一個客戶機的SYN數據段到達時,征聽套接字的完成隊列已經滿了,那麼TCP協議將忽略這個SYN數據段。對於不能接收的SYN數據段,TCP協議不發送RST數據段,
(5) 函數accept()
函數accept從征聽套接字的完成隊列中接收一個已經建立起來的TCP連接。如果完成連接隊列為空,那麼這個進程睡眠。
#include< sys/socket.h>
int accept(int sockfd,struct sockaddr * addr,int * addrlen)
參數sockfd指定征聽套接字描述符;參數addr為指向一個Internet套接字地址結構的指針;參數addrlen為指向一個整型變數的指針。執行成功時,返回3個結果:函數返回值為一個新的套接字描述符,標識這個接收的連接;參數addr指向的結構變數中存儲客戶機地址;參數addrlen指向的整型變數中存儲客戶機地址的長度。失敗時返回-1。
征聽套接字專為接收客戶機連接請求,完成3次握手操作而用的,所以TCP協議不能使用征聽套接字描述符來標識這個連接,於是TCP協議創建一個新的套接字來標識這個要接收的連接,並將它的描述符發揮給應用程序。現在有兩個套接字,一個是調用函數accept時使用的征聽套接字,另一個是函數accept返回的連接套接字(connected socket)。一個伺服器通常只需創建一個征聽套接字,在伺服器進程的整個活動期間,用它來接收所有客戶機的連接請求,在伺服器進程終止前關閉這個征聽套接字;對於沒一個接收的(accepted)連接,TCP協議都創建一個新的連接套接字來標識這個連接,伺服器使用這個連接套接字與客戶機進行通信操作,當伺服器處理完這個客戶機請求時,關閉這個連接套接字。
當函數accept阻塞等待已經建立的連接時,如果進程捕獲到信號,函數將以錯誤返回,錯誤類型為EINTR。對於這種錯誤,一般重新調用函數accept來接收連接。
(6) 函數close()
函數close關閉一個套接字描述符。定義如下:
#include< unistd.h>
int close(int sockfd);
執行成功時返回0,否則返回-1。與操作文件描述符的close一樣,函數close將套接字描述符的引用計數器減1,如果描述符的引用計數大於0,則表示還有進程引用這個描述符,函數close正常返回;如果為0,則啟動清除套接字描述符的操作,函數close立即正常返回。
調用close之後,進程將不再能夠訪問這個套接字,但TCP協議將繼續使用這個套接字,將尚未發送的數據傳遞到對方,然後發送FIN數據段,執行關閉操作,一直等到這個TCP連接完全關閉之後,TCP協議才刪除該套接字。
(7) 函數read()和write()
用於從套接字讀寫數據。定義如下:
int read(int fd,char * buf,int len)
int write(int fd,char * buf,int len)
函數執行成功時,返回讀或寫的數據量的大小,失敗時返回-1。
每個TCP套接字都有兩個緩沖區:套接字發送緩沖區、套接字接收緩沖區,分別處理發送和接收任務。從網路讀、寫數據的操作是由TCP協議在內核中完成的:TCP協議將從網路上接收到的數據保存在相應套接字的接收緩沖區中,等待用戶調用函數將它們從接收緩沖區拷貝到用戶緩沖區;用戶將要發送的數據拷貝到相應套接字的發送緩沖區中,然後由TCP協議按照一定的演算法處理這些數據。
讀寫連接套接字的操作與讀寫文件的操作類似,也可以使用函數read和write。函數read完成將數據從套接字接收緩沖區拷貝到用戶緩沖區:當套接字接收緩沖區有數據可讀時,1:可讀數據量大於函數read指定值,返回函數參數len指定的數據量;2:了度數據量小於函數read指定值,函數read不等待請求的所有數據都到達,而是立即返回實際讀到的數據量;當無數據可讀時,函數read將阻塞不返回,等待數據到達。
當TCP協議接收到FIN數據段,相當於給讀操作一個文件結束符,此時read函數返回0,並且以後所有在這個套接字上的讀操作均返回0,這和普通文件中遇到文件結束符是一樣的。
當TCP協議接收到RST數據段,表示連接出現了某種錯誤,函數read將以錯誤返回,錯誤類型為ECONNERESET。並且以後所有在這個套接字上的讀操作均返回錯誤。錯誤返回時返回值小於0。
函數write完成將數據從用戶緩沖區拷貝到套接字發送緩沖區的任務:到套接字發送緩沖區有足夠拷貝所有用戶數據的空間時,函數write將數據拷貝到這個緩沖區中,並返回老輩的數量大小,如果可用空間小於write參數len指定的大小時,函數write將阻塞不返回,等待緩沖區有足夠的空間。
當TCP協議接收到RST數據段(當對方已經關閉了這條連接之後,繼續向這個套接字發送數據將導致對方TCP協議返回RST數據段),TCP協議接收到RST數據段時,函數write將以錯誤返回,錯誤類型為EINTR。以後可以繼續在這個套接字上寫數據。
(8) 函數getsockname()和getpeername()
函數getsockname返回套接字的本地地址;函數getpeername返回套接字對應的遠程地址。
10、 結束語
網路程序設計全靠套接字接收和發送信息。上文主要講述了Linux 下Socket的基本概念、Sockets API以及Socket所涉及到的TCP常識