~/cpp$ ./connect 192.168.1.234 1234 kkkkubuntu
block mode: ubuntu 14.04 : time used:21.0.001053s瀏覽器
connect 超時時間是大約21秒!服務器
注意:若是connect 127.x.x.x xxx kkkk 會當即返回由於127開頭的是網卡自身,你能夠ping一下,發現都是通的,且等同於127.0.0.1socket
#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/time.h>
#define BUFFER_SIZE 512
int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
int main( int argc, char* argv[] )
{
if( argc <= 3 )
{
printf( "usage: %s ip_address port_number send_bufer_size\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
struct sockaddr_in server_address;
bzero( &server_address, sizeof( server_address ) );
server_address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &server_address.sin_addr );
server_address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
int sendbuf = atoi( argv[3] );
int len = sizeof( sendbuf );
setsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof( sendbuf ) );
getsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, ( socklen_t* )&len );
printf( "the tcp send buffer size after setting is %d\n", sendbuf );
int old_option = fcntl( sock, F_GETFL );
printf("noblock: %d\n", old_option & O_NONBLOCK); //0-->block mode
//int oldopt = setnonblocking(sock); set nonblock mode!
struct timeval tv1, tv2;
gettimeofday(&tv1, NULL);
int ret = connect( sock, ( struct sockaddr* )&server_address, sizeof( server_address ) );
printf("connect ret code is: %d\n", ret);
if ( ret == 0 )
{
//
printf("call getsockname ...\n");
struct sockaddr_in local_address;
socklen_t length;
int ret = getpeername(sock, ( struct sockaddr* )&local_address, &length);
assert(ret == 0);
char local[INET_ADDRSTRLEN ];
printf( "local with ip: %s and port: %d\n",
inet_ntop( AF_INET, &local_address.sin_addr, local, INET_ADDRSTRLEN ), ntohs( local_address.sin_port ) );
//
char buffer[ BUFFER_SIZE ];
memset( buffer, 'a', BUFFER_SIZE );
send( sock, buffer, BUFFER_SIZE, 0 );
}
else if (ret == -1)
{
gettimeofday(&tv2, NULL);
suseconds_t msec = tv2.tv_usec - tv1.tv_usec;
time_t sec = tv2.tv_sec - tv1.tv_sec;
printf("time used:%d.%fs\n", sec, (double)msec / 1000000 );
printf("connect failed...\n");
if (errno == EINPROGRESS)
{
printf("unblock mode ret code...\n");
}
}
else
{
printf("ret code is: %d\n", ret);
}
printf("after connected!\n");
close( sock );
return 0;
}
非阻塞模式:tcp
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 1023
int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
int unblock_connect( const char* ip, int port, int time )
{
int ret = 0;
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
int fdopt = setnonblocking( sockfd );
ret = connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) );
printf("connect ret code = %d\n", ret);
if ( ret == 0 )
{
printf( "connect with server immediately\n" );
fcntl( sockfd, F_SETFL, fdopt ); //set old optional back
return sockfd;
}
//unblock mode --> connect return immediately! ret = -1 & errno=EINPROGRESS
else if ( errno != EINPROGRESS )
{
printf("ret = %d\n", ret);
printf( "unblock connect failed!\n" );
return -1;
}
else if (errno == EINPROGRESS)
{
printf( "unblock mode socket is connecting...\n" );
}
//use select to check write event, if the socket is writable, then
//connect is complete successfully!
fd_set readfds;
fd_set writefds;
struct timeval timeout;
FD_ZERO( &readfds );
FD_SET( sockfd, &writefds );
timeout.tv_sec = time; //timeout is 10 minutes
timeout.tv_usec = 0;
ret = select( sockfd + 1, NULL, &writefds, NULL, &timeout );
if ( ret <= 0 )
{
printf( "connection time out\n" );
close( sockfd );
return -1;
}
if ( ! FD_ISSET( sockfd, &writefds ) )
{
printf( "no events on sockfd found\n" );
close( sockfd );
return -1;
}
int error = 0;
socklen_t length = sizeof( error );
if( getsockopt( sockfd, SOL_SOCKET, SO_ERROR, &error, &length ) < 0 )
{
printf( "get socket option failed\n" );
close( sockfd );
return -1;
}
if( error != 0 )
{
printf( "connection failed after select with the error: %d \n", error );
close( sockfd );
return -1;
}
//connection successful!
printf( "connection ready after select with the socket: %d \n", sockfd );
fcntl( sockfd, F_SETFL, fdopt ); //set old optional back
return sockfd;
}
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
int sockfd = unblock_connect( ip, port, 10 );
if ( sockfd < 0 )
{
printf("sockfd error! return -1\n");
return 1;
}
//shutdown( sockfd, SHUT_WR ); //disable read and write
printf( "send data out\n" );
send( sockfd, "abc", 3, 0 );
shutdown( sockfd, SHUT_WR ); //disable read and write
close(sockfd);
return 0;
}
創建socket後默認connect()函數爲阻塞鏈接狀態,在大多數實現中,connect的超時時間在75s至幾分鐘之間,想要縮短超時時間,可解決問題的兩種方法:方法1、將socket句柄設置爲非阻塞狀態,方法2、採用信號處理函數設置阻塞超時控制。函數
在一個TCP套接口被設置爲非阻塞以後調用connect,connect會當即返回EINPROGRESS錯誤,表示鏈接操做正在進行中,可是仍未完成;同時TCP的三路握手操做繼續進行;在這以後,咱們能夠調用select來檢查這個連接是否創建成功;非阻塞connect有三種用途:
1.咱們能夠在三路握手的同時作一些其它的處理.connect操做要花一個往返時間完成,並且能夠是在任何地方,從幾個毫秒的局域網到幾百毫秒或幾秒的廣域網.在這段時間內咱們可能有一些其餘的處理想要執行;
2.能夠用這種技術同時創建多個鏈接.在Web瀏覽器中很廣泛;
3.因爲咱們使用select來等待鏈接的完成,所以咱們能夠給select設置一個時間限制,從而縮短connect的超時時間.在大多數實現中,connect的超時時間在75秒到幾分鐘之間.有時候應用程序想要一個更短的超時時間,使用非阻塞connect就是一種方法;
非阻塞connect聽起來雖然簡單,可是仍然有一些細節問題要處理:
1.即便套接口是非阻塞的,若是鏈接的服務器在同一臺主機上,那麼在調用connect創建鏈接時,鏈接一般會當即創建成功.咱們必須處理這種狀況;
2.源自Berkeley的實現(和Posix.1g)有兩條與select和非阻塞IO相關的規則:
A:當鏈接創建成功時,套接口描述符變成可寫;
B:當鏈接出錯時,套接口描述符變成既可讀又可寫;
注意:當一個套接口出錯時,它會被select調用標記爲既可讀又可寫;spa
非阻塞connect有這麼多好處,可是處理非阻塞connect時會遇到不少可移植性問題;code
處理非阻塞connect的步驟:
第一步:建立socket,返回套接口描述符;
第二步:調用fcntl把套接口描述符設置成非阻塞;
第三步:調用connect開始創建鏈接;
第四步:判斷鏈接是否成功創建;
A:若是connect返回0,表示鏈接簡稱成功(服務器可客戶端在同一臺機器上時就有可能發生這種狀況);
B:調用select來等待鏈接創建成功完成;
若是select返回0,則表示創建鏈接超時;咱們返回超時錯誤給用戶,同時關閉鏈接,以防止三路握手操做繼續進行下去;
若是select返回大於0的值,則須要檢查套接口描述符是否可讀或可寫;若是套接口描述符可讀或可寫,則咱們能夠經過調用getsockopt來獲得套接口上待處理的錯誤(SO_ERROR),若是鏈接創建成功,這個錯誤值將是0,若是創建鏈接時遇到錯誤,則這個值是鏈接錯誤所對應的errno值(好比:ECONNREFUSED,ETIMEDOUT等).
"讀取套接口上的錯誤"是遇到的第一個可移植性問題;若是出現問題,getsockopt源自Berkeley的實現是返回0,等待處理的錯誤在變量errno中返回;可是Solaris會讓getsockopt返回-1,errno置爲待處理的錯誤;咱們對這兩種狀況都要處理;server
這樣,在處理非阻塞connect時,在不一樣的套接口實現的平臺中存在的移植性問題,首先,有可能在調用select以前,鏈接就已經創建成功,並且對方的數據已經到來.在這種狀況下,鏈接成功時套接口將既可讀又可寫.這和鏈接失敗時是同樣的.這個時候咱們還得經過getsockopt來讀取錯誤值;這是第二個可移植性問題;
移植性問題總結:
1.對於出錯的套接口描述符,getsockopt的返回值源自Berkeley的實現是返回0,待處理的錯誤值存儲在errno中;而源自Solaris的實現是返回-1,待處理的錯誤存儲在errno中;(套接口描述符出錯時調用getsockopt的返回值不可移植)
2.有可能在調用select以前,鏈接就已經創建成功,並且對方的數據已經到來,在這種狀況下,套接口描述符是既可讀又可寫;這與套接口描述符出錯時是同樣的;(怎樣判斷鏈接是否創建成功的條件不可移植)blog
這樣的話,在咱們判斷鏈接是否創建成功的條件不惟一時,咱們能夠有如下的方法來解決這個問題:
1.調用getpeername代替getsockopt.若是調用getpeername失敗,getpeername返回ENOTCONN,表示鏈接創建失敗,咱們必須以SO_ERROR調用getsockopt獲得套接口描述符上的待處理錯誤;
2.調用read,讀取長度爲0字節的數據.若是read調用失敗,則表示鏈接創建失敗,並且read返回的errno指明瞭鏈接失敗的緣由.若是鏈接創建成功,read應該返回0;
3.再調用一次connect.它應該失敗,若是錯誤errno是EISCONN,就表示套接口已經創建,並且第一次鏈接是成功的;不然,鏈接就是失敗的;
被中斷的connect:
若是在一個阻塞式套接口上調用connect,在TCP的三路握手操做完成以前被中斷了,好比說,被捕獲的信號中斷,將會發生什麼呢?假定connect不會自動重啓,它將返回EINTR.那麼,這個時候,咱們就不能再調用connect等待鏈接創建完成了,若是再次調用connect來等待鏈接創建完成的話,connect將會返回錯誤值EADDRINUSE.在這種狀況下,應該作的是調用select,就像在非阻塞式connect中所作的同樣.而後,select在鏈接創建成功(使套接口描述符可寫)或鏈接創建失敗(使套接口描述符既可讀又可寫)時返回;
方法2、定義信號處理函數:
首先定義一箇中斷信號處理函數u_alarm_handler,用於超時後的報警處理,而後定義一個2秒的定時器,執行connect,當系統connect成功,則系統正常執行下去;若是connect不成功阻塞在這裏,則超過定義的2秒後,系統會產生一個信號,觸發執行u_alarm_handler函數, 當執行完u_alarm_handler後,程序將繼續從connect的下面一行執行下去。
其中,處理函數能夠以下定義,也能夠加入更多的錯誤處理。